{"openapi":"3.1.0","info":{"title":"UXDuck API","description":"Place search, the public maps keys, feedback, accounts and the updates list, cloud sync, plugins, the hosted model and shared conversations (ADR-0013, ADR-0014, ADR-0016).","version":"0.1.0"},"servers":[{"url":"https://api.uxduck.com","description":"Production"},{"url":"https://api-staging.uxduck.com","description":"Staging"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"The token from the set-auth-token header after signing in."},"signedSession":{"type":"http","scheme":"bearer","description":"The 5-minute signed session from GET /v1/me."}}},"paths":{"/version":{"get":{"operationId":"getVersion","tags":["Service"],"summary":"Get the API release and deployed build","description":"Public and independent of the database. The release version is bundled from package.json; commit and deploymentId are null when unavailable.","security":[],"responses":{"200":{"description":"The API serving this request","content":{"application/json":{"schema":{"type":"object","properties":{"version":{"type":"string"},"commit":{"anyOf":[{"type":"string"},{"type":"null"}]},"deploymentId":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["version","commit","deploymentId"]}}}}}}},"/v1/places/suggest":{"post":{"operationId":"suggestPlaces","tags":["Places"],"summary":"Suggest place names as the operator types","description":"Global names without positions, from Esri's suggest operation, which is not billed. Legacy near coordinates are accepted but ignored. Queries shorter than two characters answer an empty list.","responses":{"200":{"description":"Suggestions","content":{"application/json":{"schema":{"type":"object","properties":{"suggestions":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"detail":{"type":"string"},"key":{"type":"string"},"text":{"type":"string"}},"required":["name","detail","key","text"]}},"attribution":{"type":"string"}},"required":["suggestions"]}}}},"400":{"description":"The body does not match the schema","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Too many requests from this address. Try again in a minute","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"502":{"description":"Esri is not answering or refused the request","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Place search is not configured","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"q":{"type":"string","maxLength":200},"limit":{"type":"integer","minimum":1,"maximum":10,"default":6},"near":{"type":"object","properties":{"latitudeDeg":{"type":"number","minimum":-90,"maximum":90},"longitudeDeg":{"type":"number","minimum":-180,"maximum":180}},"required":["latitudeDeg","longitudeDeg"]}},"required":["q"]}}}}}},"/v1/places/locate":{"post":{"operationId":"locatePlace","tags":["Places"],"summary":"Locate a suggestion","description":"One geocode, from the suggestion's key and text.","responses":{"200":{"description":"The place","content":{"application/json":{"schema":{"type":"object","properties":{"place":{"type":"object","properties":{"name":{"type":"string"},"detail":{"type":"string"},"latitudeDeg":{"type":"number"},"longitudeDeg":{"type":"number"}},"required":["name","detail","latitudeDeg","longitudeDeg"]},"attribution":{"type":"string"}},"required":["place","attribution"]}}}},"400":{"description":"The body does not match the schema","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Esri found no place for the suggestion","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Too many requests from this address. Try again in a minute","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"502":{"description":"Esri is not answering or refused the request","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Place search is not configured","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string","minLength":1,"maxLength":1000},"text":{"type":"string","minLength":1,"maxLength":300},"near":{"type":"object","properties":{"latitudeDeg":{"type":"number","minimum":-90,"maximum":90},"longitudeDeg":{"type":"number","minimum":-180,"maximum":180}},"required":["latitudeDeg","longitudeDeg"]}},"required":["key","text"]}}}}}},"/v1/places/reverse":{"post":{"operationId":"nameMapLocation","tags":["Places"],"summary":"Name a map location for saving","description":"One reverse geocode. Returns the address with the original clicked coordinates, never the nearby address's position. Shares the locate rate limit.","responses":{"200":{"description":"The named location","content":{"application/json":{"schema":{"type":"object","properties":{"place":{"type":"object","properties":{"name":{"type":"string"},"detail":{"type":"string"},"latitudeDeg":{"type":"number"},"longitudeDeg":{"type":"number"}},"required":["name","detail","latitudeDeg","longitudeDeg"]},"attribution":{"type":"string"}},"required":["place","attribution"]}}}},"400":{"description":"The body does not match the schema","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Esri found no address for this location","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Too many requests from this address. Try again in a minute","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"502":{"description":"Esri is not answering or refused the request","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Place search is not configured","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"latitudeDeg":{"type":"number","minimum":-90,"maximum":90},"longitudeDeg":{"type":"number","minimum":-180,"maximum":180}},"required":["latitudeDeg","longitudeDeg"]}}}}}},"/v1/maps/google-key":{"get":{"operationId":"getGoogleMapsKey","tags":["Maps"],"summary":"The public Google Maps key for photorealistic 3D tiles","description":"The key browsers pass to Google's Map Tiles API for photorealistic 3D tiles in the 3D map. Google keys do not expire, so expiresAt is null. Never cached.","responses":{"200":{"description":"The key","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"expiresAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]}},"required":["key","expiresAt"]}}}},"503":{"description":"No key is set","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/maps/key":{"get":{"operationId":"getMapKey","tags":["Maps"],"summary":"The public ArcGIS maps key","description":"The key browsers use to load Esri's basemaps directly, with its expiry read from Esri, or null when Esri does not say. Never cached.","responses":{"200":{"description":"The key","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"expiresAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]}},"required":["key","expiresAt"]}}}},"503":{"description":"No key is set, Esri refuses it, or it has expired","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/feedback":{"post":{"operationId":"sendFeedback","tags":["Feedback"],"summary":"Send a feedback report","description":"Multipart: `report` (JSON), and optionally `screenshot` (PNG, JPEG or WebP, 3 MB), `bundle` (uxduck.feedback/1, 5 MB), `flight` (a tlog, 20 MB), `voice` (a recording up to 5 minutes, 10 MB, transcribed) and up to 10 `attachment` parts (any files, 25 MB together). The text may be empty with a voice note. Keeps each report id once and queues durable delivery as one Linear issue, emails the reply address that it arrived, and belongs to the signed-in account when there is one.","responses":{"200":{"description":"Already kept, or the same text within the hour","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"enum":["received","filed","duplicate","held"],"type":"string"},"issue":{"type":"string"},"transcript":{"type":"string"}},"required":["status"]}}}},"202":{"description":"Received; durable background delivery to Linear is pending","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"enum":["received","filed","duplicate","held"],"type":"string"},"issue":{"type":"string"},"transcript":{"type":"string"}},"required":["status"]}}}},"400":{"description":"The report or a file does not pass its checks","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Turnstile did not pass","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Credited, but the account has no username yet","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Too large","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Too many reports from this address","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Feedback is not configured: the database, the bucket, Linear or Slack, or Cartesia for a voice note","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/feedback/{id}/more":{"post":{"operationId":"addToFeedback","tags":["Feedback"],"summary":"Add words, files or a voice note to a report sent earlier","description":"Multipart: `addition` (JSON: `id`, `key`, `text`), up to 10 `attachment` parts (25 MB together) and a `voice` note (5 minutes, transcribed). The key is the one sent with the report, or the owner's key from `GET /mine`. Becomes a comment on the report's Linear issue once it is filed.","responses":{"200":{"description":"Already added","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"enum":["received","filed","duplicate","held"],"type":"string"},"issue":{"type":"string"},"transcript":{"type":"string"}},"required":["status"]}}}},"202":{"description":"Received; durable background delivery is pending","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"enum":["received","filed","duplicate","held"],"type":"string"},"issue":{"type":"string"},"transcript":{"type":"string"}},"required":["status"]}}}},"400":{"description":"Nothing to add, or a file does not pass its checks","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No report with that id and key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Too large","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Too many additions","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Feedback is not configured, or Cartesia for a voice note","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/v1/feedback/mine":{"get":{"operationId":"myFeedback","tags":["Feedback"],"summary":"The signed-in account's feedback, newest first","description":"Every report sent while signed in to this account, or claimed by it, from any computer: its words, the transcript of its voice note, its files' names, its reference and what was added since, with a key to add more. Up to 100.","responses":{"200":{"description":"The account's reports","content":{"application/json":{"schema":{"type":"object","properties":{"reports":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string"},"text":{"type":"string"},"transcript":{"type":"string"},"at":{"type":"number"},"status":{"enum":["received","filed","held"],"type":"string"},"issue":{"type":"string"},"files":{"type":"array","items":{"type":"string"}},"addKey":{"type":"string"},"additions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"at":{"type":"number"},"text":{"type":"string"},"transcript":{"type":"string"},"files":{"type":"array","items":{"type":"string"}}},"required":["id","at","text","files"]}}},"required":["id","kind","text","at","status","files","addKey","additions"]}}},"required":["reports"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Feedback is not configured","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/feedback/claim":{"post":{"operationId":"claimFeedback","tags":["Feedback"],"summary":"Keep reports sent from this browser in the signed-in account","description":"JSON: `reports`, up to 50 `{ id, key }` with the keys sent with them. Each report that has no owner yet and whose key matches becomes the account's.","responses":{"200":{"description":"How many became the account's","content":{"application/json":{"schema":{"type":"object","properties":{"claimed":{"type":"number"}},"required":["claimed"]}}}},"400":{"description":"Not a list of reports and keys","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Feedback is not configured","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/speech/stream":{"get":{"operationId":"streamSpeech","tags":["Speech"],"summary":"Dictate: speech in, words back, live","description":"A WebSocket. Send 16 kHz mono 16-bit PCM as binary frames, then `finalize` and `close` as text. Answers JSON: `partial` (the words being said), `final` (words that will not change), `flushed`, `limit`, `error` and `done`. Up to 5 minutes, from a shared allowance. A refusal is the socket opened, one `error` with its `code`, and closed.","responses":{"101":{"description":"The socket"},"426":{"description":"Not a WebSocket request","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/speech/transcribe":{"post":{"operationId":"transcribeSpeech","tags":["Speech"],"summary":"Write down one of UXDuck's own recordings again","description":"The body is a WAV recording as UXDuck's dictation makes it (16 kHz mono 16-bit PCM), up to 5 minutes. Answers its words and its length in seconds.","responses":{"200":{"description":"Its words","content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"},"seconds":{"type":"number"}},"required":["text","seconds"]}}}},"400":{"description":"Not one of UXDuck's own recordings","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Longer than 5 minutes","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Too many at once, or the shared allowance is used up","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"502":{"description":"Cartesia could not write it down","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Transcription is switched off or not set up","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/auth/email-code/request":{"post":{"operationId":"requestAccountEmailCode","tags":["Accounts"],"summary":"Request a sign-in or registration code","description":"With `continue`, an existing account is emailed a sign-in code and any other address a code that creates its account once verified; the email says which. `login` never creates an account and `signup` always registers. Responses do not disclose account existence, except that a staging allowlist refuses unlisted new addresses.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","maxLength":254},"intent":{"enum":["continue","login","signup"],"type":"string"}},"required":["email","intent"]}}}},"responses":{"200":{"description":"Request accepted","content":{"application/json":{"schema":{"type":"object","properties":{"challengeId":{"type":"string"}},"required":["challengeId"]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Verification or registration is not allowed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Too many requests","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Email unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/auth/email-code/verify":{"post":{"operationId":"verifyAccountEmailCode","tags":["Accounts"],"summary":"Complete login or registration","description":"The stored challenge determines the email and intent. Codes expire in ten minutes, allow five incorrect attempts and can only be used once.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"challengeId":{"type":"string","format":"uuid"},"code":{"type":"string","pattern":"^\\d{6}$"}},"required":["challengeId","code"]}}}},"responses":{"200":{"description":"Authenticated; a browser receives a cookie only"},"400":{"description":"Invalid or expired code","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Too many code attempts","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Too many requests","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/auth/email-otp/send-verification-otp":{"post":{"operationId":"sendSignInCode","tags":["Accounts"],"summary":"Email a sign-in code","description":"Requests a code for an existing account only. Never creates an account. Limited per address and per email. A page sends a Turnstile token in X-Turnstile-Token; the daemon cannot and sends none.","parameters":[{"name":"X-Turnstile-Token","in":"header","required":false,"schema":{"type":"string"},"description":"Cloudflare Turnstile's token, from a web page"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"type":{"enum":["sign-in"],"type":"string"}},"required":["email","type"]}}}},"responses":{"200":{"description":"The code was sent","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}},"403":{"description":"The Turnstile check did not pass","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Too many codes asked for","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/auth/sign-in/email-otp":{"post":{"operationId":"signInWithCode","tags":["Accounts"],"summary":"Sign in with the emailed code","description":"Signs into an existing account only; registration uses /email-code/request with signup intent. The bearer token comes back in the set-auth-token header and as token; a page gets neither, only the httpOnly session cookie.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"otp":{"type":"string"}},"required":["email","otp"]}}}},"responses":{"200":{"description":"Signed in","content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"},"isNew":{"type":"boolean"},"user":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"name":{"type":"string"},"emailVerified":{"type":"boolean"}},"required":["id","email","name","emailVerified"]}},"required":["isNew","user"]}}},"headers":{"set-auth-token":{"description":"The bearer token for signed-in routes","schema":{"type":"string"}}}},"400":{"description":"The code is wrong or has expired, as Better Auth's { code, message }"},"429":{"description":"Too many tries","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/me/ai-access":{"get":{"operationId":"getAiAccess","tags":["Accounts"],"summary":"This account's AI preview access","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"AI access","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"enum":["request","pending","approved","revoked"],"type":"string"}},"required":["status"]}}}},"401":{"description":"Sign in with a verified account","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"post":{"operationId":"requestAiAccess","tags":["Accounts"],"summary":"Request reviewed AI preview access","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"purpose":{"type":"string","minLength":10,"maxLength":1000}},"required":["purpose"]}}}},"responses":{"200":{"description":"Request status","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"enum":["request","pending","approved","revoked"],"type":"string"}},"required":["status"]}}}},"400":{"description":"Describe the drone or UXDuck use","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Sign in with a verified account","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Access revoked","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Request too large","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/me/sessions":{"get":{"operationId":"listSessions","tags":["Accounts"],"summary":"Where the account is signed in","description":"Every session, most recently used first, with the one asking marked current.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The sessions","content":{"application/json":{"schema":{"type":"object","properties":{"sessions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"current":{"type":"boolean"},"computer":{"anyOf":[{"type":"string"},{"type":"null"}]},"userAgent":{"anyOf":[{"type":"string"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time"},"lastActiveAt":{"type":"string","format":"date-time"}},"required":["id","current","computer","userAgent","createdAt","lastActiveAt"]}}},"required":["sessions"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/me/sessions/current":{"put":{"operationId":"nameSession","tags":["Accounts"],"summary":"Name this session after its computer","description":"UXDuck Desktop calls this after signing in, so the account lists it by name.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":64}},"required":["name"]}}}},"responses":{"200":{"description":"Named","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":64}},"required":["name"]}}}},"400":{"description":"Not a name","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/me/sessions/sign-out-others":{"post":{"operationId":"signOutOtherSessions","tags":["Accounts"],"summary":"Sign out everywhere else","description":"Ends every session of the account but the one asking.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"How many were signed out","content":{"application/json":{"schema":{"type":"object","properties":{"signedOut":{"type":"number"}},"required":["signedOut"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/me/sessions/{id}":{"delete":{"operationId":"signOutSession","tags":["Accounts"],"summary":"Sign out one session","description":"Ends one of the account's sessions, this one included.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Signed out","content":{"application/json":{"schema":{"type":"object","properties":{"signedOut":{"type":"number"}},"required":["signedOut"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such session in this account","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/v1/me/delete":{"post":{"operationId":"askToDeleteAccount","tags":["Accounts"],"summary":"Email a link that deletes the account","description":"The link opens the app's /account/delete page, works for one hour and is used once. Limited per account.","security":[{"bearerAuth":[]}],"responses":{"202":{"description":"Check your email","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"const":"check-your-email"}},"required":["status"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Asked too often","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/me/delete/check":{"post":{"operationId":"checkAccountDeletion","tags":["Accounts"],"summary":"The address a deletion link deletes","description":"For the page that confirms, before anything is deleted.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","minLength":1}},"required":["token"]}}}},"responses":{"200":{"description":"The account's address","content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"}},"required":["email"]}}}},"400":{"description":"No token","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"The link has been used, has expired or is not ours","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/me/delete/confirm":{"post":{"operationId":"deleteAccount","tags":["Accounts"],"summary":"Delete the account a link names","description":"Deletes the account's files, then its rows and every session, and emails a confirmation. Trial chats used stay used for that address.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","minLength":1}},"required":["token"]}}}},"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"const":"deleted"}},"required":["status"]}}}},"400":{"description":"No token","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"The link has been used, has expired or is not ours","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here, or the files could not be deleted yet","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/me":{"get":{"operationId":"getMe","tags":["Accounts"],"summary":"The signed-in account","description":"The account, whether it is on the updates list, whether AI chat is on for it, the models the hosted model offers, and a 5-minute signed session: the bearer token for /v1/model.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The account","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"name":{"type":"string"},"username":{"anyOf":[{"type":"string"},{"type":"null"}]},"emailVerified":{"type":"boolean"},"role":{"anyOf":[{"type":"string"},{"type":"null"}]},"aiReviewer":{"type":"boolean"},"sourceAnswered":{"type":"boolean"}},"required":["id","email","name","username","emailVerified","role","aiReviewer","sourceAnswered"]},"productUpdates":{"type":"boolean"},"analyticsExcluded":{"type":"boolean"},"aiChat":{"type":"boolean"},"hostedModels":{"type":"array","items":{"type":"string"}},"signedSession":{"type":"object","properties":{"token":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"}},"required":["token","expiresAt"]}},"required":["user","productUpdates","analyticsExcluded","aiChat","hostedModels","signedSession"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/me/account":{"get":{"operationId":"getMyAccount","tags":["Accounts"],"summary":"The signed-in account alone","description":"The account as GET /v1/me gives it, without the updates list, the AI chat flag or a signed session: what the website reads for every signed-in page.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The account","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"name":{"type":"string"},"username":{"anyOf":[{"type":"string"},{"type":"null"}]},"emailVerified":{"type":"boolean"},"role":{"anyOf":[{"type":"string"},{"type":"null"}]},"aiReviewer":{"type":"boolean"},"sourceAnswered":{"type":"boolean"}},"required":["id","email","name","username","emailVerified","role","aiReviewer","sourceAnswered"]},"analyticsExcluded":{"type":"boolean"}},"required":["user","analyticsExcluded"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/me/name":{"put":{"operationId":"setName","tags":["Accounts"],"summary":"Set the account's name","description":"What the team sees with the account's feedback: spaces collapsed, up to 80 characters. Empty removes it.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"}},"required":["name"]}}}},"responses":{"200":{"description":"The name","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"}},"required":["name"]}}}},"400":{"description":"Not a name","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/me/source":{"put":{"operationId":"setSignupSource","tags":["Accounts"],"summary":"Say where the account heard of UXDuck","description":"Asked once of every account whose `sourceAnswered` is false (S251). `skipped` records that the person passed. `other` is kept, spaces collapsed and up to 80 characters, only with the source `other`. An account answers once.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"source":{"enum":["youtube","search","reddit","forum","friend","ai","other","skipped"],"type":"string"},"other":{"type":"string"}},"required":["source"]}}}},"responses":{"204":{"description":"Recorded"},"400":{"description":"Not one of the sources","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Already answered","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/me/username":{"put":{"operationId":"setUsername","tags":["Accounts"],"summary":"Choose the account's username","description":"Lowercased. The old username is held for this account for 30 days, and issues credited to the account are re-credited.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"username":{"type":"string"}},"required":["username"]}}}},"responses":{"200":{"description":"The username","content":{"application/json":{"schema":{"type":"object","properties":{"username":{"type":"string"}},"required":["username"]}}}},"400":{"description":"Not a username that can be used","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Taken, or held for another account","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/updates":{"post":{"operationId":"joinUpdates","tags":["Updates"],"summary":"Join the updates list","description":"Emails a confirmation link, at most once in 10 minutes to an address. Already subscribed addresses receive a reminder, at most once in 24 hours after the last sign-up email. The answer is the same whether or not the address is already on the list. Deployed APIs require a Turnstile secret; where a secret is configured, the page's token in X-Turnstile-Token must pass.","parameters":[{"name":"X-Turnstile-Token","in":"header","required":false,"description":"Cloudflare Turnstile's token, from the website's form","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"}},"required":["email"]}}}},"responses":{"202":{"description":"Check your email","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"const":"check-your-email"}},"required":["status"]}}}},"400":{"description":"Not an email address","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"The Turnstile check did not pass","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Too many from this address","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/updates/confirm":{"post":{"operationId":"confirmUpdates","tags":["Updates"],"summary":"Confirm a place on the updates list","description":"The token from the confirmation email's link, which lasts 7 days.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"}},"required":["token"]}}}},"responses":{"200":{"description":"Subscribed","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"const":"subscribed"}},"required":["status"]}}}},"400":{"description":"No token","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"The link has been used, has expired or is not ours","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/updates/unsubscribe":{"post":{"operationId":"leaveUpdates","tags":["Updates"],"summary":"Leave the updates list","description":"The token from the unsubscribe link in the confirmation email or any update.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"}},"required":["token"]}}}},"responses":{"200":{"description":"Unsubscribed","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"const":"unsubscribed"}},"required":["status"]}}}},"400":{"description":"No token","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"The link is not ours","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/updates/me":{"get":{"operationId":"getMyUpdates","tags":["Updates"],"summary":"Whether the signed-in account is on the updates list","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Its choice","content":{"application/json":{"schema":{"type":"object","properties":{"subscribed":{"type":"boolean"}},"required":["subscribed"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"put":{"operationId":"setMyUpdates","tags":["Updates"],"summary":"Join or leave the updates list as the signed-in account","description":"No confirmation email: the account's email is already verified.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"subscribed":{"type":"boolean"}},"required":["subscribed"]}}}},"responses":{"200":{"description":"Its choice","content":{"application/json":{"schema":{"type":"object","properties":{"subscribed":{"type":"boolean"}},"required":["subscribed"]}}}},"400":{"description":"subscribed is not true or false","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"The account's email is not verified","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/simulation-handoff":{"post":{"operationId":"emailSimulationLink","tags":["Simulation"],"summary":"Email a simulation link to open on a computer","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","maxLength":254},"updates":{"type":"boolean"},"handoffId":{"type":"string","format":"uuid"},"start":{"type":"object","properties":{"vehicle":{"anyOf":[{"const":"plane"},{"const":"multicopter"},{"const":"vtol"},{"const":"rover"},{"const":"boat"},{"const":"submarine"},{"const":"airship"},{"const":"jet"}]},"latitudeDeg":{"type":"number","minimum":-90,"maximum":90},"longitudeDeg":{"type":"number","minimum":-180,"maximum":180},"headingDeg":{"type":"number","minimum":0,"maximum":360},"inAirM":{"type":"number","minimum":10,"maximum":500}},"required":["vehicle","latitudeDeg","longitudeDeg","headingDeg"]}},"required":["email","updates","handoffId","start"]}}}},"responses":{"202":{"description":"Email sent; updates require their own confirmation","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"const":"sent"},"updates":{"enum":["requested","not-requested","unavailable"],"type":"string"}},"required":["status","updates"]}}}},"400":{"description":"Invalid email or starting choices","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Verification failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Too many email requests","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Email or verification unavailable","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/sync/push":{"post":{"operationId":"pushChanges","tags":["Sync"],"summary":"Send this computer's latest changes","description":"Each change replaces the account's copy of its entity only if it wins: a higher version, or the higher digest at the same version. At most 500 changes and 4 MB a push; a change over 512 KB is refused and counted in refused.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"deviceId":{"type":"string","pattern":"^dv[0-9a-f]{16}$"},"deviceName":{"type":"string","minLength":1,"maxLength":64},"changes":{"type":"array","items":{"type":"object","properties":{"entity":{"enum":["thread","item","folder","place","snapshot","operator","vehicle","history","plugin","own-set","link","plan","baseline","flight","operation","dialect","firmware-profile"],"type":"string"},"id":{"type":"string","minLength":1,"maxLength":200},"version":{"type":"integer","minimum":0,"maximum":9007199254740991},"digest":{"type":"string","pattern":"^[0-9a-f]{64}$"},"tsUs":{"type":"integer","minimum":0,"maximum":9007199254740991},"change":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["entity","id","version","digest","tsUs","change"]},"maxItems":500}},"required":["deviceId","deviceName","changes"]}}}},"responses":{"200":{"description":"How many were taken, lost to a newer copy, or refused","content":{"application/json":{"schema":{"type":"object","properties":{"taken":{"type":"integer","minimum":0,"maximum":9007199254740991},"skipped":{"type":"integer","minimum":0,"maximum":9007199254740991},"refused":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["taken","skipped","refused"]}}}},"400":{"description":"The push does not match its schema","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"The push is over 4 MB","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/sync/pull":{"get":{"operationId":"pullChanges","tags":["Sync"],"summary":"The copies other computers replaced since a cursor","description":"Oldest first, at most 500 a page, skipping the asking computer's own. Ask again with the cursor while more is true.","security":[{"bearerAuth":[]}],"parameters":[{"name":"device","in":"query","required":true,"schema":{"type":"string","pattern":"^dv[0-9a-f]{16}$"},"description":"The asking computer's device id"},{"name":"after","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0},"description":"The cursor from the last pull"}],"responses":{"200":{"description":"A page of changes and the next cursor","content":{"application/json":{"schema":{"type":"object","properties":{"changes":{"type":"array","items":{"type":"object","properties":{"device":{"type":"string"},"entity":{"enum":["thread","item","folder","place","snapshot","operator","vehicle","history","plugin","own-set","link","plan","baseline","flight","operation","dialect","firmware-profile"],"type":"string"},"id":{"type":"string"},"version":{"type":"integer","minimum":0,"maximum":9007199254740991},"digest":{"type":"string"},"tsUs":{"type":"integer","minimum":0,"maximum":9007199254740991},"change":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["device","entity","id","version","digest","tsUs","change"]}},"cursor":{"type":"integer","minimum":0,"maximum":9007199254740991},"more":{"type":"boolean"}},"required":["changes","cursor","more"]}}}},"400":{"description":"device or after is not valid","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/sync/logbook":{"get":{"operationId":"getLogbook","tags":["Sync"],"summary":"The flights kept for the logbook, from every computer","description":"Newest first, at most 5000 read. One vehicle's flight sent by two computers is listed once, naming both. Totals are time in the air, real and simulated apart (C9).","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The logbook and its totals","content":{"application/json":{"schema":{"type":"object","properties":{"flights":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"vehicleName":{"type":"string"},"armedAtUs":{"type":"integer","minimum":0,"maximum":9007199254740991},"airUs":{"type":"integer","minimum":0,"maximum":9007199254740991},"devices":{"type":"array","items":{"type":"string"}}},"required":["id","vehicleName","armedAtUs","airUs","devices"]}},"totals":{"type":"object","properties":{"realAirUs":{"type":"integer","minimum":0,"maximum":9007199254740991},"simulatedAirUs":{"type":"integer","minimum":0,"maximum":9007199254740991},"count":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["realAirUs","simulatedAirUs","count"]}},"required":["flights","totals"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/sync/logbook/{id}":{"delete":{"operationId":"deleteLogbookFlight","tags":["Sync"],"summary":"Delete a flight from the account","description":"Each computer's copy of the flight goes. Computers that had it from the account drop it; the one that recorded it keeps it, no longer saved, and can save it again.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The ids deleted","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"array","items":{"type":"string"}}},"required":["deleted"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such flight in the account","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/v1/sync/versions":{"get":{"operationId":"listVersions","tags":["Sync"],"summary":"Every copy of one entity the account was sent","description":"Newest first, at most 50; won says whether it became the account's copy. Kept for 90 days, and the last 50 of each entity for longer (C5).","security":[{"bearerAuth":[]}],"parameters":[{"name":"entity","in":"query","required":true,"schema":{"type":"string"}},{"name":"id","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The entity's versions, newest first","content":{"application/json":{"schema":{"type":"object","properties":{"versions":{"type":"array","items":{"type":"object","properties":{"device":{"type":"string"},"version":{"type":"integer","minimum":0,"maximum":9007199254740991},"digest":{"type":"string"},"tsUs":{"type":"integer","minimum":0,"maximum":9007199254740991},"won":{"type":"boolean"},"change":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["device","version","digest","tsUs","won","change"]}}},"required":["versions"]}}}},"400":{"description":"entity or id is not valid","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/computers/keys":{"get":{"operationId":"grantKeys","tags":["Computers"],"summary":"The keys computers check grants with","description":"The Ed25519 public keys the relay's grants for control from away are signed with (S80). A computer keeps them, so it can check grants while this API is down.","responses":{"200":{"description":"The keys, base64url","content":{"application/json":{"schema":{"type":"object","properties":{"keys":{"type":"array","items":{"type":"object","properties":{"kid":{"type":"string"},"x":{"type":"string"}},"required":["kid","x"]}}},"required":["keys"]}}}}}}},"/v1/computers":{"get":{"operationId":"listComputers","tags":["Computers"],"summary":"The account's computers","description":"Every computer that has connected to the account or synced with it, online first, each with when it was last heard.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The computers","content":{"application/json":{"schema":{"type":"object","properties":{"computers":{"type":"array","items":{"type":"object","properties":{"daemonVersion":{"anyOf":[{"type":"string"},{"type":"null"}]},"id":{"type":"string"},"lastSeenAtMs":{"anyOf":[{"type":"integer","minimum":0,"maximum":9007199254740991},{"type":"null"}]},"name":{"type":"string"},"online":{"type":"boolean"}},"required":["id","name","online"]}}},"required":["computers"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/computers/edge":{"get":{"operationId":"connectComputer","tags":["Computers"],"summary":"Connect this computer to the account","description":"A signed-in UXDuck's one outbound WebSocket (ADR-0003 C7), with its device id and name in the query. JSON-RPC 2.0: `edge/hello` first, then `edge/ping` every few seconds; silence for 15 s takes it offline. A second connection with the same id replaces the first.","security":[{"bearerAuth":[]}],"responses":{"101":{"description":"Switching to the WebSocket"},"400":{"description":"Not a computer's id, or no name","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"426":{"description":"Not a WebSocket upgrade","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/computers/{id}/live":{"get":{"operationId":"watchComputer","tags":["Computers"],"summary":"Watch one of the account's computers","description":"A WebSocket for someone signed in to the same account: `edge/presence` now and whenever the computer comes or goes. Later slices add its vehicles (ADR-0003 C8).","security":[{"bearerAuth":[]}],"responses":{"101":{"description":"Switching to the WebSocket"},"400":{"description":"Not a computer's id, or no name","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Not one of the account's computers","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"426":{"description":"Not a WebSocket upgrade","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/v1/ingest/grants":{"post":{"operationId":"grantChunkUploads","tags":["Flights"],"summary":"URLs to store a flight's chunks","description":"Create-only URLs for up to 50 chunks, each `<recording>/<seq>-<sha256>.tlog.zst`, good for an hour. Put each chunk's bytes to its URL, then report them to /v1/ingest/complete.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"deviceId":{"type":"string","pattern":"^dv[0-9a-f]{16}$"},"chunks":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,120}\\/\\d{6}-[0-9a-f]{64}\\.(tlog\\.zst|mp4|m4s)$"},"size":{"type":"integer","minimum":1,"maximum":67108864},"sha256":{"type":"string","pattern":"^[0-9a-f]{64}$"}},"required":["name","size","sha256"]},"maxItems":50}},"required":["deviceId","chunks"]}}}},"responses":{"200":{"description":"A URL for each chunk","content":{"application/json":{"schema":{"type":"object","properties":{"grants":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"url":{"type":"string"}},"required":["name","url"]}},"expiresAtMs":{"type":"number"}},"required":["grants","expiresAtMs"]}}}},"400":{"description":"Not chunks this computer can store","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused by the account's plan: `code` is `storage-full` past its quota, `plan` for video on a free plan","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/ingest/complete":{"post":{"operationId":"completeChunkUploads","tags":["Flights"],"summary":"Chunks put in storage","description":"Checks each chunk is in storage with its size, and catalogues it in the account. Only acknowledged chunks are the account's to keep; the computer keeps the rest and tries again.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"deviceId":{"type":"string","pattern":"^dv[0-9a-f]{16}$"},"chunks":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,120}\\/\\d{6}-[0-9a-f]{64}\\.(tlog\\.zst|mp4|m4s)$"},"size":{"type":"integer","minimum":1,"maximum":67108864},"sha256":{"type":"string","pattern":"^[0-9a-f]{64}$"},"firstUs":{"type":"integer","minimum":0},"lastUs":{"type":"integer","minimum":0}},"required":["name","size","sha256","firstUs","lastUs"]},"maxItems":50}},"required":["deviceId","chunks"]}}}},"responses":{"200":{"description":"Which chunks the account keeps, and which it did not find","content":{"application/json":{"schema":{"type":"object","properties":{"acknowledged":{"type":"array","items":{"type":"string"}},"missing":{"type":"array","items":{"type":"string"}}},"required":["acknowledged","missing"]}}}},"400":{"description":"Not chunks this computer stored","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/flights":{"get":{"operationId":"listFlights","tags":["Flights"],"summary":"The account's flights","description":"Every flight a computer of the account stored, newest first.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The flights","content":{"application/json":{"schema":{"type":"object","properties":{"flights":{"type":"array","items":{"type":"object","properties":{"deviceId":{"type":"string"},"computer":{"type":"string"},"id":{"type":"string"},"firstUs":{"anyOf":[{"type":"number"},{"type":"null"}]},"lastUs":{"anyOf":[{"type":"number"},{"type":"null"}]},"chunks":{"type":"number"},"bytes":{"type":"number"}},"required":["deviceId","id","firstUs","lastUs","chunks","bytes"]}}},"required":["flights"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/flights/{device}/{id}":{"get":{"operationId":"getFlight","tags":["Flights"],"summary":"A flight's chunks","description":"Its chunks in order, each with a URL to download it for an hour. Decompressed (zstd) and joined, they are the flight's tlog.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The chunks","content":{"application/json":{"schema":{"type":"object","properties":{"chunks":{"type":"array","items":{"type":"object","properties":{"seq":{"type":"number"},"size":{"type":"number"},"sha256":{"type":"string"},"url":{"type":"string"}},"required":["seq","size","sha256","url"]}}},"required":["chunks"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Not one of the account's flights","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"device","required":true},{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/v1/flights/{device}/{id}/log":{"get":{"operationId":"getFlightLog","tags":["Flights"],"summary":"A flight's log","description":"Its chunks joined in order, as one zstd stream (one frame per chunk): decompressed, the flight's tlog. For UXDuck Web's replay (S209), which can't read the chunks' URLs across origins.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The log, zstd","content":{"application/zstd":{}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Not one of the account's flights","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"device","required":true},{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/v1/recordings":{"get":{"operationId":"listRecordings","tags":["Flights"],"summary":"The account's camera recordings","description":"Every camera recording a computer of the account stored, newest first.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The recordings","content":{"application/json":{"schema":{"type":"object","properties":{"recordings":{"type":"array","items":{"type":"object","properties":{"deviceId":{"type":"string"},"computer":{"type":"string"},"id":{"type":"string"},"firstUs":{"anyOf":[{"type":"number"},{"type":"null"}]},"lastUs":{"anyOf":[{"type":"number"},{"type":"null"}]},"chunks":{"type":"number"},"bytes":{"type":"number"}},"required":["deviceId","id","firstUs","lastUs","chunks","bytes"]}}},"required":["recordings"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/recordings/{device}/{id}":{"get":{"operationId":"getRecording","tags":["Flights"],"summary":"A recording's segments","description":"Its init segment (1) and segments in order, each with its capture time and a URL to download it for an hour, and the segments missing. Joined in order they are its MP4.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The segments and the gaps","content":{"application/json":{"schema":{"type":"object","properties":{"segments":{"type":"array","items":{"type":"object","properties":{"seq":{"type":"number"},"size":{"type":"number"},"sha256":{"type":"string"},"firstUs":{"anyOf":[{"type":"number"},{"type":"null"}]},"lastUs":{"anyOf":[{"type":"number"},{"type":"null"}]},"url":{"type":"string"}},"required":["seq","size","sha256","firstUs","lastUs","url"]}},"gaps":{"type":"array","items":{"type":"object","properties":{"fromSeq":{"type":"number"},"toSeq":{"type":"number"},"fromUs":{"anyOf":[{"type":"number"},{"type":"null"}]},"toUs":{"anyOf":[{"type":"number"},{"type":"null"}]}},"required":["fromSeq","toSeq","fromUs","toUs"]}}},"required":["segments","gaps"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Not one of the account's recordings","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"device","required":true},{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/v1/recordings/{device}/{id}/playlist.m3u8":{"get":{"operationId":"getRecordingPlaylist","tags":["Flights"],"summary":"A recording as HLS","description":"An HLS playlist over its fMP4 segments, each with its capture time, and a discontinuity where segments are missing. Its URLs last an hour.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The playlist","content":{"application/vnd.apple.mpegurl":{}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Not one of the account's recordings, or its start has not arrived","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"device","required":true},{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/v1/storage":{"get":{"operationId":"getStorageUsed","tags":["Flights"],"summary":"What the account keeps","description":"Bytes stored for the account: flights' logs, camera recordings and live recordings not yet replaced by them (S84), from all its computers. With its plan (ADR-0046 B3): its quota, and whether it may back up video and simulated flights.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Bytes, in all and by kind, and the plan","content":{"application/json":{"schema":{"type":"object","properties":{"bytes":{"type":"number"},"flights":{"type":"number"},"recordings":{"type":"number"},"live":{"type":"number"},"quotaBytes":{"type":"number"},"plan":{"enum":["free","paid"],"type":"string"},"video":{"type":"boolean"},"simulated":{"type":"boolean"}},"required":["bytes","flights","recordings","live","quotaBytes","plan","video","simulated"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/media/jwks":{"get":{"operationId":"mediaGrantKeys","tags":["Media"],"summary":"The keys media nodes check grants with","description":"The public keys of the media grants (S84): MediaMTX's `authJWTJWKS`. The next key is published before it signs, and the previous one while its grants last.","responses":{"200":{"description":"A JSON Web Key Set","content":{"application/json":{"schema":{"type":"object","properties":{"keys":{"type":"array","items":{"type":"object","properties":{"kid":{"type":"string"},"kty":{"type":"string"},"x":{"type":"string"}},"required":["kid","kty","x"]}}},"required":["keys"]}}}}}}},"/v1/media/publish":{"post":{"operationId":"publishCamera","tags":["Media"],"summary":"Publish a camera's live rendition","description":"For a computer of the account while someone away watches one of its cameras: every media node, nearest first, with a publish grant for an hour that each of them accepts. Ask again before it runs out; a node that fails is left for the next without asking.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"cameraId":{"type":"string"},"deviceId":{"type":"string"},"recordingId":{"anyOf":[{"type":"string"},{"type":"null"}]},"store":{"type":"boolean"}},"required":["cameraId","deviceId","store"]}}}},"responses":{"200":{"description":"The nodes and the grant","content":{"application/json":{"schema":{"type":"object","properties":{"expiresAtMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"grant":{"type":"string"},"nodes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"region":{"type":"string"},"srtUrl":{"anyOf":[{"type":"string"},{"type":"null"}]},"whipUrl":{"type":"string"}},"required":["id","region","whipUrl"]}},"protocol":{"enum":["whip","srt"],"type":"string"},"streamId":{"type":"string"}},"required":["expiresAtMs","grant","nodes","protocol","streamId"]}}}},"400":{"description":"Not a computer's id or a camera's","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Not one of the account's computers","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts or live video are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/media/watch":{"post":{"operationId":"watchCamera","tags":["Media"],"summary":"Play a camera from away","description":"For someone signed in to the same account: the WHEP address of the camera's stream, a read grant for it that lasts two minutes (checked once, as playing starts), and the STUN and TURN servers to reach the node from any network.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"cameraId":{"type":"string"},"deviceId":{"type":"string"},"nodeId":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["cameraId","deviceId"]}}}},"responses":{"200":{"description":"Where and how to play it","content":{"application/json":{"schema":{"type":"object","properties":{"expiresAtMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"grant":{"type":"string"},"iceServers":{"type":"array","items":{"type":"object","properties":{"credential":{"anyOf":[{"type":"string"},{"type":"null"}]},"urls":{"type":"array","items":{"type":"string"}},"username":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["urls"]}},"streamId":{"type":"string"},"whepUrl":{"type":"string"}},"required":["expiresAtMs","grant","iceServers","streamId","whepUrl"]}}}},"400":{"description":"Not a computer's id or a camera's","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Not one of the account's computers","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts or live video are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/media/recordings":{"get":{"operationId":"listLiveRecordings","tags":["Media"],"summary":"The account's live recordings","description":"What the media nodes recorded of cameras watched from away, newest first: one per camera, node and day, kept until the camera's own recording is stored and covers it.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The live recordings","content":{"application/json":{"schema":{"type":"object","properties":{"recordings":{"type":"array","items":{"type":"object","properties":{"deviceId":{"type":"string"},"computer":{"type":"string"},"id":{"type":"string"},"firstUs":{"anyOf":[{"type":"number"},{"type":"null"}]},"lastUs":{"anyOf":[{"type":"number"},{"type":"null"}]},"chunks":{"type":"number"},"bytes":{"type":"number"}},"required":["deviceId","id","firstUs","lastUs","chunks","bytes"]}}},"required":["recordings"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/media/recordings/{device}/{id}":{"get":{"operationId":"getLiveRecording","tags":["Media"],"summary":"A live recording's segments","description":"Its segments in order, each a whole MP4 of about 10 s with its capture time and a URL to download it for an hour.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The segments","content":{"application/json":{"schema":{"type":"object","properties":{"segments":{"type":"array","items":{"type":"object","properties":{"seq":{"type":"number"},"size":{"type":"number"},"sha256":{"type":"string"},"firstUs":{"anyOf":[{"type":"number"},{"type":"null"}]},"lastUs":{"anyOf":[{"type":"number"},{"type":"null"}]},"url":{"type":"string"}},"required":["seq","size","sha256","firstUs","lastUs","url"]}}},"required":["segments"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Not one of the account's live recordings","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"device","required":true},{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/v1/media/live/grants":{"post":{"operationId":"grantLiveUploads","tags":["Media"],"summary":"URLs to store a media node's live recording","description":"For media nodes, with their token: a create-only URL for each of up to 50 segments the node recorded, good for an hour, or why it is not kept (its stream unknown, or its account not storing). Put each, then report them to /v1/media/live/complete.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"nodeId":{"type":"string","pattern":"^[a-z0-9-]{1,32}$"},"segments":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","pattern":"^live\\/s[0-9a-f]{24}$"},"startedAtMs":{"type":"integer","minimum":0},"durationMs":{"type":"integer","minimum":0,"maximum":600000},"size":{"type":"integer","minimum":1,"maximum":67108864},"sha256":{"type":"string","pattern":"^[0-9a-f]{64}$"}},"required":["path","startedAtMs","durationMs","size","sha256"]},"maxItems":50}},"required":["nodeId","segments"]}}}},"responses":{"200":{"description":"A URL or a reason for each segment, in order","content":{"application/json":{"schema":{"type":"object","properties":{"grants":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"url":{"type":"string"},"skip":{"type":"string"}},"required":[]}},"expiresAtMs":{"type":"number"}},"required":["grants","expiresAtMs"]}}}},"400":{"description":"Not segments a node recorded","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Not a media node","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage is not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/media/live/complete":{"post":{"operationId":"completeLiveUploads","tags":["Media"],"summary":"Live segments put in storage","description":"For media nodes: checks each segment is in storage with its size and catalogues it as the account's live recording, with its capture time. The node deletes only what is acknowledged.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"nodeId":{"type":"string","pattern":"^[a-z0-9-]{1,32}$"},"segments":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string","pattern":"^live\\/s[0-9a-f]{24}$"},"startedAtMs":{"type":"integer","minimum":0},"durationMs":{"type":"integer","minimum":0,"maximum":600000},"size":{"type":"integer","minimum":1,"maximum":67108864},"sha256":{"type":"string","pattern":"^[0-9a-f]{64}$"}},"required":["path","startedAtMs","durationMs","size","sha256"]},"maxItems":50}},"required":["nodeId","segments"]}}}},"responses":{"200":{"description":"Which segments the account keeps, by key","content":{"application/json":{"schema":{"type":"object","properties":{"acknowledged":{"type":"array","items":{"type":"string"}},"missing":{"type":"array","items":{"type":"string"}}},"required":["acknowledged","missing"]}}}},"400":{"description":"Not segments a node recorded","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Not a media node","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage is not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/watch-links":{"post":{"operationId":"createWatchLink","tags":["Watch links"],"summary":"Make a watch link","description":"For the owner, at their computer: a link anyone can open to watch it, or one vehicle on it, read only, until it expires (24 hours unless asked, a week at most) or is revoked. The token is in the answer once and never again.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"deviceId":{"type":"string"},"flownBy":{"type":"string"},"hours":{"anyOf":[{"type":"integer","minimum":0,"maximum":4294967295},{"type":"null"}]},"vehicleId":{"anyOf":[{"type":"string"},{"type":"null"}]},"vehicleName":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["deviceId","flownBy"]}}}},"responses":{"200":{"description":"The link and its token","content":{"application/json":{"schema":{"type":"object","properties":{"link":{"type":"object","properties":{"createdAtMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"deviceId":{"type":"string"},"expiresAtMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"flownBy":{"type":"string"},"id":{"type":"string"},"vehicleId":{"anyOf":[{"type":"string"},{"type":"null"}]},"vehicleName":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["createdAtMs","deviceId","expiresAtMs","flownBy","id"]},"token":{"type":"string"}},"required":["link","token"]}}}},"400":{"description":"Not a computer's id, no operator's name, or too long","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Not one of the account's computers","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"get":{"operationId":"listWatchLinks","tags":["Watch links"],"summary":"The owner's watch links","description":"Links that have not ended, newest first; with `device`, only that computer's. Never their tokens.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The links","content":{"application/json":{"schema":{"type":"object","properties":{"links":{"type":"array","items":{"type":"object","properties":{"createdAtMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"deviceId":{"type":"string"},"expiresAtMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"flownBy":{"type":"string"},"id":{"type":"string"},"vehicleId":{"anyOf":[{"type":"string"},{"type":"null"}]},"vehicleName":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["createdAtMs","deviceId","expiresAtMs","flownBy","id"]}}},"required":["links"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"delete":{"operationId":"revokeComputersWatchLinks","tags":["Watch links"],"summary":"Revoke every watch link of a computer","description":"What a computer asks when its owner turns off letting the account watch it, or signs out (W9): every link of `device` ends now.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"How many were revoked","content":{"application/json":{"schema":{"type":"object","properties":{"revoked":{"type":"number"}},"required":["revoked"]}}}},"400":{"description":"Not a computer's id","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/watch-links/{id}":{"delete":{"operationId":"revokeWatchLink","tags":["Watch links"],"summary":"Revoke a watch link","description":"Ends the link now: every guest watching through it is closed within a second, and the link answers that it has ended from then on.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Revoked","content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Not one of the account's links","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/v1/watch/{token}":{"get":{"operationId":"watchLinkInfo","tags":["Watch links"],"summary":"What a watch link shows","description":"For anyone with the link, no sign-in: the vehicle it shows, its operator and when it ends. 410 once it has ended, or for a token that never was.","responses":{"200":{"description":"What the page shows","content":{"application/json":{"schema":{"type":"object","properties":{"expiresAtMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"flownBy":{"type":"string"},"vehicleId":{"anyOf":[{"type":"string"},{"type":"null"}]},"vehicleName":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["expiresAtMs","flownBy"]}}}},"410":{"description":"The link has ended","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"token","required":true}]}},"/v1/watch/{token}/live":{"get":{"operationId":"watchAsGuest","tags":["Watch links"],"summary":"Watch with a watch link","description":"A WebSocket for a guest: `edge/presence`, `edge/snapshot` and `edge/delta` as a signed-in viewer gets them, for the link's vehicle only if it names one. `edge/watch` and `edge/watchVideo` work; commands, the lease and receipts answer `watch-only`. Closed with 4002 when the link ends and 4003 when it already has 100 guests.","responses":{"101":{"description":"Switching to the WebSocket"},"410":{"description":"The link has ended","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"426":{"description":"Not a WebSocket upgrade","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts or the relay are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"token","required":true}]}},"/v1/watch/{token}/video":{"post":{"operationId":"watchCameraAsGuest","tags":["Watch links"],"summary":"Play a camera with a watch link","description":"As `/v1/media/watch` for the account (S84, W5): a two-minute WHEP read grant and the ICE servers, for a camera of the link's vehicle, or of any vehicle on the computer for a link that shows them all.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"cameraId":{"type":"string"},"nodeId":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["cameraId"]}}}},"responses":{"200":{"description":"Where and how to play it","content":{"application/json":{"schema":{"type":"object","properties":{"expiresAtMs":{"type":"integer","minimum":0,"maximum":9007199254740991},"grant":{"type":"string"},"iceServers":{"type":"array","items":{"type":"object","properties":{"credential":{"anyOf":[{"type":"string"},{"type":"null"}]},"urls":{"type":"array","items":{"type":"string"}},"username":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["urls"]}},"streamId":{"type":"string"},"whepUrl":{"type":"string"}},"required":["expiresAtMs","grant","iceServers","streamId","whepUrl"]}}}},"400":{"description":"Not a camera's id","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Not a camera this link shows","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"410":{"description":"The link has ended","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts, the relay or live video are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"token","required":true}]}},"/v1/plugins/bundles/{digest}":{"put":{"operationId":"uploadPluginBundle","tags":["Plugins"],"summary":"Upload a plugin's bundle","description":"At most 5 MB. Its SHA-256 must be the digest in the path. Kept once, however many accounts upload it.","security":[{"bearerAuth":[]}],"parameters":[{"name":"digest","in":"path","required":true,"schema":{"type":"string","pattern":"^[0-9a-f]{64}$"},"description":"The bundle's SHA-256, in hex"}],"requestBody":{"required":true,"content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"responses":{"200":{"description":"Kept","content":{"application/json":{"schema":{"type":"object","properties":{"digest":{"type":"string"},"bytes":{"type":"integer"}},"required":["digest","bytes"]}}}},"400":{"description":"Not a digest, or the bundle does not match it","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Over 5 MB","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts or plugin storage are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"get":{"operationId":"downloadPluginBundle","tags":["Plugins"],"summary":"Download a bundle this account uploaded","security":[{"bearerAuth":[]}],"parameters":[{"name":"digest","in":"path","required":true,"schema":{"type":"string","pattern":"^[0-9a-f]{64}$"},"description":"The bundle's SHA-256, in hex"}],"responses":{"200":{"description":"The bundle","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"This account has not uploaded it","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts or plugin storage are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/plugins/shares":{"post":{"operationId":"sharePlugin","tags":["Plugins"],"summary":"Make a link to one of this account's plugins","description":"The bundle must be uploaded first. Nothing shared is reviewed by UXDuck.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pluginId":{"type":"string","pattern":"^[a-z0-9][a-z0-9.-]{0,63}$"},"name":{"type":"string","minLength":1,"maxLength":80},"version":{"type":"string","minLength":1,"maxLength":40},"digest":{"type":"string","pattern":"^[0-9a-f]{64}$"},"capabilities":{"type":"array","items":{"type":"string","maxLength":40},"maxItems":20}},"required":["pluginId","name","version","digest","capabilities"]}}}},"responses":{"201":{"description":"The link","content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"},"pluginId":{"type":"string"},"name":{"type":"string"},"version":{"type":"string"},"digest":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string","format":"date-time"},"sharedBy":{"type":"string"}},"required":["token","pluginId","name","version","digest","capabilities","createdAt"]}}}},"400":{"description":"Not a plugin to share","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The bundle has not been uploaded","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts or plugin storage are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"get":{"operationId":"listPluginShares","tags":["Plugins"],"summary":"This account's live links","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The links, newest first","content":{"application/json":{"schema":{"type":"object","properties":{"shares":{"type":"array","items":{"type":"object","properties":{"token":{"type":"string"},"pluginId":{"type":"string"},"name":{"type":"string"},"version":{"type":"string"},"digest":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string","format":"date-time"},"sharedBy":{"type":"string"}},"required":["token","pluginId","name","version","digest","capabilities","createdAt"]}}},"required":["shares"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts or plugin storage are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/plugins/shares/{token}":{"get":{"operationId":"getPluginShare","tags":["Plugins"],"summary":"What a link offers and who shared it","description":"For any signed-in account. sharedBy is left out when the sharing account has no name.","security":[{"bearerAuth":[]}],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{32}$"},"description":"The share link's token"}],"responses":{"200":{"description":"The plugin offered","content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"},"pluginId":{"type":"string"},"name":{"type":"string"},"version":{"type":"string"},"digest":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string","format":"date-time"},"sharedBy":{"type":"string"}},"required":["token","pluginId","name","version","digest","capabilities","createdAt"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"The link was stopped or never existed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts or plugin storage are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"delete":{"operationId":"stopPluginShare","tags":["Plugins"],"summary":"Stop a link","description":"Computers that installed the plugin keep their copy.","security":[{"bearerAuth":[]}],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{32}$"},"description":"The share link's token"}],"responses":{"204":{"description":"Stopped"},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such live link of this account's","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts or plugin storage are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/plugins/shares/{token}/bundle":{"get":{"operationId":"downloadSharedPlugin","tags":["Plugins"],"summary":"Download the bundle a link offers","security":[{"bearerAuth":[]}],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{32}$"},"description":"The share link's token"}],"responses":{"200":{"description":"The bundle","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"The link was stopped or never existed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts or plugin storage are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/model/responses":{"get":{"operationId":"streamModelResponse","tags":["Hosted model"],"summary":"Stream a Responses model round with native steering","description":"Upgrade to a bounded WebSocket. The first uxduck.start frame supplies the signed session, model request, thread and sharing choice; credentials never enter the URL. Each response.steer update is authorized and reserved against the existing spending budgets. The final uxduck.response.done event follows accounting. Refusals arrive as socket error events.","responses":{"101":{"description":"The authenticated streaming protocol begins with uxduck.start"},"426":{"description":"Open this route as a WebSocket"}}},"post":{"operationId":"createModelResponse","tags":["Hosted model"],"summary":"The hosted model, as the OpenAI Responses API","description":"The OpenAI Responses API, streamed back unchanged. The server sends its own instructions, takes only plain function tools, caps output and enforces spending budgets. A request without X-UXDuck-Continues starts a chat. Refusals are in the provider's error shape, with type uxduck.","security":[{"signedSession":[]}],"parameters":[{"name":"X-UXDuck-Continues","in":"header","required":false,"schema":{"type":"string"},"description":"The response id a tool round follows"},{"name":"X-UXDuck-Thread","in":"header","required":false,"schema":{"type":"string"},"description":"The app's thread id"},{"name":"X-UXDuck-Share","in":"header","required":false,"schema":{"type":"string","enum":["conversations"]},"description":"conversations keeps the turn, scrubbed"}],"requestBody":{"required":true,"description":"The OpenAI Responses API request, at most 256 KB. The model must be one the hosted model offers.","content":{"application/json":{"schema":{"type":"object","properties":{"model":{"type":"string"}},"required":["model"]}}}},"responses":{"200":{"description":"The provider's answer, as it streams","headers":{"X-UXDuck-Model":{"description":"The model that answered","schema":{"type":"string"}}},"content":{"text/event-stream":{"schema":{"type":"string"}}}},"400":{"description":"too-large, bad-request or model","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"type":{"const":"uxduck"},"code":{"type":"string"}},"required":["message","type","code"]}},"required":["error"]}}}},"401":{"description":"session: no signed session, or it has expired","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"type":{"const":"uxduck"},"code":{"type":"string"}},"required":["message","type","code"]}},"required":["error"]}}}},"402":{"description":"daily-cap or global-cap","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"type":{"const":"uxduck"},"code":{"type":"string"}},"required":["message","type","code"]}},"required":["error"]}}}},"403":{"description":"continuation: the chat is not this account's; ai-chat-off: AI chat is turned off for this account","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"type":{"const":"uxduck"},"code":{"type":"string"}},"required":["message","type","code"]}},"required":["error"]}}}},"429":{"description":"provider: the model provider is temporarily throttling requests","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"type":{"const":"uxduck"},"code":{"type":"string"}},"required":["message","type","code"]}},"required":["error"]}}}},"503":{"description":"The hosted model is not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"type":{"const":"uxduck"},"code":{"type":"string"}},"required":["message","type","code"]}},"required":["error"]}}}}}}},"/v1/model/messages":{"post":{"operationId":"createModelMessage","tags":["Hosted model"],"summary":"The hosted model, as the Anthropic Messages API","description":"The Anthropic Messages API, streamed back unchanged. The server sends its own instructions, takes only plain function tools, caps output and enforces spending budgets. A request without X-UXDuck-Continues starts a chat. Refusals are in the provider's error shape, with type uxduck.","security":[{"signedSession":[]}],"parameters":[{"name":"X-UXDuck-Continues","in":"header","required":false,"schema":{"type":"string"},"description":"The response id a tool round follows"},{"name":"X-UXDuck-Thread","in":"header","required":false,"schema":{"type":"string"},"description":"The app's thread id"},{"name":"X-UXDuck-Share","in":"header","required":false,"schema":{"type":"string","enum":["conversations"]},"description":"conversations keeps the turn, scrubbed"}],"requestBody":{"required":true,"description":"The Anthropic Messages API request, at most 256 KB. The model must be one the hosted model offers.","content":{"application/json":{"schema":{"type":"object","properties":{"model":{"type":"string"}},"required":["model"]}}}},"responses":{"200":{"description":"The provider's answer, as it streams","headers":{"X-UXDuck-Model":{"description":"The model that answered","schema":{"type":"string"}}},"content":{"text/event-stream":{"schema":{"type":"string"}}}},"400":{"description":"too-large, bad-request or model","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"type":{"const":"uxduck"},"code":{"type":"string"}},"required":["message","type","code"]}},"required":["error"]}}}},"401":{"description":"session: no signed session, or it has expired","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"type":{"const":"uxduck"},"code":{"type":"string"}},"required":["message","type","code"]}},"required":["error"]}}}},"402":{"description":"daily-cap or global-cap","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"type":{"const":"uxduck"},"code":{"type":"string"}},"required":["message","type","code"]}},"required":["error"]}}}},"403":{"description":"continuation: the chat is not this account's; ai-chat-off: AI chat is turned off for this account","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"type":{"const":"uxduck"},"code":{"type":"string"}},"required":["message","type","code"]}},"required":["error"]}}}},"429":{"description":"provider: the model provider is temporarily throttling requests","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"type":{"const":"uxduck"},"code":{"type":"string"}},"required":["message","type","code"]}},"required":["error"]}}}},"503":{"description":"The hosted model is not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"type":{"const":"uxduck"},"code":{"type":"string"}},"required":["message","type","code"]}},"required":["error"]}}}}}}},"/v1/turns/feedback":{"post":{"operationId":"rateTurn","tags":["Shared conversations"],"summary":"Thumbs up or down on a kept answer","description":"404 with error not-shared when the answer was not kept, so the app can offer to send that one conversation.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"responseId":{"type":"string","maxLength":200},"rating":{"enum":["up","down"],"type":"string"},"comment":{"type":"string","maxLength":2000}},"required":["responseId","rating"]}}}},"responses":{"201":{"description":"Stored","content":{"application/json":{"schema":{"type":"object","properties":{"stored":{"const":true}},"required":["stored"]}}}},"400":{"description":"The body does not match its schema","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"not-shared: the answer was not kept","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/turns/send":{"post":{"operationId":"sendConversation","tags":["Shared conversations"],"summary":"Send one conversation with a rating","description":"For a thumbs-down while sharing was off: the one conversation, scrubbed as any other shared turn.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"thread":{"type":"string","pattern":"^[a-z0-9]{8,64}$"},"rating":{"enum":["up","down"],"type":"string"},"comment":{"type":"string","maxLength":2000},"model":{"type":"string","maxLength":100,"default":"unknown"},"items":{"type":"array","items":{"type":"object","properties":{"kind":{"enum":["userMessage","agentMessage","toolCall","error"],"type":"string"},"text":{"type":"string","maxLength":20000},"toolCall":{"type":"object","properties":{"method":{"type":"string","maxLength":100},"params":{},"result":{},"error":{}},"required":["method"]}},"required":["kind"]},"minItems":1,"maxItems":200}},"required":["thread","rating","items"]}}}},"responses":{"201":{"description":"Stored","content":{"application/json":{"schema":{"type":"object","properties":{"stored":{"const":true}},"required":["stored"]}}}},"400":{"description":"The body does not match its schema","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/turns/mine":{"get":{"operationId":"listMyTurns","tags":["Shared conversations"],"summary":"What this account has shared","description":"The count, how long turns are kept, and the latest 50 with a preview.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The shared turns","content":{"application/json":{"schema":{"type":"object","properties":{"count":{"type":"integer"},"keptForDays":{"type":"integer"},"turns":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"model":{"type":"string"},"preview":{"type":"string"}},"required":["id","createdAt","model","preview"]}}},"required":["count","keptForDays","turns"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"delete":{"operationId":"deleteMyTurns","tags":["Shared conversations"],"summary":"Delete everything this account has shared","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"How many were deleted","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"integer"}},"required":["deleted"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/v1/turns/mine/{id}":{"delete":{"operationId":"deleteMyTurn","tags":["Shared conversations"],"summary":"Delete one shared turn","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"integer"}},"required":["deleted"]}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such turn of this account's","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Accounts are not set up here","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}}}}