{
	"openapi": "3.0.3",
	"info": {
		"title": "ArdaForm HTTP API",
		"version": "1.0.0",
		"description": "Get data into a project and back out again, over plain HTTP and JSON.\n\n**Authentication** — every endpoint except the indexes needs a project key, made in the portal on a project's API keys tab, sent as `Authorization: Bearer af_…`. The key names its project, so no endpoint takes a project id.\n\n**Errors** are always `{\"error\": {\"code\", \"message\"}}`. Match on `code`.\n\n**Scope** — a key is `read` or `write`, and read never implies write.\n\nFull reference: https://ardaform.net/docs/api-reference",
		"contact": {
			"name": "ArdaForm",
			"url": "https://ardaform.net/contact"
		}
	},
	"servers": [
		{
			"url": "https://api.ardaform.net",
			"description": "Live"
		}
	],
	"security": [
		{
			"bearerAuth": []
		}
	],
	"components": {
		"securitySchemes": {
			"bearerAuth": {
				"type": "http",
				"scheme": "bearer",
				"description": "A project key from the portal. Begins `af_`."
			}
		},
		"schemas": {
			"Error": {
				"type": "object",
				"description": "The shape of every failure, whatever went wrong.",
				"properties": {
					"error": {
						"type": "object",
						"properties": {
							"code": {
								"type": "string",
								"description": "Stable. **Match on this.** Listed per endpoint in the reference."
							},
							"message": {
								"type": "string",
								"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
							}
						},
						"additionalProperties": true,
						"required": [
							"code",
							"message"
						]
					}
				}
			},
			"Dataset": {
				"type": "object",
				"description": "A named collection of records — a catalogue, a price list, a transaction log. It belongs to the project the key was made on.",
				"properties": {
					"key": {
						"type": "string",
						"description": "How you address it, and what your own code says. Lower-case letters, digits, hyphen and underscore, starting with a letter or digit. Immutable once created."
					},
					"name": {
						"type": "string",
						"description": "For people. Change it freely."
					},
					"description": {
						"type": "string",
						"description": "Optional. What it is for.",
						"nullable": true
					},
					"access": {
						"type": "string",
						"description": "What the app ON THE DEVICE may do. It does not restrict this API — a read-only dataset still has to be filled, and this is how.",
						"enum": [
							"rw",
							"ro"
						]
					},
					"partition_field": {
						"type": "string",
						"description": "The field on a record that splits the dataset, and the query parameter you filter on. Several devices may share one value — three tills in one shop share their sales. The field may hold a LIST, which puts one record in several partitions at once: one price four branches share is one record, not four copies. Null means the dataset is not partitioned.",
						"nullable": true
					},
					"retention_mode": {
						"type": "string",
						"description": "What happens when the dataset runs out of room. `never` keeps everything and REFUSES new records once it is full — nothing already written is thrown away. `days` drops records older than `retention_value` days. `space` drops the oldest once `retention_value` per cent of the limit is used, so the dataset rolls rather than stops.",
						"enum": [
							"never",
							"days",
							"space"
						]
					},
					"retention_value": {
						"type": "integer",
						"description": "Days for `days`, per cent for `space`. Null for `never`, and setting it there is ignored.",
						"nullable": true
					},
					"storage_limit_bytes": {
						"type": "integer",
						"description": "A ceiling on this dataset alone, inside the account allowance. Null means it is bounded only by the account.",
						"nullable": true
					},
					"cursor": {
						"type": "integer",
						"description": "This dataset's current sync number. A client that has just read the whole dataset takes this as the `since` for its first incremental call. Opaque — compare it, never do arithmetic on it."
					},
					"records": {
						"type": "integer",
						"description": "Counted at read time, never stored — a kept counter is a second copy of the truth and the first half-write puts the two out of agreement for ever."
					},
					"bytes": {
						"type": "integer",
						"description": "The payloads as stored, and what the storage allowance is measured against. No index or row overhead — it is a number you could work out yourself from the data you sent."
					},
					"updated_at": {
						"type": "string",
						"description": "When the DATA last changed, not the row — a rename does not move it. UTC, like every timestamp here.",
						"format": "date-time"
					},
					"created_at": {
						"type": "string",
						"description": "When the dataset was made. UTC, like every timestamp here.",
						"format": "date-time"
					}
				}
			},
			"Record": {
				"type": "object",
				"description": "One row of your data. What is inside `data` is entirely yours; the API only reads the two fields it needs to address and partition it.",
				"properties": {
					"key": {
						"type": "string",
						"description": "The record's own id, as you know it — taken from `id`, `key` or `record_key` in the object you sent. With one, a write is an upsert; without one it is an append. Null means it was appended.",
						"nullable": true
					},
					"partitions": {
						"type": "array",
						"description": "The partitions this record belongs to, lifted out of the payload using the dataset's `partition_field`. ALWAYS an array — empty when the dataset is not partitioned, one entry for a single value, several when the field held a list. One price four branches share is one record saying `\"branch\": [\"oldbury\", \"kings-heath\", …]`, not four copies that have to be kept in step.",
						"items": {
							"type": "string"
						}
					},
					"data": {
						"type": "object",
						"description": "Your record, parsed. Returned as an object, never as a JSON string — left as text it would arrive double-encoded, which no importer reads."
					},
					"created_at": {
						"type": "string",
						"description": "When the record first arrived. UTC.",
						"format": "date-time"
					},
					"updated_at": {
						"type": "string",
						"description": "When it was last written — an upsert moves it. UTC.",
						"format": "date-time"
					}
				}
			},
			"Webhook": {
				"type": "object",
				"description": "An HTTP endpoint of yours that the app on a device may send a message to — `Arda.webhook.call(slug, data)`. The plane makes the call, signs it and records every delivery; the device never holds your key and never talks to your server directly.",
				"properties": {
					"slug": {
						"type": "string",
						"description": "How the app addresses it, and what your code says. Lower-case letters, digits, hyphen and underscore, starting with a letter or digit. FROZEN after create — `verify` is reserved."
					},
					"name": {
						"type": "string",
						"description": "For people. Change it freely."
					},
					"url": {
						"type": "string",
						"description": "Where the plane sends. http or https on a public host: localhost and loopback, link-local and private addresses are refused when saved, and refused again by the plane at call time after the name resolves. NULL FOR A WEBHOOK THAT ONLY RECORDS ITS CALLS: nothing is sent, the app is answered `ok` with status 202, and every call is a delivery whose envelope `…/deliveries` hands back — a mailbox your own systems poll.",
						"nullable": true
					},
					"method": {
						"type": "string",
						"description": "How it is sent. `post` and `put` carry the envelope as a JSON body; `get` carries the same fields as query parameters.",
						"enum": [
							"post",
							"get",
							"put"
						]
					},
					"hasKey": {
						"type": "boolean",
						"description": "Whether a key is set. THE KEY ITSELF IS NEVER RETURNED: set it with `key` on create or PATCH, clear it with `\"key\": null`. When set, every delivery carries it as `Authorization: Bearer` and is signed with it (`X-Arda-Signature`)."
					},
					"verify": {
						"type": "boolean",
						"description": "Mint a one-time `X-Arda-Verify` token per delivery, which your server hands back to `POST /v1/webhooks/verify` to prove the call came from here. Off by default. No effect on a webhook with no url — nothing is sent, so there is nobody to hand a token to."
					},
					"enabled": {
						"type": "boolean",
						"description": "Off, and the app's call is refused on the device and nothing is sent. The delivery log is kept."
					},
					"note": {
						"type": "string",
						"description": "Optional. Whatever helps the next person.",
						"nullable": true
					},
					"createdAt": {
						"type": "string",
						"description": "When it was made. UTC.",
						"format": "date-time"
					},
					"updatedAt": {
						"type": "string",
						"description": "When its definition last changed. A delivery does not move it. UTC.",
						"format": "date-time"
					}
				}
			},
			"WebhookDelivery": {
				"type": "object",
				"description": "One call the plane made to your server — the same id your server saw as `X-Arda-Delivery` — or, on a webhook with no url, one call it recorded instead of sending. The plane keeps the most recent ones per webhook (500 rows or seven days), never counted against your storage.",
				"properties": {
					"id": {
						"type": "string",
						"description": "The delivery id: `dlv_` and sixteen hex characters. What `X-Arda-Delivery` and the envelope's `id` carried, and what `/verify` takes."
					},
					"at": {
						"type": "string",
						"description": "When the plane made the call. UTC.",
						"format": "date-time"
					},
					"device": {
						"type": "object",
						"description": "The device whose app sent it, or null for a Test run from the portal or from `POST /v1/webhooks/{slug}/test`.",
						"nullable": true
					},
					"status": {
						"type": "integer",
						"description": "The HTTP status your server answered. 0 means it was never reached — `error` says why. On a webhook with no url it is always 202: recorded, nothing sent."
					},
					"ok": {
						"type": "boolean",
						"description": "Whether `status` was 2xx."
					},
					"ms": {
						"type": "integer",
						"description": "How long the call took, in milliseconds. A timeout counts the whole wait."
					},
					"error": {
						"type": "string",
						"description": "Why `status` is 0: a refused address, a timeout, TLS, a connection fault. Null when your server answered.",
						"nullable": true
					},
					"verified": {
						"type": "boolean",
						"description": "Whether your server has presented this delivery's token to `/verify` and been answered `valid: true`. Always false for a webhook with `verify` off."
					},
					"verifiedAt": {
						"type": "string",
						"description": "When that first happened, or null.",
						"format": "date-time",
						"nullable": true
					},
					"payload": {
						"type": "object",
						"description": "The envelope as the plane built it — what your server was sent, or what a webhook with no url recorded: `id`, `webhook`, `project`, `device`, `at`, `verify` and `data`, the app's own JSON untouched. ONLY on `GET …/deliveries/{id}`, and on the list with `include=payload`. Null for a delivery recorded before 2026-10-06, which kept none.",
						"nullable": true
					},
					"response": {
						"type": "string",
						"description": "The first 1024 characters of what your server answered. ONLY on `GET …/deliveries/{id}`. Null when it answered nothing, was never reached, or the webhook has no url.",
						"nullable": true
					}
				}
			}
		}
	},
	"tags": [
		{
			"name": "auth",
			"description": "Who this key is"
		},
		{
			"name": "datasets",
			"description": "The shape of a project's data"
		},
		{
			"name": "records",
			"description": "The data itself"
		},
		{
			"name": "files",
			"description": "A project's assets. Metadata only — nothing stores the bytes yet."
		},
		{
			"name": "webhooks",
			"description": "Where the app on a device may send a message, and the record of every message sent"
		}
	],
	"paths": {
		"/v1/auth": {
			"get": {
				"tags": [
					"auth"
				],
				"summary": "Confirm a key and see what it is for",
				"operationId": "whoami",
				"description": "The first call anybody makes. `limits` is the account's resolved allowance — package plus overrides — worth reading once at start-up rather than discovering a ceiling by hitting it.\n\n`key.scopes` says what this key may do per area, which `key.scope` cannot: the enum is `write` if the key may write ANYTHING, and cannot tell \"writes everything\" from \"appends to one dataset\". ⚠️ `scopes: null` means the key predates scopes and falls back to the enum — it is NOT \"may do nothing\".",
				"responses": {
					"200": {
						"description": "The key, its project and the account limits",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"key": {
											"type": "object",
											"properties": {
												"name": {
													"type": "string"
												},
												"prefix": {
													"type": "string",
													"description": "The first eight characters. The token itself is stored only as a sha256 and cannot be shown again by anything."
												},
												"scope": {
													"type": "string",
													"enum": [
														"read",
														"write"
													]
												},
												"branch": {
													"type": "string",
													"nullable": true,
													"description": "null means every branch."
												},
												"scopes": {
													"type": "object",
													"description": "What one API key may do, per area of the API. Null on a key made before scopes existed — which means it falls back to `scope`, NOT that it may do nothing.",
													"properties": {
														"code": {
															"type": "object",
															"description": "Branches. Nothing serves code over this API yet, so these are recorded rather than enforced — but they are where a key's branch scope lives, and `branch` is derived from them."
														},
														"datasets": {
															"type": "object",
															"description": "Managing the dataset objects themselves — `/v1/datasets`."
														},
														"records": {
															"type": "object",
															"description": "The data — `/v1/datasets/{key}/records` and `/changes`. THE ONE AREA THAT CAN BE NARROWED TO PARTICULAR DATASETS, which is the useful shape: a till key that appends to `transactions` and reads `products` and touches nothing else."
														},
														"files": {
															"type": "object",
															"description": "Assets — `/v1/files` and `/v1/content`."
														},
														"webhooks": {
															"type": "object",
															"description": "Webhooks — `/v1/webhooks`. `r` lists them, reads the delivery log and answers `/verify`; `a` creates; `w` changes one or runs a Test; `d` deletes. A document written before this area existed has no entry for it and is refused here until the key is edited in the portal."
														},
														"r": {
															"type": "string",
															"description": "Inside each area: `\"*\"` for any, or a list of dataset keys. Read."
														},
														"w": {
															"type": "string",
															"description": "Write — change what is already there."
														},
														"a": {
															"type": "string",
															"description": "Add — create new ones. Separate from `w` because \"may change the datasets it was given\" and \"may make more\" are different powers."
														},
														"d": {
															"type": "string",
															"description": "Delete."
														}
													},
													"nullable": true
												},
												"expires_on": {
													"type": "string",
													"format": "date",
													"nullable": true,
													"description": "The last DAY this key works, or null for never. It ends the day rather than the midnight before it. A refusal after that is 401 KEY_EXPIRED — deliberately not KEY_REVOKED, because one says \"ask why\" and the other says \"ask for a new one\"."
												},
												"created_at": {
													"type": "string",
													"format": "date-time"
												}
											}
										},
										"project": {
											"type": "object"
										},
										"account": {
											"type": "object"
										},
										"limits": {
											"type": "object",
											"description": "The package's resolved allowance, plus the API's own per-call ceilings."
										}
									}
								}
							}
						}
					},
					"401": {
						"description": "NO_KEY · BAD_KEY · KEY_REVOKED · KEY_EXPIRED",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"403": {
						"description": "ACCOUNT_INACTIVE · NO_API_ACCESS",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			}
		},
		"/v1/files": {
			"get": {
				"tags": [
					"files"
				],
				"summary": "The project's assets",
				"operationId": "listFiles",
				"description": "⚠️ **Metadata only.** Nothing stores or serves the bytes yet, and every response says so with `bytes_stored: false` — a list of files with sizes and hashes reads exactly like something you can download.\n\nTwo folder filters, because there are two questions: `path` is that folder exactly, `under` is that folder and everything below it.",
				"parameters": [
					{
						"name": "path",
						"in": "query",
						"schema": {
							"type": "string"
						},
						"description": "That folder exactly. No leading or trailing slash; the root is an empty string."
					},
					{
						"name": "under",
						"in": "query",
						"schema": {
							"type": "string"
						},
						"description": "That folder and everything below it."
					},
					{
						"name": "partition",
						"in": "query",
						"schema": {
							"type": "string"
						},
						"description": "Files in that partition. One asset four branches share is one file."
					},
					{
						"name": "limit",
						"in": "query",
						"schema": {
							"type": "integer",
							"minimum": 1,
							"maximum": 1000,
							"default": 200
						}
					},
					{
						"name": "offset",
						"in": "query",
						"schema": {
							"type": "integer",
							"default": 0
						}
					}
				],
				"responses": {
					"200": {
						"description": "A page of files",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"total": {
											"type": "integer"
										},
										"limit": {
											"type": "integer"
										},
										"offset": {
											"type": "integer"
										},
										"has_more": {
											"type": "boolean"
										},
										"bytes_stored": {
											"type": "boolean",
											"description": "False. Nothing stores the bytes yet."
										},
										"files": {
											"type": "array",
											"items": {
												"type": "object",
												"description": "One asset belonging to a project — an image, a stylesheet, a PDF. METADATA ONLY: nothing stores or serves the bytes yet.",
												"properties": {
													"path": {
														"type": "string",
														"description": "The folder it lives in, with no leading or trailing slash. An empty string is the root. One canonical spelling is what makes `?path=` and a folder tree agree with each other."
													},
													"name": {
														"type": "string",
														"description": "The file name, with its extension. No slashes — the folder is `path`."
													},
													"at": {
														"type": "string",
														"description": "Path and name together, which is what you put in a URL and most likely what your own code already says."
													},
													"hash": {
														"type": "string",
														"description": "sha256 of the contents, and how you answer \"have I already got this one?\" without downloading it — the whole of an efficient device sync. SET BY THE UPLOAD AND ONLY BY THE UPLOAD: `POST /v1/files` refuses a stated one, because a hash you send is a claim about bytes this service does not have. NOT the identity either: two folders may hold the same bytes, and a file whose contents change keeps its name.",
														"nullable": true
													},
													"mime": {
														"type": "string",
														"description": "What the writer said it was. Checked against the extension on write, and not otherwise validated.",
														"nullable": true
													},
													"bytes": {
														"type": "integer",
														"description": "Counts against the ACCOUNT allowance, shared with your code and your datasets. There is no separate file allowance and no per-file size limit."
													},
													"width": {
														"type": "integer",
														"description": "What the writer happened to know. Nothing validates it, and a file whose writer knew nothing about it is a perfectly good row.",
														"nullable": true
													},
													"height": {
														"type": "integer",
														"description": "As `width`.",
														"nullable": true
													},
													"partitions": {
														"type": "array",
														"description": "The partitions this file belongs to. ALWAYS an array — one asset four branches share is one file, not four copies. A write REPLACES the list rather than adding to it.",
														"items": {
															"type": "string"
														}
													},
													"created_at": {
														"type": "string",
														"description": "When it was first registered. UTC.",
														"format": "date-time"
													},
													"has_content": {
														"type": "boolean",
														"description": "Whether the bytes are here. `POST /v1/files` registers what a file IS; `PUT /v1/content/{path}` puts what it holds and is the only thing that sets `hash` — so this is never a claim. Reconciling a library needs to tell \"not uploaded yet\" from \"done\"."
													},
													"content_at": {
														"type": "string",
														"description": "Where to GET the bytes, or null when there are none. The storage layout behind it is ours — a blob is named after its own sha256, in a directory per tenant and project, and none of that is constructible from anything returned here.",
														"nullable": true
													}
												}
											}
										}
									}
								}
							}
						}
					},
					"400": {
						"description": "BAD_PARAM · BAD_PATH",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			},
			"post": {
				"tags": [
					"files"
				],
				"summary": "Register a file, or update the one at that path",
				"operationId": "putFile",
				"description": "Addressed by `path` + `name`, so sending it twice updates rather than duplicating — a caller republishing an asset should not have to find out first whether we had a row for it.\n\n⚠️ **`partitions` REPLACES the list, it does not add to it.** An asset that stops being shared with a branch has to stop being served to it.\n\n⚠️ **An allowlist of types is the one opinion this platform has about a file.** Any size inside your account allowance, any dimensions, any contents — but not an executable, and a denylist of those is a list you lose. `css`, `js` and `html` ARE allowed: this is a platform for building apps out of exactly those three.",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"required": [
									"name"
								],
								"properties": {
									"name": {
										"type": "string",
										"description": "No slashes — the folder goes in `path`."
									},
									"path": {
										"type": "string"
									},
									"mime": {
										"type": "string"
									},
									"bytes": {
										"type": "integer",
										"description": "Counts against the ACCOUNT allowance, shared with code and datasets."
									},
									"width": {
										"type": "integer"
									},
									"height": {
										"type": "integer"
									},
									"partitions": {
										"description": "A string or a list of them.",
										"oneOf": [
											{
												"type": "string"
											},
											{
												"type": "array",
												"items": {
													"type": "string"
												}
											}
										]
									}
								}
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Updated",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"file": {
											"type": "object",
											"description": "One asset belonging to a project — an image, a stylesheet, a PDF. METADATA ONLY: nothing stores or serves the bytes yet.",
											"properties": {
												"path": {
													"type": "string",
													"description": "The folder it lives in, with no leading or trailing slash. An empty string is the root. One canonical spelling is what makes `?path=` and a folder tree agree with each other."
												},
												"name": {
													"type": "string",
													"description": "The file name, with its extension. No slashes — the folder is `path`."
												},
												"at": {
													"type": "string",
													"description": "Path and name together, which is what you put in a URL and most likely what your own code already says."
												},
												"hash": {
													"type": "string",
													"description": "sha256 of the contents, and how you answer \"have I already got this one?\" without downloading it — the whole of an efficient device sync. SET BY THE UPLOAD AND ONLY BY THE UPLOAD: `POST /v1/files` refuses a stated one, because a hash you send is a claim about bytes this service does not have. NOT the identity either: two folders may hold the same bytes, and a file whose contents change keeps its name.",
													"nullable": true
												},
												"mime": {
													"type": "string",
													"description": "What the writer said it was. Checked against the extension on write, and not otherwise validated.",
													"nullable": true
												},
												"bytes": {
													"type": "integer",
													"description": "Counts against the ACCOUNT allowance, shared with your code and your datasets. There is no separate file allowance and no per-file size limit."
												},
												"width": {
													"type": "integer",
													"description": "What the writer happened to know. Nothing validates it, and a file whose writer knew nothing about it is a perfectly good row.",
													"nullable": true
												},
												"height": {
													"type": "integer",
													"description": "As `width`.",
													"nullable": true
												},
												"partitions": {
													"type": "array",
													"description": "The partitions this file belongs to. ALWAYS an array — one asset four branches share is one file, not four copies. A write REPLACES the list rather than adding to it.",
													"items": {
														"type": "string"
													}
												},
												"created_at": {
													"type": "string",
													"description": "When it was first registered. UTC.",
													"format": "date-time"
												},
												"has_content": {
													"type": "boolean",
													"description": "Whether the bytes are here. `POST /v1/files` registers what a file IS; `PUT /v1/content/{path}` puts what it holds and is the only thing that sets `hash` — so this is never a claim. Reconciling a library needs to tell \"not uploaded yet\" from \"done\"."
												},
												"content_at": {
													"type": "string",
													"description": "Where to GET the bytes, or null when there are none. The storage layout behind it is ours — a blob is named after its own sha256, in a directory per tenant and project, and none of that is constructible from anything returned here.",
													"nullable": true
												}
											}
										}
									}
								}
							}
						}
					},
					"201": {
						"description": "Registered",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"file": {
											"type": "object",
											"description": "One asset belonging to a project — an image, a stylesheet, a PDF. METADATA ONLY: nothing stores or serves the bytes yet.",
											"properties": {
												"path": {
													"type": "string",
													"description": "The folder it lives in, with no leading or trailing slash. An empty string is the root. One canonical spelling is what makes `?path=` and a folder tree agree with each other."
												},
												"name": {
													"type": "string",
													"description": "The file name, with its extension. No slashes — the folder is `path`."
												},
												"at": {
													"type": "string",
													"description": "Path and name together, which is what you put in a URL and most likely what your own code already says."
												},
												"hash": {
													"type": "string",
													"description": "sha256 of the contents, and how you answer \"have I already got this one?\" without downloading it — the whole of an efficient device sync. SET BY THE UPLOAD AND ONLY BY THE UPLOAD: `POST /v1/files` refuses a stated one, because a hash you send is a claim about bytes this service does not have. NOT the identity either: two folders may hold the same bytes, and a file whose contents change keeps its name.",
													"nullable": true
												},
												"mime": {
													"type": "string",
													"description": "What the writer said it was. Checked against the extension on write, and not otherwise validated.",
													"nullable": true
												},
												"bytes": {
													"type": "integer",
													"description": "Counts against the ACCOUNT allowance, shared with your code and your datasets. There is no separate file allowance and no per-file size limit."
												},
												"width": {
													"type": "integer",
													"description": "What the writer happened to know. Nothing validates it, and a file whose writer knew nothing about it is a perfectly good row.",
													"nullable": true
												},
												"height": {
													"type": "integer",
													"description": "As `width`.",
													"nullable": true
												},
												"partitions": {
													"type": "array",
													"description": "The partitions this file belongs to. ALWAYS an array — one asset four branches share is one file, not four copies. A write REPLACES the list rather than adding to it.",
													"items": {
														"type": "string"
													}
												},
												"created_at": {
													"type": "string",
													"description": "When it was first registered. UTC.",
													"format": "date-time"
												},
												"has_content": {
													"type": "boolean",
													"description": "Whether the bytes are here. `POST /v1/files` registers what a file IS; `PUT /v1/content/{path}` puts what it holds and is the only thing that sets `hash` — so this is never a claim. Reconciling a library needs to tell \"not uploaded yet\" from \"done\"."
												},
												"content_at": {
													"type": "string",
													"description": "Where to GET the bytes, or null when there are none. The storage layout behind it is ours — a blob is named after its own sha256, in a directory per tenant and project, and none of that is constructible from anything returned here.",
													"nullable": true
												}
											}
										}
									}
								}
							}
						}
					},
					"400": {
						"description": "BAD_FILE_NAME · BAD_FILE_TYPE · TYPE_MISMATCH · BAD_PATH · HASH_NOT_YOURS · BAD_PARTITION · BAD_PARAM",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"403": {
						"description": "READ_ONLY · STORAGE_FULL",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			}
		},
		"/v1/content/{path}": {
			"parameters": [
				{
					"name": "path",
					"in": "path",
					"required": true,
					"schema": {
						"type": "string"
					},
					"description": "The file's folder and name — `menu/tiles/bread.webp`."
				}
			],
			"get": {
				"tags": [
					"files"
				],
				"summary": "Download the bytes",
				"operationId": "getContent",
				"description": "The bytes, with the stored mime and the hash as a strong `ETag`.\n\n⚠️ A separate route from `/v1/files/{path}` rather than a suffix on it, because everything after `/v1/files/` IS the path — `…/content` would collide with a file genuinely named `content`.",
				"responses": {
					"200": {
						"description": "The file",
						"content": {
							"application/octet-stream": {
								"schema": {
									"type": "string",
									"format": "binary"
								}
							}
						}
					},
					"404": {
						"description": "NO_FILE",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"409": {
						"description": "NO_CONTENT_YET — registered, never uploaded. A different problem from 404, with a different fix",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"410": {
						"description": "CONTENT_GONE — the row says there is a file and the disk disagrees. Ours, not yours; re-upload",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			},
			"put": {
				"tags": [
					"files"
				],
				"summary": "Upload the bytes",
				"operationId": "putContent",
				"description": "⚠️ **Send `{\"base64\": \"…\"}` with `Content-Type: application/json`.** A raw binary body is refused today with `400 BINARY_UNSUPPORTED`, carrying the byte counts that prove why.\n\nThe process manager in front of this service hands an HTTP body over as DECODED TEXT — every byte that is not valid UTF-8 becomes U+FFFD, so an 86-byte PNG arrives as 85 characters and re-encodes to 121. It cannot be repaired here; the information is gone upstream. Base64 survives that path losslessly, which is the whole reason base64 exists.\n\n**Raw bodies work today for UTF-8-safe types** — css, js, html, json, csv, svg, txt. Anything else is refused rather than stored wrong.\n\nIdempotent by construction: the blob is named after its own sha256, so re-sending identical bytes writes nothing and answers `stored: false`. That is the signal for reconciling a large library — but check `GET /v1/files` first and send only what moved.",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"required": [
									"base64"
								],
								"properties": {
									"base64": {
										"type": "string",
										"description": "The file, base64 encoded."
									},
									"mime": {
										"type": "string",
										"description": "The FILE's type, not the envelope's. Without it the file is stored with no mime and served as application/octet-stream."
									}
								}
							}
						},
						"application/octet-stream": {
							"schema": {
								"type": "string",
								"format": "binary",
								"description": "UTF-8-safe types only, today."
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Replaced",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"at": {
											"type": "string"
										},
										"hash": {
											"type": "string"
										},
										"bytes": {
											"type": "integer"
										},
										"mime": {
											"type": "string"
										},
										"stored": {
											"type": "boolean"
										},
										"created": {
											"type": "boolean"
										}
									}
								}
							}
						}
					},
					"201": {
						"description": "Created",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"at": {
											"type": "string"
										},
										"hash": {
											"type": "string"
										},
										"bytes": {
											"type": "integer"
										},
										"mime": {
											"type": "string"
										},
										"stored": {
											"type": "boolean",
											"description": "False when nothing was written — identical bytes were already on disk."
										},
										"created": {
											"type": "boolean"
										}
									}
								}
							}
						}
					},
					"400": {
						"description": "BINARY_UNSUPPORTED · BAD_BASE64 · NO_BODY · BAD_FILE_NAME · BAD_FILE_TYPE · TYPE_MISMATCH · BAD_PATH",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"403": {
						"description": "READ_ONLY · STORAGE_FULL",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"413": {
						"description": "TOO_LARGE",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			}
		},
		"/v1/files/{path}": {
			"parameters": [
				{
					"name": "path",
					"in": "path",
					"required": true,
					"schema": {
						"type": "string"
					},
					"description": "The file's folder and name together — `menu/tiles/bread.webp`. Addressed by path rather than by an id, because the path is what your own code already says."
				}
			],
			"get": {
				"tags": [
					"files"
				],
				"summary": "One file",
				"operationId": "getFile",
				"responses": {
					"200": {
						"description": "The file",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"file": {
											"type": "object",
											"description": "One asset belonging to a project — an image, a stylesheet, a PDF. METADATA ONLY: nothing stores or serves the bytes yet.",
											"properties": {
												"path": {
													"type": "string",
													"description": "The folder it lives in, with no leading or trailing slash. An empty string is the root. One canonical spelling is what makes `?path=` and a folder tree agree with each other."
												},
												"name": {
													"type": "string",
													"description": "The file name, with its extension. No slashes — the folder is `path`."
												},
												"at": {
													"type": "string",
													"description": "Path and name together, which is what you put in a URL and most likely what your own code already says."
												},
												"hash": {
													"type": "string",
													"description": "sha256 of the contents, and how you answer \"have I already got this one?\" without downloading it — the whole of an efficient device sync. SET BY THE UPLOAD AND ONLY BY THE UPLOAD: `POST /v1/files` refuses a stated one, because a hash you send is a claim about bytes this service does not have. NOT the identity either: two folders may hold the same bytes, and a file whose contents change keeps its name.",
													"nullable": true
												},
												"mime": {
													"type": "string",
													"description": "What the writer said it was. Checked against the extension on write, and not otherwise validated.",
													"nullable": true
												},
												"bytes": {
													"type": "integer",
													"description": "Counts against the ACCOUNT allowance, shared with your code and your datasets. There is no separate file allowance and no per-file size limit."
												},
												"width": {
													"type": "integer",
													"description": "What the writer happened to know. Nothing validates it, and a file whose writer knew nothing about it is a perfectly good row.",
													"nullable": true
												},
												"height": {
													"type": "integer",
													"description": "As `width`.",
													"nullable": true
												},
												"partitions": {
													"type": "array",
													"description": "The partitions this file belongs to. ALWAYS an array — one asset four branches share is one file, not four copies. A write REPLACES the list rather than adding to it.",
													"items": {
														"type": "string"
													}
												},
												"created_at": {
													"type": "string",
													"description": "When it was first registered. UTC.",
													"format": "date-time"
												},
												"has_content": {
													"type": "boolean",
													"description": "Whether the bytes are here. `POST /v1/files` registers what a file IS; `PUT /v1/content/{path}` puts what it holds and is the only thing that sets `hash` — so this is never a claim. Reconciling a library needs to tell \"not uploaded yet\" from \"done\"."
												},
												"content_at": {
													"type": "string",
													"description": "Where to GET the bytes, or null when there are none. The storage layout behind it is ours — a blob is named after its own sha256, in a directory per tenant and project, and none of that is constructible from anything returned here.",
													"nullable": true
												}
											}
										}
									}
								}
							}
						}
					},
					"404": {
						"description": "NO_FILE",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			},
			"delete": {
				"tags": [
					"files"
				],
				"summary": "Remove a file",
				"operationId": "deleteFile",
				"responses": {
					"200": {
						"description": "Deleted",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"deleted": {
											"type": "object",
											"properties": {
												"at": {
													"type": "string"
												},
												"bytes": {
													"type": "integer"
												}
											}
										}
									}
								}
							}
						}
					},
					"403": {
						"description": "READ_ONLY",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"404": {
						"description": "NO_FILE",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			}
		},
		"/v1/datasets": {
			"get": {
				"tags": [
					"datasets"
				],
				"summary": "Every dataset on the key's project",
				"operationId": "listDatasets",
				"responses": {
					"200": {
						"description": "The list",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"datasets": {
											"type": "array",
											"items": {
												"type": "object",
												"description": "A named collection of records — a catalogue, a price list, a transaction log. It belongs to the project the key was made on.",
												"properties": {
													"key": {
														"type": "string",
														"description": "How you address it, and what your own code says. Lower-case letters, digits, hyphen and underscore, starting with a letter or digit. Immutable once created."
													},
													"name": {
														"type": "string",
														"description": "For people. Change it freely."
													},
													"description": {
														"type": "string",
														"description": "Optional. What it is for.",
														"nullable": true
													},
													"access": {
														"type": "string",
														"description": "What the app ON THE DEVICE may do. It does not restrict this API — a read-only dataset still has to be filled, and this is how.",
														"enum": [
															"rw",
															"ro"
														]
													},
													"partition_field": {
														"type": "string",
														"description": "The field on a record that splits the dataset, and the query parameter you filter on. Several devices may share one value — three tills in one shop share their sales. The field may hold a LIST, which puts one record in several partitions at once: one price four branches share is one record, not four copies. Null means the dataset is not partitioned.",
														"nullable": true
													},
													"retention_mode": {
														"type": "string",
														"description": "What happens when the dataset runs out of room. `never` keeps everything and REFUSES new records once it is full — nothing already written is thrown away. `days` drops records older than `retention_value` days. `space` drops the oldest once `retention_value` per cent of the limit is used, so the dataset rolls rather than stops.",
														"enum": [
															"never",
															"days",
															"space"
														]
													},
													"retention_value": {
														"type": "integer",
														"description": "Days for `days`, per cent for `space`. Null for `never`, and setting it there is ignored.",
														"nullable": true
													},
													"storage_limit_bytes": {
														"type": "integer",
														"description": "A ceiling on this dataset alone, inside the account allowance. Null means it is bounded only by the account.",
														"nullable": true
													},
													"cursor": {
														"type": "integer",
														"description": "This dataset's current sync number. A client that has just read the whole dataset takes this as the `since` for its first incremental call. Opaque — compare it, never do arithmetic on it."
													},
													"records": {
														"type": "integer",
														"description": "Counted at read time, never stored — a kept counter is a second copy of the truth and the first half-write puts the two out of agreement for ever."
													},
													"bytes": {
														"type": "integer",
														"description": "The payloads as stored, and what the storage allowance is measured against. No index or row overhead — it is a number you could work out yourself from the data you sent."
													},
													"updated_at": {
														"type": "string",
														"description": "When the DATA last changed, not the row — a rename does not move it. UTC, like every timestamp here.",
														"format": "date-time"
													},
													"created_at": {
														"type": "string",
														"description": "When the dataset was made. UTC, like every timestamp here.",
														"format": "date-time"
													}
												}
											}
										}
									}
								}
							}
						}
					},
					"401": {
						"description": "Your key",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			},
			"post": {
				"tags": [
					"datasets"
				],
				"summary": "Create a dataset",
				"operationId": "createDataset",
				"description": "Needs a `write` key. The same package ceiling the portal enforces applies here — an API that could create what the screens refuse would be a way around the package rather than another door to it.",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"required": [
									"name"
								],
								"properties": {
									"name": {
										"type": "string"
									},
									"key": {
										"type": "string",
										"description": "Derived from the name if omitted."
									},
									"description": {
										"type": "string"
									},
									"access": {
										"type": "string",
										"enum": [
											"rw",
											"ro"
										],
										"default": "rw"
									},
									"partition_field": {
										"type": "string"
									},
									"retention_mode": {
										"type": "string",
										"enum": [
											"never",
											"days",
											"space"
										]
									},
									"retention_value": {
										"type": "integer"
									},
									"storage_limit_bytes": {
										"type": "integer"
									}
								}
							}
						}
					}
				},
				"responses": {
					"201": {
						"description": "Created",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"dataset": {
											"type": "object",
											"description": "A named collection of records — a catalogue, a price list, a transaction log. It belongs to the project the key was made on.",
											"properties": {
												"key": {
													"type": "string",
													"description": "How you address it, and what your own code says. Lower-case letters, digits, hyphen and underscore, starting with a letter or digit. Immutable once created."
												},
												"name": {
													"type": "string",
													"description": "For people. Change it freely."
												},
												"description": {
													"type": "string",
													"description": "Optional. What it is for.",
													"nullable": true
												},
												"access": {
													"type": "string",
													"description": "What the app ON THE DEVICE may do. It does not restrict this API — a read-only dataset still has to be filled, and this is how.",
													"enum": [
														"rw",
														"ro"
													]
												},
												"partition_field": {
													"type": "string",
													"description": "The field on a record that splits the dataset, and the query parameter you filter on. Several devices may share one value — three tills in one shop share their sales. The field may hold a LIST, which puts one record in several partitions at once: one price four branches share is one record, not four copies. Null means the dataset is not partitioned.",
													"nullable": true
												},
												"retention_mode": {
													"type": "string",
													"description": "What happens when the dataset runs out of room. `never` keeps everything and REFUSES new records once it is full — nothing already written is thrown away. `days` drops records older than `retention_value` days. `space` drops the oldest once `retention_value` per cent of the limit is used, so the dataset rolls rather than stops.",
													"enum": [
														"never",
														"days",
														"space"
													]
												},
												"retention_value": {
													"type": "integer",
													"description": "Days for `days`, per cent for `space`. Null for `never`, and setting it there is ignored.",
													"nullable": true
												},
												"storage_limit_bytes": {
													"type": "integer",
													"description": "A ceiling on this dataset alone, inside the account allowance. Null means it is bounded only by the account.",
													"nullable": true
												},
												"cursor": {
													"type": "integer",
													"description": "This dataset's current sync number. A client that has just read the whole dataset takes this as the `since` for its first incremental call. Opaque — compare it, never do arithmetic on it."
												},
												"records": {
													"type": "integer",
													"description": "Counted at read time, never stored — a kept counter is a second copy of the truth and the first half-write puts the two out of agreement for ever."
												},
												"bytes": {
													"type": "integer",
													"description": "The payloads as stored, and what the storage allowance is measured against. No index or row overhead — it is a number you could work out yourself from the data you sent."
												},
												"updated_at": {
													"type": "string",
													"description": "When the DATA last changed, not the row — a rename does not move it. UTC, like every timestamp here.",
													"format": "date-time"
												},
												"created_at": {
													"type": "string",
													"description": "When the dataset was made. UTC, like every timestamp here.",
													"format": "date-time"
												}
											}
										}
									}
								}
							}
						}
					},
					"400": {
						"description": "BAD_REQUEST · BAD_KEY_FORMAT · RESERVED_KEY · BAD_RETENTION",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"403": {
						"description": "READ_ONLY · LIMIT_REACHED",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"409": {
						"description": "DATASET_EXISTS",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			}
		},
		"/v1/datasets/{key}": {
			"parameters": [
				{
					"name": "key",
					"in": "path",
					"required": true,
					"schema": {
						"type": "string"
					},
					"description": "The dataset's key — what you chose, not an id."
				}
			],
			"get": {
				"tags": [
					"datasets"
				],
				"summary": "One dataset",
				"operationId": "getDataset",
				"responses": {
					"200": {
						"description": "The dataset",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"dataset": {
											"type": "object",
											"description": "A named collection of records — a catalogue, a price list, a transaction log. It belongs to the project the key was made on.",
											"properties": {
												"key": {
													"type": "string",
													"description": "How you address it, and what your own code says. Lower-case letters, digits, hyphen and underscore, starting with a letter or digit. Immutable once created."
												},
												"name": {
													"type": "string",
													"description": "For people. Change it freely."
												},
												"description": {
													"type": "string",
													"description": "Optional. What it is for.",
													"nullable": true
												},
												"access": {
													"type": "string",
													"description": "What the app ON THE DEVICE may do. It does not restrict this API — a read-only dataset still has to be filled, and this is how.",
													"enum": [
														"rw",
														"ro"
													]
												},
												"partition_field": {
													"type": "string",
													"description": "The field on a record that splits the dataset, and the query parameter you filter on. Several devices may share one value — three tills in one shop share their sales. The field may hold a LIST, which puts one record in several partitions at once: one price four branches share is one record, not four copies. Null means the dataset is not partitioned.",
													"nullable": true
												},
												"retention_mode": {
													"type": "string",
													"description": "What happens when the dataset runs out of room. `never` keeps everything and REFUSES new records once it is full — nothing already written is thrown away. `days` drops records older than `retention_value` days. `space` drops the oldest once `retention_value` per cent of the limit is used, so the dataset rolls rather than stops.",
													"enum": [
														"never",
														"days",
														"space"
													]
												},
												"retention_value": {
													"type": "integer",
													"description": "Days for `days`, per cent for `space`. Null for `never`, and setting it there is ignored.",
													"nullable": true
												},
												"storage_limit_bytes": {
													"type": "integer",
													"description": "A ceiling on this dataset alone, inside the account allowance. Null means it is bounded only by the account.",
													"nullable": true
												},
												"cursor": {
													"type": "integer",
													"description": "This dataset's current sync number. A client that has just read the whole dataset takes this as the `since` for its first incremental call. Opaque — compare it, never do arithmetic on it."
												},
												"records": {
													"type": "integer",
													"description": "Counted at read time, never stored — a kept counter is a second copy of the truth and the first half-write puts the two out of agreement for ever."
												},
												"bytes": {
													"type": "integer",
													"description": "The payloads as stored, and what the storage allowance is measured against. No index or row overhead — it is a number you could work out yourself from the data you sent."
												},
												"updated_at": {
													"type": "string",
													"description": "When the DATA last changed, not the row — a rename does not move it. UTC, like every timestamp here.",
													"format": "date-time"
												},
												"created_at": {
													"type": "string",
													"description": "When the dataset was made. UTC, like every timestamp here.",
													"format": "date-time"
												}
											}
										}
									}
								}
							}
						}
					},
					"404": {
						"description": "NO_DATASET",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			},
			"patch": {
				"tags": [
					"datasets"
				],
				"summary": "Change a dataset",
				"operationId": "patchDataset",
				"description": "Send only what changes. **The key cannot be changed** — your devices address the dataset by that name — and sending a different one is an error rather than a silent ignore.",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"name": {
										"type": "string"
									},
									"description": {
										"type": "string"
									},
									"access": {
										"type": "string",
										"enum": [
											"rw",
											"ro"
										]
									},
									"partition_field": {
										"type": "string"
									},
									"retention_mode": {
										"type": "string",
										"enum": [
											"never",
											"days",
											"space"
										]
									},
									"retention_value": {
										"type": "integer"
									},
									"storage_limit_bytes": {
										"type": "integer"
									}
								}
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Saved",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"dataset": {
											"type": "object",
											"description": "A named collection of records — a catalogue, a price list, a transaction log. It belongs to the project the key was made on.",
											"properties": {
												"key": {
													"type": "string",
													"description": "How you address it, and what your own code says. Lower-case letters, digits, hyphen and underscore, starting with a letter or digit. Immutable once created."
												},
												"name": {
													"type": "string",
													"description": "For people. Change it freely."
												},
												"description": {
													"type": "string",
													"description": "Optional. What it is for.",
													"nullable": true
												},
												"access": {
													"type": "string",
													"description": "What the app ON THE DEVICE may do. It does not restrict this API — a read-only dataset still has to be filled, and this is how.",
													"enum": [
														"rw",
														"ro"
													]
												},
												"partition_field": {
													"type": "string",
													"description": "The field on a record that splits the dataset, and the query parameter you filter on. Several devices may share one value — three tills in one shop share their sales. The field may hold a LIST, which puts one record in several partitions at once: one price four branches share is one record, not four copies. Null means the dataset is not partitioned.",
													"nullable": true
												},
												"retention_mode": {
													"type": "string",
													"description": "What happens when the dataset runs out of room. `never` keeps everything and REFUSES new records once it is full — nothing already written is thrown away. `days` drops records older than `retention_value` days. `space` drops the oldest once `retention_value` per cent of the limit is used, so the dataset rolls rather than stops.",
													"enum": [
														"never",
														"days",
														"space"
													]
												},
												"retention_value": {
													"type": "integer",
													"description": "Days for `days`, per cent for `space`. Null for `never`, and setting it there is ignored.",
													"nullable": true
												},
												"storage_limit_bytes": {
													"type": "integer",
													"description": "A ceiling on this dataset alone, inside the account allowance. Null means it is bounded only by the account.",
													"nullable": true
												},
												"cursor": {
													"type": "integer",
													"description": "This dataset's current sync number. A client that has just read the whole dataset takes this as the `since` for its first incremental call. Opaque — compare it, never do arithmetic on it."
												},
												"records": {
													"type": "integer",
													"description": "Counted at read time, never stored — a kept counter is a second copy of the truth and the first half-write puts the two out of agreement for ever."
												},
												"bytes": {
													"type": "integer",
													"description": "The payloads as stored, and what the storage allowance is measured against. No index or row overhead — it is a number you could work out yourself from the data you sent."
												},
												"updated_at": {
													"type": "string",
													"description": "When the DATA last changed, not the row — a rename does not move it. UTC, like every timestamp here.",
													"format": "date-time"
												},
												"created_at": {
													"type": "string",
													"description": "When the dataset was made. UTC, like every timestamp here.",
													"format": "date-time"
												}
											}
										},
										"repartitioned": {
											"type": "integer",
											"description": "Only when `partition_field` changed: how many records were re-lifted. Every one of them also moved above your sync cursor, because its partitions changed — expect a full window from `/changes` after this."
										}
									}
								}
							}
						}
					},
					"400": {
						"description": "KEY_IMMUTABLE · NOTHING_TO_DO · BAD_RETENTION",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"403": {
						"description": "READ_ONLY",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"404": {
						"description": "NO_DATASET",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			},
			"delete": {
				"tags": [
					"datasets"
				],
				"summary": "Delete a dataset and every record in it",
				"operationId": "deleteDataset",
				"description": "Reports what went, rather than answering 204 — a bare 204 makes \"it was already empty\" indistinguishable from \"I just destroyed 48,000 records\".",
				"responses": {
					"200": {
						"description": "Deleted",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"deleted": {
											"type": "object",
											"properties": {
												"dataset": {
													"type": "string"
												},
												"records": {
													"type": "integer"
												}
											}
										}
									}
								}
							}
						}
					},
					"403": {
						"description": "READ_ONLY",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"404": {
						"description": "NO_DATASET",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			}
		},
		"/v1/datasets/{key}/changes": {
			"parameters": [
				{
					"name": "key",
					"in": "path",
					"required": true,
					"schema": {
						"type": "string"
					},
					"description": "The dataset's key — what you chose, not an id."
				}
			],
			"get": {
				"tags": [
					"records"
				],
				"summary": "What changed since a cursor",
				"operationId": "listChanges",
				"description": "Incremental sync. `since=0` is a full one — everything comes back as `changed` — and every call returns a `cursor` to pass as the next `since`.\n\nThe cursor is a **sequence number, not a timestamp**, and deliberately so: timestamps tie at the same microsecond, move when the clock is stepped, and are stamped when a row is written rather than when it becomes visible. Treat it as opaque — compare it, never do arithmetic on it.\n\nChanges and deletions come from **one window**, so a stored cursor has provably seen both up to it. `purged: true` means the dataset was emptied: drop what you hold and take `changed` as the new whole.",
				"parameters": [
					{
						"name": "since",
						"in": "query",
						"schema": {
							"type": "integer",
							"default": 0
						},
						"description": "The cursor from your last call. 0 for a full sync."
					},
					{
						"name": "limit",
						"in": "query",
						"schema": {
							"type": "integer",
							"minimum": 1,
							"maximum": 1000,
							"default": 100
						}
					},
					{
						"name": "partition",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						},
						"description": "FILTER BY YOUR OWN FIELD NAME, not by the word \"partition\". The parameter is whatever the dataset's `partition_field` says — `?shop_id=oldbury` for a dataset partitioned by `shop_id`. This entry exists so generated clients have somewhere to put it; the name is per dataset, so it cannot be spelt here. Any OTHER query parameter is refused with `400 BAD_PARAM` rather than ignored — a filter whose whole job is isolation must not fail open on a typo."
					}
				],
				"responses": {
					"200": {
						"description": "The window",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"dataset": {
											"type": "string"
										},
										"since": {
											"type": "integer"
										},
										"cursor": {
											"type": "integer",
											"description": "Pass as `since` next time."
										},
										"has_more": {
											"type": "boolean",
											"description": "More windows are waiting — call again with the new cursor."
										},
										"purged": {
											"type": "boolean",
											"description": "The dataset was emptied. Drop everything you hold."
										},
										"changed": {
											"type": "array",
											"items": {
												"type": "object",
												"description": "One row of your data. What is inside `data` is entirely yours; the API only reads the two fields it needs to address and partition it.",
												"properties": {
													"key": {
														"type": "string",
														"description": "The record's own id, as you know it — taken from `id`, `key` or `record_key` in the object you sent. With one, a write is an upsert; without one it is an append. Null means it was appended.",
														"nullable": true
													},
													"partitions": {
														"type": "array",
														"description": "The partitions this record belongs to, lifted out of the payload using the dataset's `partition_field`. ALWAYS an array — empty when the dataset is not partitioned, one entry for a single value, several when the field held a list. One price four branches share is one record saying `\"branch\": [\"oldbury\", \"kings-heath\", …]`, not four copies that have to be kept in step.",
														"items": {
															"type": "string"
														}
													},
													"data": {
														"type": "object",
														"description": "Your record, parsed. Returned as an object, never as a JSON string — left as text it would arrive double-encoded, which no importer reads."
													},
													"created_at": {
														"type": "string",
														"description": "When the record first arrived. UTC.",
														"format": "date-time"
													},
													"updated_at": {
														"type": "string",
														"description": "When it was last written — an upsert moves it. UTC.",
														"format": "date-time"
													}
												}
											}
										},
										"deleted": {
											"type": "array",
											"items": {
												"type": "string"
											},
											"description": "Record keys that are gone. Keyless records are never listed — they have no identity to match against."
										}
									}
								}
							}
						}
					},
					"400": {
						"description": "BAD_PARAM",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"404": {
						"description": "NO_DATASET",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			}
		},
		"/v1/datasets/{key}/records": {
			"parameters": [
				{
					"name": "key",
					"in": "path",
					"required": true,
					"schema": {
						"type": "string"
					},
					"description": "The dataset's key — what you chose, not an id."
				}
			],
			"get": {
				"tags": [
					"records"
				],
				"summary": "Read records",
				"operationId": "listRecords",
				"description": "If the dataset is partitioned, filter with a query parameter named after **your own** `partition_field` — `?shop_id=oldbury`, not `?partition=`.",
				"parameters": [
					{
						"name": "limit",
						"in": "query",
						"schema": {
							"type": "integer",
							"minimum": 1,
							"maximum": 1000,
							"default": 100
						}
					},
					{
						"name": "offset",
						"in": "query",
						"schema": {
							"type": "integer",
							"default": 0
						}
					},
					{
						"name": "partition",
						"in": "query",
						"required": false,
						"schema": {
							"type": "string"
						},
						"description": "FILTER BY YOUR OWN FIELD NAME, not by the word \"partition\". The parameter is whatever the dataset's `partition_field` says — `?shop_id=oldbury` for a dataset partitioned by `shop_id`. This entry exists so generated clients have somewhere to put it; the name is per dataset, so it cannot be spelt here. Any OTHER query parameter is refused with `400 BAD_PARAM` rather than ignored — a filter whose whole job is isolation must not fail open on a typo."
					}
				],
				"responses": {
					"200": {
						"description": "A page of records",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"dataset": {
											"type": "string"
										},
										"total": {
											"type": "integer",
											"description": "Matching records, counted once each — a record in four partitions is one."
										},
										"limit": {
											"type": "integer"
										},
										"offset": {
											"type": "integer"
										},
										"has_more": {
											"type": "boolean",
											"description": "There are more beyond this page. A caller who reads 1000 of 48,000 and is given no hint will ship believing it read the lot."
										},
										"records": {
											"type": "array",
											"items": {
												"type": "object",
												"description": "One row of your data. What is inside `data` is entirely yours; the API only reads the two fields it needs to address and partition it.",
												"properties": {
													"key": {
														"type": "string",
														"description": "The record's own id, as you know it — taken from `id`, `key` or `record_key` in the object you sent. With one, a write is an upsert; without one it is an append. Null means it was appended.",
														"nullable": true
													},
													"partitions": {
														"type": "array",
														"description": "The partitions this record belongs to, lifted out of the payload using the dataset's `partition_field`. ALWAYS an array — empty when the dataset is not partitioned, one entry for a single value, several when the field held a list. One price four branches share is one record saying `\"branch\": [\"oldbury\", \"kings-heath\", …]`, not four copies that have to be kept in step.",
														"items": {
															"type": "string"
														}
													},
													"data": {
														"type": "object",
														"description": "Your record, parsed. Returned as an object, never as a JSON string — left as text it would arrive double-encoded, which no importer reads."
													},
													"created_at": {
														"type": "string",
														"description": "When the record first arrived. UTC.",
														"format": "date-time"
													},
													"updated_at": {
														"type": "string",
														"description": "When it was last written — an upsert moves it. UTC.",
														"format": "date-time"
													}
												}
											}
										}
									}
								}
							}
						}
					},
					"400": {
						"description": "BAD_PARAM",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"404": {
						"description": "NO_DATASET",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			},
			"put": {
				"tags": [
					"records"
				],
				"summary": "Replace every record in the dataset",
				"operationId": "replaceRecords",
				"description": "For a catalogue re-published from source. Storage is checked BEFORE the write; what this replaces counts as freed, so re-publishing a catalogue of the same size always fits.\n\n⚠️ **This takes no query parameters, and a partition filter on it is refused** with `400 BAD_PARAM` rather than ignored. `PUT …/records?shop_id=5` reads as \"republish that shop\" and would mean \"delete every other shop\" — silently widening a scoped write is the worst of the three available behaviours. A scoped replace is a real feature and a separate decision.\n\nTo a syncing client this is a **purge**: `/changes` reports `purged: true` rather than one deletion per record.",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"oneOf": [
									{
										"type": "array",
										"items": {
											"type": "object"
										}
									},
									{
										"type": "object",
										"properties": {
											"records": {
												"type": "array",
												"items": {
													"type": "object"
												}
											}
										}
									}
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Replaced",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"dataset": {
											"type": "string"
										},
										"replaced": {
											"type": "integer"
										},
										"cursor": {
											"type": "integer",
											"description": "The sync number this write ended on. Pass it as `since` to `/changes`."
										},
										"pruned": {
											"type": "integer",
											"description": "Records retention dropped to make room, as part of THIS call. An upload that silently shrank your dataset by 4,000 rows is something the call that did it has to say out loud."
										}
									}
								}
							}
						}
					},
					"400": {
						"description": "BAD_REQUEST · BAD_RECORD · BAD_JSON · TOO_MANY · DUPLICATE_KEY · BAD_PARTITION · BAD_PARAM",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"403": {
						"description": "READ_ONLY · STORAGE_FULL · DATASET_FULL",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"413": {
						"description": "TOO_LARGE",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			},
			"post": {
				"tags": [
					"records"
				],
				"summary": "Upsert the records sent, leaving the rest alone",
				"operationId": "upsertRecords",
				"description": "A record's key is its own id, taken from `id`, `key` or `record_key` in the object — so sending it again updates rather than duplicating. A record with no key is appended, which is right for a transaction log and wrong for a catalogue; the counts come back split.",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"oneOf": [
									{
										"type": "array",
										"items": {
											"type": "object"
										}
									},
									{
										"type": "object",
										"properties": {
											"records": {
												"type": "array",
												"items": {
													"type": "object"
												}
											}
										}
									}
								]
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Written",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"dataset": {
											"type": "string"
										},
										"written": {
											"type": "integer",
											"description": "Records stored. The same as the number sent — the same key twice in one batch is refused, not collapsed, so this can never describe the request rather than the result."
										},
										"appended": {
											"type": "integer",
											"description": "How many of `written` had no key and were therefore appended rather than upserted. A SUBSET of `written`, not an addition to it."
										},
										"cursor": {
											"type": "integer",
											"description": "The sync number this write ended on. Pass it as `since` to `/changes`."
										},
										"pruned": {
											"type": "integer",
											"description": "Records retention dropped to make room, as part of THIS call. An upload that silently shrank your dataset by 4,000 rows is something the call that did it has to say out loud."
										}
									}
								}
							}
						}
					},
					"400": {
						"description": "BAD_REQUEST · BAD_RECORD · BAD_JSON · TOO_MANY · DUPLICATE_KEY · BAD_PARTITION · BAD_PARAM",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"403": {
						"description": "READ_ONLY · STORAGE_FULL · DATASET_FULL",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"413": {
						"description": "TOO_LARGE",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			},
			"delete": {
				"tags": [
					"records"
				],
				"summary": "Empty the dataset, keeping the dataset",
				"operationId": "clearRecords",
				"description": "`?confirm=1` is required. It is the one call where a typo in a path — a missing record key — would otherwise turn \"delete one\" into \"delete everything\".",
				"parameters": [
					{
						"name": "confirm",
						"in": "query",
						"required": true,
						"schema": {
							"type": "string",
							"enum": [
								"1"
							]
						}
					}
				],
				"responses": {
					"200": {
						"description": "Emptied",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"dataset": {
											"type": "string"
										},
										"deleted": {
											"type": "integer"
										},
										"cursor": {
											"type": "integer",
											"description": "The sync number the purge was written at."
										}
									}
								}
							}
						}
					},
					"400": {
						"description": "CONFIRM_REQUIRED · BAD_PARAM",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"403": {
						"description": "READ_ONLY",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			}
		},
		"/v1/datasets/{key}/records/{recordKey}": {
			"parameters": [
				{
					"name": "key",
					"in": "path",
					"required": true,
					"schema": {
						"type": "string"
					},
					"description": "The dataset's key — what you chose, not an id."
				},
				{
					"name": "recordKey",
					"in": "path",
					"required": true,
					"schema": {
						"type": "string"
					}
				}
			],
			"get": {
				"tags": [
					"records"
				],
				"summary": "One record",
				"operationId": "getRecord",
				"responses": {
					"200": {
						"description": "The record",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"record": {
											"type": "object",
											"description": "One row of your data. What is inside `data` is entirely yours; the API only reads the two fields it needs to address and partition it.",
											"properties": {
												"key": {
													"type": "string",
													"description": "The record's own id, as you know it — taken from `id`, `key` or `record_key` in the object you sent. With one, a write is an upsert; without one it is an append. Null means it was appended.",
													"nullable": true
												},
												"partitions": {
													"type": "array",
													"description": "The partitions this record belongs to, lifted out of the payload using the dataset's `partition_field`. ALWAYS an array — empty when the dataset is not partitioned, one entry for a single value, several when the field held a list. One price four branches share is one record saying `\"branch\": [\"oldbury\", \"kings-heath\", …]`, not four copies that have to be kept in step.",
													"items": {
														"type": "string"
													}
												},
												"data": {
													"type": "object",
													"description": "Your record, parsed. Returned as an object, never as a JSON string — left as text it would arrive double-encoded, which no importer reads."
												},
												"created_at": {
													"type": "string",
													"description": "When the record first arrived. UTC.",
													"format": "date-time"
												},
												"updated_at": {
													"type": "string",
													"description": "When it was last written — an upsert moves it. UTC.",
													"format": "date-time"
												}
											}
										}
									}
								}
							}
						}
					},
					"404": {
						"description": "NO_DATASET · NO_RECORD",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			},
			"delete": {
				"tags": [
					"records"
				],
				"summary": "Remove one record",
				"operationId": "deleteRecord",
				"responses": {
					"200": {
						"description": "Deleted",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"deleted": {
											"type": "object",
											"properties": {
												"dataset": {
													"type": "string"
												},
												"record": {
													"type": "string"
												}
											}
										},
										"cursor": {
											"type": "integer",
											"description": "The sync number the deletion was written at."
										}
									}
								}
							}
						}
					},
					"403": {
						"description": "READ_ONLY",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"404": {
						"description": "NO_DATASET · NO_RECORD",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			}
		},
		"/v1/webhooks": {
			"get": {
				"tags": [
					"webhooks"
				],
				"summary": "Every webhook on the key's project",
				"operationId": "listWebhooks",
				"description": "Readable whatever the package says — a list of what exists is not a use of the feature. The key is never in the list; `hasKey` is all a reader learns about it.",
				"responses": {
					"200": {
						"description": "The list",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"webhooks": {
											"type": "array",
											"items": {
												"type": "object",
												"description": "An HTTP endpoint of yours that the app on a device may send a message to — `Arda.webhook.call(slug, data)`. The plane makes the call, signs it and records every delivery; the device never holds your key and never talks to your server directly.",
												"properties": {
													"slug": {
														"type": "string",
														"description": "How the app addresses it, and what your code says. Lower-case letters, digits, hyphen and underscore, starting with a letter or digit. FROZEN after create — `verify` is reserved."
													},
													"name": {
														"type": "string",
														"description": "For people. Change it freely."
													},
													"url": {
														"type": "string",
														"description": "Where the plane sends. http or https on a public host: localhost and loopback, link-local and private addresses are refused when saved, and refused again by the plane at call time after the name resolves. NULL FOR A WEBHOOK THAT ONLY RECORDS ITS CALLS: nothing is sent, the app is answered `ok` with status 202, and every call is a delivery whose envelope `…/deliveries` hands back — a mailbox your own systems poll.",
														"nullable": true
													},
													"method": {
														"type": "string",
														"description": "How it is sent. `post` and `put` carry the envelope as a JSON body; `get` carries the same fields as query parameters.",
														"enum": [
															"post",
															"get",
															"put"
														]
													},
													"hasKey": {
														"type": "boolean",
														"description": "Whether a key is set. THE KEY ITSELF IS NEVER RETURNED: set it with `key` on create or PATCH, clear it with `\"key\": null`. When set, every delivery carries it as `Authorization: Bearer` and is signed with it (`X-Arda-Signature`)."
													},
													"verify": {
														"type": "boolean",
														"description": "Mint a one-time `X-Arda-Verify` token per delivery, which your server hands back to `POST /v1/webhooks/verify` to prove the call came from here. Off by default. No effect on a webhook with no url — nothing is sent, so there is nobody to hand a token to."
													},
													"enabled": {
														"type": "boolean",
														"description": "Off, and the app's call is refused on the device and nothing is sent. The delivery log is kept."
													},
													"note": {
														"type": "string",
														"description": "Optional. Whatever helps the next person.",
														"nullable": true
													},
													"createdAt": {
														"type": "string",
														"description": "When it was made. UTC.",
														"format": "date-time"
													},
													"updatedAt": {
														"type": "string",
														"description": "When its definition last changed. A delivery does not move it. UTC.",
														"format": "date-time"
													}
												}
											}
										}
									}
								}
							}
						}
					},
					"403": {
						"description": "OUT_OF_SCOPE",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			},
			"post": {
				"tags": [
					"webhooks"
				],
				"summary": "Create a webhook",
				"operationId": "createWebhook",
				"description": "Needs `webhooks:a`, and a package that includes webhooks (`403 NO_WEBHOOKS` otherwise). `webhook_endpoints` is a ceiling across EVERY project of the account — the same one the portal enforces — and 0 means no ceiling. The url must be http or https on a public host: localhost and private, loopback or link-local addresses are `400 BAD_URL`. **Leave the url out** and the webhook only RECORDS its calls: nothing is sent, the app is answered `ok` with status 202, and `…/deliveries` hands every call back with its envelope. **The slug is frozen once created**, because your app addresses the webhook by it.",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"required": [
									"name",
									"slug"
								],
								"properties": {
									"name": {
										"type": "string",
										"description": "For people. At most 120 characters."
									},
									"slug": {
										"type": "string",
										"description": "Lower-case letters, digits, hyphen and underscore, starting with a letter or digit, up to 80. `verify` is reserved."
									},
									"url": {
										"type": "string",
										"nullable": true,
										"description": "http or https, at most 1024 characters, a public host. Absent, null or \"\" makes a webhook that only records its calls (`url: null` in the answer)."
									},
									"method": {
										"type": "string",
										"enum": [
											"post",
											"get",
											"put"
										],
										"default": "post",
										"description": "`post` and `put` send the envelope as a JSON body; `get` sends it as query parameters."
									},
									"key": {
										"type": "string",
										"description": "WRITE-ONLY. At most 255 characters. Sent as `Authorization: Bearer` and used to sign every delivery (`X-Arda-Signature`). Never returned — read `hasKey`."
									},
									"verify": {
										"type": "boolean",
										"default": false,
										"description": "Mint a one-time `X-Arda-Verify` token per delivery, for `POST /v1/webhooks/verify`. Stored but without effect while there is no url."
									},
									"enabled": {
										"type": "boolean",
										"default": true
									},
									"note": {
										"type": "string",
										"description": "At most 255 characters."
									}
								}
							}
						}
					}
				},
				"responses": {
					"201": {
						"description": "Created",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"webhook": {
											"type": "object",
											"description": "An HTTP endpoint of yours that the app on a device may send a message to — `Arda.webhook.call(slug, data)`. The plane makes the call, signs it and records every delivery; the device never holds your key and never talks to your server directly.",
											"properties": {
												"slug": {
													"type": "string",
													"description": "How the app addresses it, and what your code says. Lower-case letters, digits, hyphen and underscore, starting with a letter or digit. FROZEN after create — `verify` is reserved."
												},
												"name": {
													"type": "string",
													"description": "For people. Change it freely."
												},
												"url": {
													"type": "string",
													"description": "Where the plane sends. http or https on a public host: localhost and loopback, link-local and private addresses are refused when saved, and refused again by the plane at call time after the name resolves. NULL FOR A WEBHOOK THAT ONLY RECORDS ITS CALLS: nothing is sent, the app is answered `ok` with status 202, and every call is a delivery whose envelope `…/deliveries` hands back — a mailbox your own systems poll.",
													"nullable": true
												},
												"method": {
													"type": "string",
													"description": "How it is sent. `post` and `put` carry the envelope as a JSON body; `get` carries the same fields as query parameters.",
													"enum": [
														"post",
														"get",
														"put"
													]
												},
												"hasKey": {
													"type": "boolean",
													"description": "Whether a key is set. THE KEY ITSELF IS NEVER RETURNED: set it with `key` on create or PATCH, clear it with `\"key\": null`. When set, every delivery carries it as `Authorization: Bearer` and is signed with it (`X-Arda-Signature`)."
												},
												"verify": {
													"type": "boolean",
													"description": "Mint a one-time `X-Arda-Verify` token per delivery, which your server hands back to `POST /v1/webhooks/verify` to prove the call came from here. Off by default. No effect on a webhook with no url — nothing is sent, so there is nobody to hand a token to."
												},
												"enabled": {
													"type": "boolean",
													"description": "Off, and the app's call is refused on the device and nothing is sent. The delivery log is kept."
												},
												"note": {
													"type": "string",
													"description": "Optional. Whatever helps the next person.",
													"nullable": true
												},
												"createdAt": {
													"type": "string",
													"description": "When it was made. UTC.",
													"format": "date-time"
												},
												"updatedAt": {
													"type": "string",
													"description": "When its definition last changed. A delivery does not move it. UTC.",
													"format": "date-time"
												}
											}
										}
									}
								}
							}
						}
					},
					"400": {
						"description": "BAD_REQUEST · BAD_KEY_FORMAT · BAD_URL",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"403": {
						"description": "OUT_OF_SCOPE · NO_WEBHOOKS · LIMIT_REACHED",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"409": {
						"description": "WEBHOOK_EXISTS",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			}
		},
		"/v1/webhooks/verify": {
			"post": {
				"tags": [
					"webhooks"
				],
				"summary": "Prove a delivery came from here",
				"operationId": "verifyDelivery",
				"description": "For YOUR SERVER, on a webhook with `verify` on: hand back the `X-Arda-Verify` token a delivery carried, with its `X-Arda-Delivery` id, and learn whether the plane really made that call. A delivery is verifiable for 24 hours.\n\n`valid: false` is an ANSWER, not an error — it means \"that was not us\". `verifiedAt` is the value from BEFORE this call: null means nobody has presented this token before; a timestamp means it was verified already, which is how a receiver tells a replay from a first arrival. Needs `webhooks:r`.",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"required": [
									"delivery",
									"token"
								],
								"properties": {
									"delivery": {
										"type": "string",
										"description": "The `X-Arda-Delivery` header — `dlv_` and sixteen hex characters."
									},
									"token": {
										"type": "string",
										"description": "The `X-Arda-Verify` header, exactly as it arrived."
									}
								}
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The verdict",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"valid": {
											"type": "boolean",
											"description": "Whether the token matches that delivery. False is an answer: the plane did not make that call."
										},
										"webhook": {
											"type": "string",
											"description": "The slug. Only when valid."
										},
										"device": {
											"type": "object",
											"nullable": true,
											"description": "The device whose app sent it, `{uid, name}`, or null for a Test. Only when valid."
										},
										"at": {
											"type": "string",
											"format": "date-time",
											"description": "When the plane made the call. Only when valid."
										},
										"status": {
											"type": "integer",
											"description": "The HTTP status your server answered, as the plane recorded it. Only when valid."
										},
										"verifiedAt": {
											"type": "string",
											"format": "date-time",
											"nullable": true,
											"description": "When this token was FIRST verified, from before this call — null the first time, a timestamp on a retry or a replay. Only when valid."
										}
									}
								}
							}
						}
					},
					"400": {
						"description": "BAD_REQUEST · NOT_VERIFIABLE",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"403": {
						"description": "OUT_OF_SCOPE",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"404": {
						"description": "NO_DELIVERY",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			}
		},
		"/v1/webhooks/{slug}": {
			"parameters": [
				{
					"name": "slug",
					"in": "path",
					"required": true,
					"schema": {
						"type": "string"
					},
					"description": "The webhook's slug — what your app says in `Arda.webhook.call(slug, …)`, not an id."
				}
			],
			"get": {
				"tags": [
					"webhooks"
				],
				"summary": "One webhook",
				"operationId": "getWebhook",
				"responses": {
					"200": {
						"description": "The webhook",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"webhook": {
											"type": "object",
											"description": "An HTTP endpoint of yours that the app on a device may send a message to — `Arda.webhook.call(slug, data)`. The plane makes the call, signs it and records every delivery; the device never holds your key and never talks to your server directly.",
											"properties": {
												"slug": {
													"type": "string",
													"description": "How the app addresses it, and what your code says. Lower-case letters, digits, hyphen and underscore, starting with a letter or digit. FROZEN after create — `verify` is reserved."
												},
												"name": {
													"type": "string",
													"description": "For people. Change it freely."
												},
												"url": {
													"type": "string",
													"description": "Where the plane sends. http or https on a public host: localhost and loopback, link-local and private addresses are refused when saved, and refused again by the plane at call time after the name resolves. NULL FOR A WEBHOOK THAT ONLY RECORDS ITS CALLS: nothing is sent, the app is answered `ok` with status 202, and every call is a delivery whose envelope `…/deliveries` hands back — a mailbox your own systems poll.",
													"nullable": true
												},
												"method": {
													"type": "string",
													"description": "How it is sent. `post` and `put` carry the envelope as a JSON body; `get` carries the same fields as query parameters.",
													"enum": [
														"post",
														"get",
														"put"
													]
												},
												"hasKey": {
													"type": "boolean",
													"description": "Whether a key is set. THE KEY ITSELF IS NEVER RETURNED: set it with `key` on create or PATCH, clear it with `\"key\": null`. When set, every delivery carries it as `Authorization: Bearer` and is signed with it (`X-Arda-Signature`)."
												},
												"verify": {
													"type": "boolean",
													"description": "Mint a one-time `X-Arda-Verify` token per delivery, which your server hands back to `POST /v1/webhooks/verify` to prove the call came from here. Off by default. No effect on a webhook with no url — nothing is sent, so there is nobody to hand a token to."
												},
												"enabled": {
													"type": "boolean",
													"description": "Off, and the app's call is refused on the device and nothing is sent. The delivery log is kept."
												},
												"note": {
													"type": "string",
													"description": "Optional. Whatever helps the next person.",
													"nullable": true
												},
												"createdAt": {
													"type": "string",
													"description": "When it was made. UTC.",
													"format": "date-time"
												},
												"updatedAt": {
													"type": "string",
													"description": "When its definition last changed. A delivery does not move it. UTC.",
													"format": "date-time"
												}
											}
										}
									}
								}
							}
						}
					},
					"403": {
						"description": "OUT_OF_SCOPE",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"404": {
						"description": "NO_WEBHOOK",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			},
			"patch": {
				"tags": [
					"webhooks"
				],
				"summary": "Change a webhook",
				"operationId": "patchWebhook",
				"description": "Send only what changes. **The slug cannot be changed** — your app addresses the webhook by that name — and sending a different one is `400 SLUG_IMMUTABLE` rather than a silent ignore. `\"key\": \"…\"` sets the key and `\"key\": null` clears it; it is never read back. Needs `webhooks:w` and a package that includes webhooks.",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "object",
								"properties": {
									"name": {
										"type": "string"
									},
									"url": {
										"type": "string",
										"nullable": true,
										"description": "A url sets it; null or \"\" clears it, and the webhook then only records its calls."
									},
									"method": {
										"type": "string",
										"enum": [
											"post",
											"get",
											"put"
										]
									},
									"key": {
										"type": "string",
										"nullable": true,
										"description": "A string sets it, null clears it. Never returned."
									},
									"verify": {
										"type": "boolean"
									},
									"enabled": {
										"type": "boolean"
									},
									"note": {
										"type": "string",
										"nullable": true
									}
								}
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Saved",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"webhook": {
											"type": "object",
											"description": "An HTTP endpoint of yours that the app on a device may send a message to — `Arda.webhook.call(slug, data)`. The plane makes the call, signs it and records every delivery; the device never holds your key and never talks to your server directly.",
											"properties": {
												"slug": {
													"type": "string",
													"description": "How the app addresses it, and what your code says. Lower-case letters, digits, hyphen and underscore, starting with a letter or digit. FROZEN after create — `verify` is reserved."
												},
												"name": {
													"type": "string",
													"description": "For people. Change it freely."
												},
												"url": {
													"type": "string",
													"description": "Where the plane sends. http or https on a public host: localhost and loopback, link-local and private addresses are refused when saved, and refused again by the plane at call time after the name resolves. NULL FOR A WEBHOOK THAT ONLY RECORDS ITS CALLS: nothing is sent, the app is answered `ok` with status 202, and every call is a delivery whose envelope `…/deliveries` hands back — a mailbox your own systems poll.",
													"nullable": true
												},
												"method": {
													"type": "string",
													"description": "How it is sent. `post` and `put` carry the envelope as a JSON body; `get` carries the same fields as query parameters.",
													"enum": [
														"post",
														"get",
														"put"
													]
												},
												"hasKey": {
													"type": "boolean",
													"description": "Whether a key is set. THE KEY ITSELF IS NEVER RETURNED: set it with `key` on create or PATCH, clear it with `\"key\": null`. When set, every delivery carries it as `Authorization: Bearer` and is signed with it (`X-Arda-Signature`)."
												},
												"verify": {
													"type": "boolean",
													"description": "Mint a one-time `X-Arda-Verify` token per delivery, which your server hands back to `POST /v1/webhooks/verify` to prove the call came from here. Off by default. No effect on a webhook with no url — nothing is sent, so there is nobody to hand a token to."
												},
												"enabled": {
													"type": "boolean",
													"description": "Off, and the app's call is refused on the device and nothing is sent. The delivery log is kept."
												},
												"note": {
													"type": "string",
													"description": "Optional. Whatever helps the next person.",
													"nullable": true
												},
												"createdAt": {
													"type": "string",
													"description": "When it was made. UTC.",
													"format": "date-time"
												},
												"updatedAt": {
													"type": "string",
													"description": "When its definition last changed. A delivery does not move it. UTC.",
													"format": "date-time"
												}
											}
										}
									}
								}
							}
						}
					},
					"400": {
						"description": "SLUG_IMMUTABLE · NOTHING_TO_DO · BAD_REQUEST · BAD_URL",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"403": {
						"description": "OUT_OF_SCOPE · NO_WEBHOOKS",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"404": {
						"description": "NO_WEBHOOK",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			},
			"delete": {
				"tags": [
					"webhooks"
				],
				"summary": "Delete a webhook and its delivery log",
				"operationId": "deleteWebhook",
				"description": "Reports what went with it — the deliveries are deleted too.",
				"responses": {
					"200": {
						"description": "Deleted",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"deleted": {
											"type": "object",
											"properties": {
												"webhook": {
													"type": "string"
												},
												"deliveries": {
													"type": "integer"
												}
											}
										}
									}
								}
							}
						}
					},
					"403": {
						"description": "OUT_OF_SCOPE",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"404": {
						"description": "NO_WEBHOOK",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			}
		},
		"/v1/webhooks/{slug}/deliveries": {
			"parameters": [
				{
					"name": "slug",
					"in": "path",
					"required": true,
					"schema": {
						"type": "string"
					},
					"description": "The webhook's slug — what your app says in `Arda.webhook.call(slug, …)`, not an id."
				}
			],
			"get": {
				"tags": [
					"webhooks"
				],
				"summary": "The delivery log — newest first, or a feed after one delivery",
				"operationId": "listDeliveries",
				"description": "What the plane sent and what your server answered, one row per call. Written by the plane; this API only reads it.\n\n**Without `after`** it is the log, newest first. **With `after=dlv_…`** it is a FEED: the deliveries recorded after that one, OLDEST first — keep the last id you handled, ask again, and you never miss one or see one twice. A delivery joins the feed once it is two seconds old, so two calls finishing together cannot leave a gap behind your cursor. An `after` the log no longer holds (it keeps 500 rows or seven days) is `404 NO_DELIVERY`: you fell behind and lost some.\n\n`include=payload` puts each delivery's envelope on it and caps `limit` at 50; otherwise `limit` is 1–200, default 50. Any other query parameter, or another `include` value, is `400 BAD_PARAM` rather than ignored.",
				"parameters": [
					{
						"name": "limit",
						"in": "query",
						"schema": {
							"type": "integer",
							"minimum": 1,
							"maximum": 200,
							"default": 50
						},
						"description": "At most 200 — at most 50 with `include=payload`."
					},
					{
						"name": "include",
						"in": "query",
						"schema": {
							"type": "string",
							"enum": [
								"payload"
							]
						},
						"description": "`payload` puts the envelope on every delivery."
					},
					{
						"name": "after",
						"in": "query",
						"schema": {
							"type": "string"
						},
						"description": "A delivery id of this webhook. Answers the deliveries recorded after it, oldest first."
					}
				],
				"responses": {
					"200": {
						"description": "The log",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"webhook": {
											"type": "string"
										},
										"limit": {
											"type": "integer"
										},
										"hasMore": {
											"type": "boolean",
											"description": "There are more beyond this page — older ones without `after`, newer ones ready now with it. Ask again at once, from the last id."
										},
										"deliveries": {
											"type": "array",
											"items": {
												"type": "object",
												"description": "One call the plane made to your server — the same id your server saw as `X-Arda-Delivery` — or, on a webhook with no url, one call it recorded instead of sending. The plane keeps the most recent ones per webhook (500 rows or seven days), never counted against your storage.",
												"properties": {
													"id": {
														"type": "string",
														"description": "The delivery id: `dlv_` and sixteen hex characters. What `X-Arda-Delivery` and the envelope's `id` carried, and what `/verify` takes."
													},
													"at": {
														"type": "string",
														"description": "When the plane made the call. UTC.",
														"format": "date-time"
													},
													"device": {
														"type": "object",
														"description": "The device whose app sent it, or null for a Test run from the portal or from `POST /v1/webhooks/{slug}/test`.",
														"nullable": true
													},
													"status": {
														"type": "integer",
														"description": "The HTTP status your server answered. 0 means it was never reached — `error` says why. On a webhook with no url it is always 202: recorded, nothing sent."
													},
													"ok": {
														"type": "boolean",
														"description": "Whether `status` was 2xx."
													},
													"ms": {
														"type": "integer",
														"description": "How long the call took, in milliseconds. A timeout counts the whole wait."
													},
													"error": {
														"type": "string",
														"description": "Why `status` is 0: a refused address, a timeout, TLS, a connection fault. Null when your server answered.",
														"nullable": true
													},
													"verified": {
														"type": "boolean",
														"description": "Whether your server has presented this delivery's token to `/verify` and been answered `valid: true`. Always false for a webhook with `verify` off."
													},
													"verifiedAt": {
														"type": "string",
														"description": "When that first happened, or null.",
														"format": "date-time",
														"nullable": true
													},
													"payload": {
														"type": "object",
														"description": "The envelope as the plane built it — what your server was sent, or what a webhook with no url recorded: `id`, `webhook`, `project`, `device`, `at`, `verify` and `data`, the app's own JSON untouched. ONLY on `GET …/deliveries/{id}`, and on the list with `include=payload`. Null for a delivery recorded before 2026-10-06, which kept none.",
														"nullable": true
													},
													"response": {
														"type": "string",
														"description": "The first 1024 characters of what your server answered. ONLY on `GET …/deliveries/{id}`. Null when it answered nothing, was never reached, or the webhook has no url.",
														"nullable": true
													}
												}
											}
										}
									}
								}
							}
						}
					},
					"400": {
						"description": "BAD_PARAM",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"403": {
						"description": "OUT_OF_SCOPE",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"404": {
						"description": "NO_WEBHOOK · NO_DELIVERY",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			}
		},
		"/v1/webhooks/{slug}/deliveries/{id}": {
			"parameters": [
				{
					"name": "slug",
					"in": "path",
					"required": true,
					"schema": {
						"type": "string"
					},
					"description": "The webhook's slug — what your app says in `Arda.webhook.call(slug, …)`, not an id."
				},
				{
					"name": "id",
					"in": "path",
					"required": true,
					"schema": {
						"type": "string"
					},
					"description": "The delivery id — `dlv_` and sixteen hex characters: the `X-Arda-Delivery` your server saw, and the `id` in the log."
				}
			],
			"get": {
				"tags": [
					"webhooks"
				],
				"summary": "One delivery, with what was sent and what came back",
				"operationId": "getDelivery",
				"description": "The log row plus `payload` — the envelope as the plane built it: what your server was sent, or what a webhook with no url recorded — and `response`, the first 1024 characters of your answer. The id must be one of THIS webhook's deliveries on the key's project. Needs `webhooks:r`.",
				"responses": {
					"200": {
						"description": "The delivery",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"delivery": {
											"type": "object",
											"description": "One call the plane made to your server — the same id your server saw as `X-Arda-Delivery` — or, on a webhook with no url, one call it recorded instead of sending. The plane keeps the most recent ones per webhook (500 rows or seven days), never counted against your storage.",
											"properties": {
												"id": {
													"type": "string",
													"description": "The delivery id: `dlv_` and sixteen hex characters. What `X-Arda-Delivery` and the envelope's `id` carried, and what `/verify` takes."
												},
												"at": {
													"type": "string",
													"description": "When the plane made the call. UTC.",
													"format": "date-time"
												},
												"device": {
													"type": "object",
													"description": "The device whose app sent it, or null for a Test run from the portal or from `POST /v1/webhooks/{slug}/test`.",
													"nullable": true
												},
												"status": {
													"type": "integer",
													"description": "The HTTP status your server answered. 0 means it was never reached — `error` says why. On a webhook with no url it is always 202: recorded, nothing sent."
												},
												"ok": {
													"type": "boolean",
													"description": "Whether `status` was 2xx."
												},
												"ms": {
													"type": "integer",
													"description": "How long the call took, in milliseconds. A timeout counts the whole wait."
												},
												"error": {
													"type": "string",
													"description": "Why `status` is 0: a refused address, a timeout, TLS, a connection fault. Null when your server answered.",
													"nullable": true
												},
												"verified": {
													"type": "boolean",
													"description": "Whether your server has presented this delivery's token to `/verify` and been answered `valid: true`. Always false for a webhook with `verify` off."
												},
												"verifiedAt": {
													"type": "string",
													"description": "When that first happened, or null.",
													"format": "date-time",
													"nullable": true
												},
												"payload": {
													"type": "object",
													"description": "The envelope as the plane built it — what your server was sent, or what a webhook with no url recorded: `id`, `webhook`, `project`, `device`, `at`, `verify` and `data`, the app's own JSON untouched. ONLY on `GET …/deliveries/{id}`, and on the list with `include=payload`. Null for a delivery recorded before 2026-10-06, which kept none.",
													"nullable": true
												},
												"response": {
													"type": "string",
													"description": "The first 1024 characters of what your server answered. ONLY on `GET …/deliveries/{id}`. Null when it answered nothing, was never reached, or the webhook has no url.",
													"nullable": true
												}
											}
										}
									}
								}
							}
						}
					},
					"400": {
						"description": "BAD_PARAM",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"403": {
						"description": "OUT_OF_SCOPE",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"404": {
						"description": "NO_WEBHOOK · NO_DELIVERY",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			}
		},
		"/v1/webhooks/{slug}/test": {
			"parameters": [
				{
					"name": "slug",
					"in": "path",
					"required": true,
					"schema": {
						"type": "string"
					},
					"description": "The webhook's slug — what your app says in `Arda.webhook.call(slug, …)`, not an id."
				}
			],
			"post": {
				"tags": [
					"webhooks"
				],
				"summary": "Send a test delivery now",
				"operationId": "testWebhook",
				"description": "The plane makes one call to the url exactly as it would for a device — same headers, same signature, same log row, with `device: null` — and reports what came back. Needs `webhooks:w` and a package that includes webhooks. On a webhook with no url nothing is sent: the delivery is recorded and the answer is `ok` with status 202. `502 CONNECT_UNAVAILABLE` means the delivery service did not answer, which is a fact about the plane and not about your webhook.",
				"responses": {
					"200": {
						"description": "What happened",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"delivery": {
											"type": "object",
											"properties": {
												"id": {
													"type": "string",
													"nullable": true,
													"description": "The delivery id, also in the log."
												},
												"ok": {
													"type": "boolean",
													"description": "Whether your server answered 2xx."
												},
												"status": {
													"type": "integer",
													"description": "0 when the server was never reached."
												},
												"ms": {
													"type": "integer"
												},
												"body": {
													"type": "string",
													"nullable": true,
													"description": "The first 1024 characters of what your server answered. Null when there was none."
												},
												"error": {
													"type": "string",
													"nullable": true,
													"description": "Why `status` is 0."
												}
											}
										}
									}
								}
							}
						}
					},
					"403": {
						"description": "OUT_OF_SCOPE · NO_WEBHOOKS",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"404": {
						"description": "NO_WEBHOOK",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"409": {
						"description": "WEBHOOK_DISABLED",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"429": {
						"description": "RATE_LIMITED",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					},
					"502": {
						"description": "CONNECT_UNAVAILABLE",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"description": "The shape of every failure, whatever went wrong.",
									"properties": {
										"error": {
											"type": "object",
											"properties": {
												"code": {
													"type": "string",
													"description": "Stable. **Match on this.** Listed per endpoint in the reference."
												},
												"message": {
													"type": "string",
													"description": "For whoever is reading a terminal. Reworded without notice — never match on it."
												}
											},
											"additionalProperties": true,
											"required": [
												"code",
												"message"
											]
										}
									}
								}
							}
						}
					}
				}
			}
		}
	}
}
