{"openapi":"3.1.0","info":{"title":"VartiQ API","version":"1.0.0","description":"REST API for integrating VartiQ with other platforms: send and read messages, manage chats, status updates and blocking, and receive events by webhook.\n\n**Authentication**: `Authorization: Bearer <token>` with a session token from `POST /auth/login` (after `POST /auth/otp`), or an API key (`cak_…`) from `POST /api-keys` (recommended for servers and bots: it survives restarts and can be revoked).\n\n**Real time**: subscribe to webhooks, or connect a Socket.IO client to the same server with `auth: { token }` (a session token or API key) to receive the same events live.\n\n**Limits**: 120 requests per minute per credential (`RateLimit-*` headers; `429` with `Retry-After` when exceeded).\n\n**Errors**: `{ \"error\": { \"code\", \"message\" } }`."},"servers":[{"url":"https://vartiq.prayuktech.com/api/v1"}],"security":[{"bearer":[]}],"tags":[{"name":"Auth","description":"Sign up and log in with one-time codes: a mobile number for individuals; mobile number and email (both verified) for businesses"},{"name":"Account","description":"Your profile"},{"name":"API keys","description":"Long-lived credentials for servers and bots"},{"name":"Webhooks","description":"Receive events at your own URL"},{"name":"Users","description":"Finding and blocking people"},{"name":"Conversations","description":"One-to-one chats and groups"},{"name":"Messages","description":"Sending, reading, starring and deleting messages"},{"name":"Status","description":"Status updates that disappear after 24 hours"},{"name":"Chat lock","description":"Lock chats with a secret code, required again in every new session"},{"name":"Hide chat","description":"Hide chats from the chat list, opened with the secret code or fingerprint/face lock"},{"name":"Encryption","description":"End-to-end encryption for messages typed in the VartiQ web app. Each device makes a key pair and registers the public key; senders encrypt each message on their device, once per recipient device, so the server stores only ciphertext.\n\n**Scheme (v1)**: a random 256-bit content key encrypts the message JSON (`{\"t\": \"text\"}`) with AES-256-GCM. The content key is wrapped for every recipient device with AES-256-GCM under a key from HKDF-SHA-256 over two ECDH (P-256) secrets: one-time key × recipient device key, and sender device key × recipient device key (which proves who sent it). Additional authenticated data is `convId|sender|senderDeviceId`. Voice messages encrypt the audio with the same content key (nonce `mi`).\n\nNot covered: messages sent by the REST API without an envelope, business auto replies, status updates, names, times and who talks to whom. Calls are encrypted peer to peer by WebRTC."},{"name":"Notifications","description":"Notifications on a person's devices for new messages and calls while the app is closed (Web Push). They carry no message text."},{"name":"Parent or guardian","description":"Under 18? A parent or guardian is mandatory: they get a private link, agree, and choose what the child can do (calls, location, being found by new people, public channels), or disable or delete the account. They never see messages."},{"name":"Profile photos","description":"Profile photos: 2 for a free account, 5 with Premium. The first is the picture; privacy is everyone, contacts or nobody (under 18: contacts at most); anyone may report a photo."},{"name":"Translation","description":"Show received messages of a chat in the language you choose"},{"name":"Channels","description":"Public one-to-many channels: owners and admins post, anyone can read and follow (not end-to-end encrypted)"},{"name":"Business","description":"Business profile, hours, greeting and away messages"},{"name":"Meta","description":"Service health and this document"}],"paths":{"/health":{"get":{"tags":["Meta"],"summary":"Health check","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}}},"operationId":"health"}},"/config":{"get":{"tags":["Meta"],"summary":"App configuration: which features are on","description":"Read this when your app starts (and again when the Socket.IO event `config:update` arrives). It says which features the administrator has switched on (hide the others in your UI: the server refuses them with `403 feature_disabled`), whether the service is in **maintenance** (every other call then answers `503 maintenance`), an **announcement** to show people, and the **minimum app version** (send your version in the `X-Client-Version` header, or `auth.clientVersion` on the socket; older apps get `426 update_required`). Mobile and web apps use the same endpoint.","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"features":{"type":"object","additionalProperties":{"type":"boolean"},"example":{"registration":true,"calls":true,"channels":false}},"maintenance":{"type":"object","properties":{"on":{"type":"boolean"},"message":{"type":"string"}},"required":["on"]},"announcement":{"type":"string"},"minClientVersion":{"type":["string","null"]},"limits":{"type":"object","properties":{"free":{"type":"object","properties":{"chatLocks":{"type":"integer"},"hiddenChats":{"type":"integer"}}}}}},"required":["features","maintenance"]}}}}},"operationId":"getConfig"}},"/auth/otp":{"post":{"tags":["Auth"],"summary":"Send a one-time code by SMS or email","description":"Step 1 of sign-up and login. Send `phone` (international format, e.g. `+919876543210`) for an SMS code, or `email` for an email code.\n\nCodes have 6 digits, last 5 minutes and allow 5 wrong tries. Another code for the same address and purpose can be sent after 30 seconds; at most 5 codes per address per hour.\n\n`purpose: \"register\"` fails with 409 `phone_taken`/`email_taken` if the address already has an account; `purpose: \"login\"` fails with 404 `not_registered` if it has none.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"purpose":{"type":"string","enum":["register","login"]},"phone":{"type":"string"},"email":{"type":"string","format":"email"}},"required":["purpose"]},"example":{"purpose":"register","phone":"+919876543210"}}}},"security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OtpSent"}}}},"400":{"$ref":"#/components/responses/E400"},"404":{"$ref":"#/components/responses/E404"},"409":{"$ref":"#/components/responses/E409"},"429":{"$ref":"#/components/responses/E429"},"502":{"$ref":"#/components/responses/E502"}},"operationId":"requestOtp"}},"/auth/register":{"post":{"tags":["Auth"],"summary":"Create an account (verified by OTP)","description":"**Consent (required)**: `consent: { terms: true, version }`. The person must have read and accepted the Terms of Use and Privacy Notice (`GET /legal`); `version` must be the current `version` from `GET /legal`. Without it the answer is `400 consent_required`; an old version is `409 consent_outdated`. The acceptance (what, which version, when) is recorded on the account.\n\n**Individual**: `displayName`, `phone` and `phoneOtp` (the `requestId` from `POST /auth/otp` and the code received by SMS).\n\n**Business**: `accountType: \"business\"`, the business name as `displayName`, `phone` + `phoneOtp`, `email` + `emailOtp` (both are required and verified) and `business` with at least a `category` (any other `Business` fields are optional).\n\n**Age (required)**: `birthDate` as `YYYY-MM-DD`. Below the minimum age (8 by default, set by the administrator) the answer is `403 too_young`. **Under 18** a parent or guardian is mandatory: send `guardian` with `name`, `relationship` and a `phone` and/or `email` (at least one; `400 guardian_required` otherwise; it must not be the child's own number or email). The account is created, but every call except `GET /me`, the legal/consent and data-export/delete calls and `/me/guardian*` answers `403 guardian_required` until the parent or guardian has opened the private link we send them and agreed. A business account must be opened by an adult (`400 business_adult`). The link is built on the server's `PUBLIC_URL`; if that is not set (and the request is not to localhost) a child cannot register (`503`).\n\nEvery code is checked before any is used up, so a typo in one code does not cost the other. A username is made from the name unless you send `username`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"accountType":{"type":"string","enum":["individual","business"],"default":"individual"},"displayName":{"type":"string","maxLength":40},"username":{"type":"string","pattern":"^[a-z0-9_.]{3,20}$","description":"optional"},"phone":{"type":"string"},"phoneOtp":{"$ref":"#/components/schemas/OtpProof"},"email":{"type":"string","format":"email","description":"required for a business; optional for an individual, who then also sends emailOtp (verified like the phone)"},"emailOtp":{"$ref":"#/components/schemas/OtpProof"},"business":{"$ref":"#/components/schemas/Business"},"consent":{"$ref":"#/components/schemas/ConsentAccept"},"birthDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"date of birth, YYYY-MM-DD"},"guardian":{"$ref":"#/components/schemas/GuardianDetails"}},"required":["displayName","phone","phoneOtp","consent","birthDate"]},"example":{"accountType":"business","displayName":"Pizza Palace","phone":"+919876543210","phoneOtp":{"requestId":"otp_6f1c…","code":"482913"},"email":"hello@pizza.example","emailOtp":{"requestId":"otp_a93e…","code":"105577"},"business":{"category":"Restaurant","timezone":"Asia/Kolkata"},"consent":{"terms":true,"version":"2026-10-28"},"birthDate":"1990-05-17"}}}},"security":[],"responses":{"201":{"description":"Account created; `token` is a login session. For someone under 18 the response also has `guardian` (`state: \"pending\"`, whether the private link reached the parent, and the masked contacts): the account cannot be used until the parent or guardian agrees.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Session"}}}},"400":{"$ref":"#/components/responses/E400"},"403":{"$ref":"#/components/responses/E403"},"409":{"$ref":"#/components/responses/E409"},"429":{"$ref":"#/components/responses/E429"},"503":{"$ref":"#/components/responses/E503"}},"operationId":"register"}},"/auth/login":{"post":{"tags":["Auth"],"summary":"Log in with a one-time code","description":"Send the `phone` (or a business account's `email`) with the `requestId` from `POST /auth/otp` (`purpose: \"login\"`) and the `code`. Returns a session token; sessions end when the server restarts, so use an API key for long-running integrations. Accounts created before mobile login can still send `{ \"username\", \"password\" }`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"phone":{"type":"string"},"email":{"type":"string","format":"email"},"requestId":{"type":"string"},"code":{"type":"string","pattern":"^\\d{6}$"},"clientType":{"type":"string","enum":["web","app"],"description":"Send `app` only from the phone app. Anything else is treated as a browser: while a primary phone is signed in it cannot use an SMS code and gets `403 passcode_required` (also from `POST /auth/otp`)"},"pin":{"type":"string","pattern":"^\\d{6}$","description":"the login PIN, if the account has one (`401 pin_required` without it; the code is not used up; `401 wrong_pin`; `429 too_many_attempts` after 5 wrong)"},"linkCode":{"type":"string","pattern":"^\\d{6}$","description":"browser login: the passcode made on the primary phone (`POST /me/link-code`), sent with `phone` instead of `requestId` and `code`"},"deviceName":{"type":"string","maxLength":60,"description":"shown in the list of linked browsers"},"username":{"type":"string","description":"older accounts only, with password"},"password":{"type":"string","description":"older accounts only"}}},"example":{"phone":"+919876543210","requestId":"otp_6f1c…","code":"482913"}}}},"security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Session"}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"},"429":{"$ref":"#/components/responses/E429"}},"operationId":"login"}},"/auth/logout":{"post":{"tags":["Auth"],"summary":"End this session","responses":{"204":{"description":"Done (no body)"},"401":{"$ref":"#/components/responses/E401"}},"operationId":"logout"}},"/me":{"get":{"tags":["Account"],"summary":"My profile","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"},"blocked":{"type":"array","items":{"type":"string"}},"iceServers":{"type":"array","items":{"type":"object"}},"defaultDisappear":{"type":["integer","null"],"description":"timer (seconds) for chats you start"},"business":{"oneOf":[{"$ref":"#/components/schemas/Business"},{"type":"null"}]},"contact":{"$ref":"#/components/schemas/Contact"},"privacy":{"type":"object","properties":{"profileViews":{"type":"boolean"}}},"consent":{"type":"object","properties":{"version":{"type":"string"},"upToDate":{"type":"boolean"},"optional":{"type":"object","additionalProperties":{"type":"boolean"}}}}}}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"getMe"},"patch":{"tags":["Account"],"summary":"Update my profile","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"displayName":{"type":"string"},"about":{"type":"string"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"}},"operationId":"updateMe"},"delete":{"tags":["Account"],"summary":"Delete my account (step 2)","description":"Permanently disables the account at once and erases its chats, media and personal data within 24 hours. **Login stops working immediately**: every session, API key and webhook is revoked, connections are closed, the mobile number and email are released (the person can register again as a brand-new account with a new registration date), and other people see \"Deleted account\". Within 24 hours (by default about an hour) the profile, messages and voice notes sent, photos, statuses, channels, products, keys, devices and settings are erased and the person leaves their groups. A basic inactive record stays: username, registration date, when it was disabled, the record of what was accepted, and any subscription record. Send the `requestId` and `code` from `POST /me/delete/code` and `confirmed: true` (the tick in the confirmation popup). This cannot be undone.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"requestId":{"type":"string"},"code":{"type":"string"},"confirmed":{"type":"boolean","description":"must be true"}},"required":["requestId","code","confirmed"]},"example":{"requestId":"otp_6f1c…","code":"482913","confirmed":true}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean"},"loginBlocked":{"type":"boolean"},"dataErasedBy":{"type":"string","format":"date-time","description":"the latest time the chats, media and personal data will be erased"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"}},"operationId":"deleteMyAccount"}},"/channels":{"get":{"tags":["Channels"],"summary":"Discover public channels","description":"Search by name, handle, description or category; without `q` the most followed channels. Channels are public: anyone signed in can find, read and follow them.","parameters":[{"name":"q","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"channels":{"type":"array","items":{"$ref":"#/components/schemas/Channel"}},"users":{"$ref":"#/components/schemas/Users"}}}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"searchChannels"},"post":{"tags":["Channels"],"summary":"Create a channel","description":"You become its owner and first follower. Up to 10 channels per person. The handle can't be changed later. **Channel posts are public and are not end-to-end encrypted.**","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"handle":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"category":{"type":"string"}},"required":["handle","name"]},"example":{"handle":"pune_food_news","name":"Pune Food News","description":"New openings and offers","category":"Food"}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"channel":{"$ref":"#/components/schemas/Channel"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"409":{"$ref":"#/components/responses/E409"}},"operationId":"createChannel"}},"/me/channels":{"get":{"tags":["Channels"],"summary":"Channels I follow or own","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"channels":{"type":"array","items":{"$ref":"#/components/schemas/Channel"}},"users":{"$ref":"#/components/schemas/Users"}}}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"listMyChannels"}},"/channels/{id}":{"get":{"tags":["Channels"],"summary":"A channel","description":"`id` is the channel id or its handle.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"channel":{"$ref":"#/components/schemas/Channel"},"users":{"$ref":"#/components/schemas/Users"}}}}}},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"getChannel"},"patch":{"tags":["Channels"],"summary":"Edit a channel (owner or admin)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"category":{"type":"string"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"channel":{"$ref":"#/components/schemas/Channel"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"updateChannel"},"delete":{"tags":["Channels"],"summary":"Delete a channel and its posts (owner)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Done (no body)"},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"deleteChannel"}},"/channels/{id}/follow":{"put":{"tags":["Channels"],"summary":"Follow a channel","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"channel":{"$ref":"#/components/schemas/Channel"}}}}}},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"followChannel"},"delete":{"tags":["Channels"],"summary":"Unfollow a channel","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"channel":{"$ref":"#/components/schemas/Channel"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"unfollowChannel"}},"/channels/{id}/admins/{username}":{"put":{"tags":["Channels"],"summary":"Make someone an admin (owner)","description":"Admins can post, delete posts and edit the channel. Up to 5; they also follow the channel.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"username","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"channel":{"$ref":"#/components/schemas/Channel"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"404":{"$ref":"#/components/responses/E404"},"409":{"$ref":"#/components/responses/E409"}},"operationId":"addChannelAdmin"},"delete":{"tags":["Channels"],"summary":"Remove an admin (owner)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"username","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"channel":{"$ref":"#/components/schemas/Channel"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"removeChannelAdmin"}},"/channels/{id}/posts":{"get":{"tags":["Channels"],"summary":"Posts of a channel (oldest first)","description":"Public: no need to follow. Reading as a follower marks the channel read.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":30}},{"name":"before","in":"query","schema":{"type":"integer"},"description":"only posts older than this timestamp; pass `nextBefore` to page back"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"posts":{"type":"array","items":{"$ref":"#/components/schemas/ChannelPost"}},"hasMore":{"type":"boolean"},"nextBefore":{"type":["integer","null"]},"users":{"$ref":"#/components/schemas/Users"}}}}}},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"listChannelPosts"},"post":{"tags":["Channels"],"summary":"Publish a post (owner or admin)","description":"Followers receive it live (`channel:post`). 20 posts per minute.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","maxLength":4000}},"required":["text"]},"example":{"text":"We open at 9 tomorrow! 🍕"}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"post":{"$ref":"#/components/schemas/ChannelPost"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"404":{"$ref":"#/components/responses/E404"},"429":{"$ref":"#/components/responses/E429"}},"operationId":"publishChannelPost"}},"/channels/{id}/posts/{postId}":{"delete":{"tags":["Channels"],"summary":"Delete a post (owner or admin)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"postId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Done (no body)"},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"deleteChannelPost"}},"/channels/{id}/read":{"post":{"tags":["Channels"],"summary":"Mark a channel read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Done (no body)"},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"markChannelRead"}},"/channels/{id}/report":{"post":{"tags":["Channels"],"summary":"Report a channel","description":"One report per person per channel. Reports are kept for whoever runs the service to review (there is no admin screen yet).","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","maxLength":200}},"required":["reason"]},"example":{"reason":"Spam"}}}},"responses":{"204":{"description":"Done (no body)"},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"reportChannel"}},"/users/{username}/catalogue":{"get":{"tags":["Business"],"summary":"A business's product catalogue","description":"Products with prices, for any business account (the profile's `business.productCount` says how many). Anyone signed in can look.","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Catalogue"}}}},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"getCatalogue"}},"/me/catalogue":{"get":{"tags":["Business"],"summary":"My catalogue","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Catalogue"}}}},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"getMyCatalogue"}},"/me/catalogue/currency":{"put":{"tags":["Business"],"summary":"Currency for all prices","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"currency":{"type":"string","pattern":"^[A-Za-z]{3}$"}},"required":["currency"]},"example":{"currency":"USD"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"currency":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"}},"operationId":"setCatalogueCurrency"}},"/me/catalogue/products":{"post":{"tags":["Business"],"summary":"Add a product","description":"Business accounts only; up to 100 products. `price: null` shows \"price on request\". Add a picture with `PUT /me/catalogue/products/{id}/image`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"price":{"type":["number","null"]},"unit":{"type":"string"},"category":{"type":"string"},"sku":{"type":"string"},"link":{"type":"string"},"available":{"type":"boolean","default":true}},"required":["name"]},"example":{"name":"Margherita pizza","description":"Wood-fired, 10 inch","price":299,"unit":"each","category":"Pizza"}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"product":{"$ref":"#/components/schemas/Product"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"409":{"$ref":"#/components/responses/E409"}},"operationId":"addProduct"}},"/me/catalogue/products/{id}":{"put":{"tags":["Business"],"summary":"Update a product","description":"Send only the fields to change; an empty string clears an optional text field.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"price":{"type":["number","null"]},"unit":{"type":"string"},"category":{"type":"string"},"sku":{"type":"string"},"link":{"type":"string"},"available":{"type":"boolean"}}},"example":{"price":349,"available":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"product":{"$ref":"#/components/schemas/Product"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"updateProduct"},"delete":{"tags":["Business"],"summary":"Delete a product","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Done (no body)"},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"deleteProduct"}},"/me/catalogue/products/{id}/image":{"put":{"tags":["Business"],"summary":"Set a product picture","description":"Send the picture itself (`image/jpeg`, `image/png` or `image/webp`, max 1 MB). Replaces the old one.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"image/*":{"schema":{"type":"string","format":"binary"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"product":{"$ref":"#/components/schemas/Product"}}}}}},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"},"413":{"$ref":"#/components/responses/E413"},"415":{"$ref":"#/components/responses/E415"}},"operationId":"setProductImage"},"delete":{"tags":["Business"],"summary":"Remove a product picture","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"product":{"$ref":"#/components/schemas/Product"}}}}}},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"removeProductImage"}},"/appearance":{"get":{"tags":["Account"],"summary":"Colour themes, app icons and avatar shapes","description":"The catalogue of looks. `themes[].tokens` has the colour values for light and dark (CSS variable names, usable as design tokens in a mobile app); `premium: true` options need a Premium subscription. Every theme meets WCAG AA contrast.","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"defaults":{"$ref":"#/components/schemas/AppearancePrefs"},"modes":{"type":"array","items":{"type":"string"}},"themes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"premium":{"type":"boolean"},"swatch":{"type":"string"},"tokens":{"type":["object","null"],"description":"`{ light: {...}, dark: {...} }`; null = the default brand colours"}}}},"icons":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"premium":{"type":"boolean"},"svg":{"type":"string","description":"SVG; colours use `var(--primary)`, replace with the theme colour"}}}},"avatarShapes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"premium":{"type":"boolean"}}}}}}}}}},"operationId":"getAppearanceCatalogue"}},"/legal":{"get":{"tags":["Account"],"summary":"Terms, Privacy Notice and consent texts","description":"Everything a sign-up screen needs: the current `version`, the documents (`url` is a readable page, `json` the same as data), the exact consent wording, and who is responsible (company, Grievance Officer). `configured: false` means the company details are still placeholders.","parameters":[{"name":"lang","in":"query","required":false,"description":"language of the text: `en` (default) or an Eighth-Schedule language code (`as bn brx doi gu hi kn ks kok mai ml mni mr ne or pa sa sat sd ta te ur`). Translations are prepared with automated tools until the operator marks a language as reviewed; the English text applies if anything is unclear.","schema":{"type":"string"}}],"security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"version":{"type":"string"},"configured":{"type":"boolean"},"company":{"type":"object"},"documents":{"type":"array","items":{"type":"object"}},"consents":{"type":"array","items":{"type":"object"}},"language":{"type":"string"},"languages":{"type":"array","items":{"type":"object"}},"ui":{"type":"object"}}}}}}},"operationId":"getLegal"}},"/legal/{doc}":{"get":{"tags":["Account"],"summary":"One legal document as data","description":"`privacy` or `terms`, as `{ id, title, version, sections: [{ title, paragraphs, bullets, table }] }`. The same text is a page at `/legal/privacy` and `/legal/terms`.","parameters":[{"name":"doc","in":"path","required":true,"schema":{"type":"string","enum":["privacy","terms"]}},{"name":"lang","in":"query","required":false,"description":"language of the text: `en` (default) or an Eighth-Schedule language code (`as bn brx doi gu hi kn ks kok mai ml mni mr ne or pa sa sat sd ta te ur`). Translations are prepared with automated tools until the operator marks a language as reviewed; the English text applies if anything is unclear.","schema":{"type":"string"}}],"security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"$ref":"#/components/responses/E404"}},"operationId":"getLegalDocument"}},"/me/consent":{"get":{"tags":["Account"],"summary":"What I have agreed to","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentStatus"}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"getMyConsent"},"post":{"tags":["Account"],"summary":"Accept the current Terms and Privacy Notice","description":"Needed when `upToDate` is false (the texts changed). `terms` must be true.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentAccept"},"example":{"terms":true}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentStatus"}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"}},"operationId":"acceptConsent"}},"/me/consent/{id}":{"put":{"tags":["Account"],"summary":"Give or withdraw an optional consent","description":"`id` is `translation` (message text is sent to a translation service) or `kyc` (an ID number is sent to a verification service). The feature answers `403 consent_translation` / `consent_kyc` until given. Withdrawing translation also switches translation off in every chat.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","enum":["translation","kyc"]}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"granted":{"type":"boolean"}},"required":["granted"]},"example":{"granted":true}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentStatus"}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"}},"operationId":"setOptionalConsent"}},"/me/export":{"get":{"tags":["Account"],"summary":"Download my data","description":"Everything held about the account as one JSON file (profile, settings, consent history, chats with their messages, statuses, channels, developer keys). End-to-end encrypted messages appear as encrypted envelopes.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"exportMyData"}},"/me/delete/code":{"post":{"tags":["Account"],"summary":"Delete my account: send the confirmation code","description":"Step 1: a code is sent by SMS to the account's verified mobile number.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OtpSent"}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"429":{"$ref":"#/components/responses/E429"}},"operationId":"requestAccountDeletionCode"}},"/me/appearance":{"get":{"tags":["Account"],"summary":"My look","description":"`appearance` is what to show now (Premium choices fall back to the defaults while Premium is not active); `saved` is what the person chose, restored when Premium returns.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"appearance":{"$ref":"#/components/schemas/AppearancePrefs"},"saved":{"$ref":"#/components/schemas/AppearancePrefs"},"premium":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"getMyAppearance"},"put":{"tags":["Account"],"summary":"Change my look","description":"Send any of `mode` (auto, light, dark: free), `theme`, `appIcon`, `avatarShape`. Premium options answer `402 premium_required` without Premium; going back to the defaults always works. Other devices get the `appearance:update` event.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppearancePrefs"},"example":{"theme":"violet","appIcon":"heart"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"appearance":{"$ref":"#/components/schemas/AppearancePrefs"},"saved":{"$ref":"#/components/schemas/AppearancePrefs"},"premium":{"type":"boolean"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"402":{"$ref":"#/components/responses/E402"},"403":{"$ref":"#/components/responses/E403"}},"operationId":"setMyAppearance"}},"/translate/languages":{"get":{"tags":["Translation"],"summary":"Languages for translating received messages","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"languages":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"},"native":{"type":"string"}}}},"available":{"type":"boolean"},"sandbox":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"listTranslationLanguages"}},"/conversations/{id}/translation":{"put":{"tags":["Translation"],"summary":"Choose the language received messages of a chat are shown in","description":"Per chat (so per contact in a one-to-one chat), private to you, synced across your devices. `lang: null` turns it off. The chat summary then has `translateTo`. Because messages are end-to-end encrypted, translating means your app sends the decrypted text of received messages to the translation service (not stored); the app asks you before turning it on.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Conversation id. URL-encode it: direct chat ids contain colons, e.g. `dm%3Aalice%3Abob`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"lang":{"type":["string","null"]}},"required":["lang"]},"example":{"lang":"hi"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"convId":{"type":"string"},"lang":{"type":["string","null"]}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"},"503":{"$ref":"#/components/responses/E503"}},"operationId":"setChatTranslation"}},"/conversations/{id}/translate":{"post":{"tags":["Translation"],"summary":"Translate texts into the chat's language","description":"Send up to 30 texts (4000 characters each) that your app has already decrypted; get them back translated, in the same order, with the detected source language. Only works for chats where translation is on (`400 translation_off`). Texts are passed to the translation service and not stored or logged. 120 texts per minute.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Conversation id. URL-encode it: direct chat ids contain colons, e.g. `dm%3Aalice%3Abob`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"texts":{"type":"array","items":{"type":"string"}}},"required":["texts"]},"example":{"texts":["Good morning! Is the order ready?"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"target":{"type":"string"},"translations":{"type":"array","items":{"type":"object","properties":{"text":{"type":"string"},"detected":{"type":["string","null"]}}}}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"},"423":{"$ref":"#/components/responses/E423"},"429":{"$ref":"#/components/responses/E429"},"502":{"$ref":"#/components/responses/E502"},"503":{"$ref":"#/components/responses/E503"}},"operationId":"translateTexts"}},"/users/{username}/view":{"post":{"tags":["Users"],"summary":"Record that I viewed someone's profile","description":"Call this when your app opens a person's profile. They can then see you in **Who viewed my profile** with the date of your last view, whether or not you are in their contacts. Nothing is recorded when you or they have profile views turned off, when either of you has blocked the other, or when you view yourself. Only your latest view is kept.","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Done (no body)"},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"viewProfile"}},"/me/profile-views":{"get":{"tags":["Account"],"summary":"Who viewed my profile","description":"Everyone who viewed your profile in the last 90 days, newest first, each with `inContact` (you have a one-to-one chat with messages) and `lastViewedAt`. Empty with `enabled: false` when you turned profile views off.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean"},"retentionDays":{"type":"integer"},"viewers":{"type":"array","items":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"},"inContact":{"type":"boolean"},"lastViewedAt":{"type":"integer","description":"ms since epoch"}},"required":["user","inContact","lastViewedAt"]}}},"required":["enabled","viewers"]}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"getProfileViews"}},"/me/privacy":{"put":{"tags":["Account"],"summary":"Privacy settings","description":"`profileViews: false` stops recording your own views of other people, hides who viewed you, and erases your current list. It is a trade: with it off you can't see viewers either.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"profileViews":{"type":"boolean"}},"required":["profileViews"]},"example":{"profileViews":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"privacy":{"type":"object","properties":{"profileViews":{"type":"boolean"}}}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"}},"operationId":"updatePrivacy"}},"/me/subscription":{"get":{"tags":["Account"],"summary":"Plan, limits and usage","description":"**Free** accounts can lock up to 3 chats and hide 1 chat. **Premium** has no limits. Premium is bought in advance as a daily, monthly, 3-month, 6-month or yearly plan (no auto-renewal; the administrator can switch plans off or change prices); buying while Premium extends it. When it ends, chats that are already locked or hidden stay protected, but no more can be added until Premium is renewed.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Subscription"}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"getSubscription"}},"/me/subscription/checkout":{"post":{"tags":["Account"],"summary":"Start paying for a plan","description":"Creates an order and returns the provider's `paymentUrl`: with **Razorpay** this is a Razorpay payment page (UPI, cards, netbanking, wallets); open it so the person can pay (card and bank details never touch VartiQ). `paymentUrl` is null in the sandbox, where no real payment is taken. Premium switches on when Razorpay reports the payment received (a signed webhook, even if the app was closed), or when you call `POST /me/subscription/confirm`; the Socket.IO events `subscription:update` and `payment:received` tell open apps at once. Each payment counts once. The order id is the reference printed on the Razorpay page.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"plan":{"type":"string","enum":["daily","monthly","quarterly","halfyearly","yearly"]}},"required":["plan"]},"example":{"plan":"monthly"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"orderId":{"type":"string"},"plan":{"type":"string"},"amount":{"type":"number"},"currency":{"type":"string"},"paymentUrl":{"type":["string","null"]},"provider":{"type":"string"},"providerLabel":{"type":"string"},"sandbox":{"type":"boolean"}},"required":["orderId","plan","amount","currency"]}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"429":{"$ref":"#/components/responses/E429"},"502":{"$ref":"#/components/responses/E502"},"503":{"$ref":"#/components/responses/E503"}},"operationId":"startCheckout"}},"/me/payments":{"get":{"tags":["Account"],"summary":"My payments received (receipts)","description":"Newest first: the payment id, plan, amount, when it was received, Premium until, and any refund.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"payments":{"type":"array","items":{"$ref":"#/components/schemas/PaymentReceipt"}}}}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"listMyPayments"}},"/me/subscription/confirm":{"post":{"tags":["Account"],"summary":"Confirm payment and switch Premium on","description":"The server asks the payment provider whether the order was paid (nothing from the client is trusted). `402 payment_pending` means not paid yet: try again after paying. Each order can be used once.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"orderId":{"type":"string"}},"required":["orderId"]},"example":{"orderId":"ord_…"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Subscription"}}}},"401":{"$ref":"#/components/responses/E401"},"402":{"$ref":"#/components/responses/E402"},"404":{"$ref":"#/components/responses/E404"},"502":{"$ref":"#/components/responses/E502"},"503":{"$ref":"#/components/responses/E503"}},"operationId":"confirmSubscription"}},"/me/verification":{"get":{"tags":["Account"],"summary":"Verified-badge status","description":"Whether the account is verified, whether verification is available on this server, and the ID types accepted (personal accounts and business accounts accept different ones).","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"verified":{"type":"boolean"},"available":{"type":"boolean"},"sandbox":{"type":"boolean","description":"development stand-in: verifies anyone"},"types":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"hint":{"type":"string"}}}}}}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"getVerification"},"post":{"tags":["Account"],"summary":"Finish verification with the code","description":"Send the `requestId` from `POST /me/verification/otp` and the code the person received. On success the account gets the **Verified** badge (`user.verified: true`, visible to everyone). **No KYC information is stored**: only the fact that it is verified. Changing the display name removes the badge until the person verifies again.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"requestId":{"type":"string"},"otp":{"type":"string"}},"required":["requestId","otp"]},"example":{"requestId":"kyc_…","otp":"482913"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"verified":{"type":"boolean"},"user":{"$ref":"#/components/schemas/User"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"502":{"$ref":"#/components/responses/E502"},"503":{"$ref":"#/components/responses/E503"}},"operationId":"finishVerification"}},"/me/verification/otp":{"post":{"tags":["Account"],"summary":"Start identity verification (KYC)","description":"Send the ID `type` (e.g. `aadhaar`, `pan`, `passport`; business accounts: `gstin`, `cin`, `pan`) and `idNumber`. The number is checked for format and passed to the verification gateway, which sends a one-time code to the mobile number linked to the ID; it is **never stored or logged** by VartiQ. 3 attempts per hour. Finish with `POST /me/verification`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string"},"idNumber":{"type":"string"}},"required":["type","idNumber"]},"example":{"type":"pan","idNumber":"ABCDE1234F"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"requestId":{"type":"string"},"expiresIn":{"type":"integer"},"sentTo":{"type":"string"},"devCode":{"type":"string","description":"sandbox with OTP_DEV_CODES=1 only"}},"required":["requestId","expiresIn"]}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"409":{"$ref":"#/components/responses/E409"},"429":{"$ref":"#/components/responses/E429"},"502":{"$ref":"#/components/responses/E502"},"503":{"$ref":"#/components/responses/E503"}},"operationId":"startVerification"}},"/me/contact/otp":{"post":{"tags":["Account"],"summary":"Send a code to add or change my mobile number or email","description":"Send `phone` or `email`. Then confirm with `PUT /me/contact`. A business account needs both a verified mobile number and email.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"phone":{"type":"string"},"email":{"type":"string","format":"email"}}},"example":{"email":"owner@pizza.example"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OtpSent"}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"409":{"$ref":"#/components/responses/E409"},"429":{"$ref":"#/components/responses/E429"},"502":{"$ref":"#/components/responses/E502"}},"operationId":"requestContactOtp"}},"/me/contact":{"put":{"tags":["Account"],"summary":"Save a verified mobile number or email","description":"The same `phone` or `email` as in `POST /me/contact/otp`, with its `requestId` and `code`. A business's contact email follows the verified email.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"phone":{"type":"string"},"email":{"type":"string","format":"email"},"requestId":{"type":"string"},"code":{"type":"string"}},"required":["requestId","code"]},"example":{"email":"owner@pizza.example","requestId":"otp_a93e…","code":"105577"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"contact":{"$ref":"#/components/schemas/Contact"},"business":{"oneOf":[{"$ref":"#/components/schemas/Business"},{"type":"null"}]}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"409":{"$ref":"#/components/responses/E409"}},"operationId":"verifyContact"}},"/me/default-disappearing":{"put":{"tags":["Account"],"summary":"Default message timer for chats you start","description":"Seconds from 60 (1 minute) to 31536000 (365 days); 0 or null turns it off. Applies to new chats you start; existing chats keep their timer.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"seconds":{"type":["integer","null"]}},"required":["seconds"]},"example":{"seconds":604800}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"defaultDisappear":{"type":["integer","null"]}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"}},"operationId":"setDefaultDisappearing"}},"/me/business":{"get":{"tags":["Business"],"summary":"My business profile","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"business":{"oneOf":[{"$ref":"#/components/schemas/Business"},{"type":"null"}]}}}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"getBusiness"},"put":{"tags":["Business"],"summary":"Become a business account, or update it","description":"Needs a verified mobile number and email on the account (400 `verification_required` otherwise; see `/me/contact`). The contact `email` is always the verified email. Everyone sees the business details and hours (with your profile). Greeting and away message texts stay private; they are sent automatically.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Business"},"example":{"category":"Restaurant","description":"Wood-fired pizza since 1998","address":"12 Market St, Pune","email":"hello@pizza.example","website":"https://pizza.example","timezone":"Asia/Kolkata","hours":{"mon":{"mode":"hours","open":"11:00","close":"23:00"},"tue":{"mode":"closed"},"sat":{"mode":"open24"}},"greeting":{"enabled":true,"text":"Thanks for messaging us! How can we help?"},"away":{"enabled":true,"text":"We're closed right now and will reply when we open."}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"business":{"$ref":"#/components/schemas/Business"},"user":{"$ref":"#/components/schemas/User"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"}},"operationId":"setBusiness"},"delete":{"tags":["Business"],"summary":"Switch back to a personal account","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"business":{"type":"null"},"user":{"$ref":"#/components/schemas/User"}}}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"removeBusiness"}},"/api-keys":{"get":{"tags":["API keys"],"summary":"List my API keys","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"apiKeys":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}}}}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"listApiKeys"},"post":{"tags":["API keys"],"summary":"Create an API key","description":"Needs a login session token. The returned `key` is shown only once; store it securely. Use it like a token: `Authorization: Bearer cak_…`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","example":"CRM integration"}}}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"apiKey":{"$ref":"#/components/schemas/ApiKey"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"409":{"$ref":"#/components/responses/E409"}},"operationId":"createApiKey"}},"/api-keys/{id}":{"delete":{"tags":["API keys"],"summary":"Revoke an API key","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Done (no body)"},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"revokeApiKey"}},"/webhooks":{"get":{"tags":["Webhooks"],"summary":"List my webhooks","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"webhooks":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"}},"events":{"type":"array","items":{"type":"string"}}}}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"listWebhooks"},"post":{"tags":["Webhooks"],"summary":"Create a webhook","description":"Events for you are POSTed to `url` (see the Webhooks section). `events` defaults to `[\"*\"]` = everything except `typing` and `presence`. The `secret` (shown once) signs deliveries.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string","enum":["*","message:new","message:status","message:deleted","message:starred","conversation:new","typing","presence","user:updated","blocked:update","call:incoming","call:ended","call:state","status:new","status:viewed","status:deleted","chatlock:update","conversation:updated","message:expired","location:update","poll:update","event:update","event:reminder","ping"]}},"description":{"type":"string"}},"required":["url"]},"example":{"url":"https://example.com/vartiq/webhook","events":["message:new","call:incoming"]}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"secret":{"type":"string"},"webhook":{"$ref":"#/components/schemas/Webhook"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"409":{"$ref":"#/components/responses/E409"}},"operationId":"createWebhook"}},"/webhooks/{id}":{"get":{"tags":["Webhooks"],"summary":"Get a webhook (with its last delivery)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"webhook":{"$ref":"#/components/schemas/Webhook"}}}}}},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"getWebhook"},"patch":{"tags":["Webhooks"],"summary":"Turn a webhook on/off or change its events","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"active":{"type":"boolean"},"events":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"webhook":{"$ref":"#/components/schemas/Webhook"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"updateWebhook"},"delete":{"tags":["Webhooks"],"summary":"Delete a webhook","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Done (no body)"},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"deleteWebhook"}},"/webhooks/{id}/test":{"post":{"tags":["Webhooks"],"summary":"Send a test `ping` event","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"result":{"type":"object","properties":{"ok":{"type":"boolean"},"status":{"type":["integer","null"]},"error":{"type":["string","null"]}}}}}}}},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"testWebhook"}},"/users":{"get":{"tags":["Users"],"summary":"Search people","parameters":[{"name":"q","in":"query","schema":{"type":"string"},"description":"matches username or display name"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"users":{"type":"array","items":{"$ref":"#/components/schemas/User"}}}}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"searchUsers"}},"/users/{username}":{"get":{"tags":["Users"],"summary":"Get a user","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"},"blocked":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"getUser"}},"/blocked":{"get":{"tags":["Users"],"summary":"People I have blocked","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"blocked":{"type":"array","items":{"type":"string"}}}}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"listBlocked"}},"/blocked/{username}":{"put":{"tags":["Users"],"summary":"Block someone","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"blocked":{"type":"array","items":{"type":"string"}},"user":{"$ref":"#/components/schemas/User"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"blockUser"},"delete":{"tags":["Users"],"summary":"Unblock someone","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"blocked":{"type":"array","items":{"type":"string"}},"user":{"$ref":"#/components/schemas/User"}}}}}},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"unblockUser"}},"/conversations":{"get":{"tags":["Conversations"],"summary":"My conversations (newest first)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"conversations":{"type":"array","items":{"$ref":"#/components/schemas/Conversation"}},"users":{"$ref":"#/components/schemas/Users"}}}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"listConversations"},"post":{"tags":["Conversations"],"summary":"Open a direct chat or create a group","description":"Direct: `{ \"type\": \"direct\", \"username\": \"bob\" }` returns the existing chat if there is one. Group: `{ \"type\": \"group\", \"name\": \"Team\", \"members\": [\"bob\", \"carol\"] }`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"type":{"const":"direct"},"username":{"type":"string"}},"required":["type","username"]},{"type":"object","properties":{"type":{"const":"group"},"name":{"type":"string"},"members":{"type":"array","items":{"type":"string"}}},"required":["type","name","members"]}]},"example":{"type":"direct","username":"bob"}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"conversation":{"$ref":"#/components/schemas/Conversation"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"createConversation"}},"/conversations/{id}":{"get":{"tags":["Conversations"],"summary":"Get a conversation","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Conversation id. URL-encode it: direct chat ids contain colons, e.g. `dm%3Aalice%3Abob`."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"conversation":{"$ref":"#/components/schemas/Conversation"},"users":{"$ref":"#/components/schemas/Users"}}}}}},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"getConversation"}},"/conversations/{id}/messages":{"get":{"tags":["Messages"],"summary":"Message history (oldest first)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Conversation id. URL-encode it: direct chat ids contain colons, e.g. `dm%3Aalice%3Abob`."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"before","in":"query","schema":{"type":"integer"},"description":"only messages older than this timestamp; pass `nextBefore` to page back"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"messages":{"type":"array","items":{"$ref":"#/components/schemas/Message"}},"hasMore":{"type":"boolean"},"nextBefore":{"type":["integer","null"]}}}}}},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"},"423":{"$ref":"#/components/responses/E423"}},"operationId":"listMessages"},"post":{"tags":["Messages"],"summary":"Send a text message","description":"Send `Idempotency-Key` (or `clientId` in the body) to make retries safe: the same key returns the original message instead of sending twice.\n\nPlain `text` sent through the API is **not** end-to-end encrypted (the server sees it, and it is shown to recipients as unencrypted). To send an end-to-end encrypted message, encrypt on your own device and send an `e2ee` envelope instead of `text` (see **Encryption**). A stale device list answers `409 keys_changed`: fetch `GET /conversations/{id}/keys` and encrypt again.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Conversation id. URL-encode it: direct chat ids contain colons, e.g. `dm%3Aalice%3Abob`."},{"name":"Idempotency-Key","in":"header","schema":{"type":"string","maxLength":64}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","maxLength":4000},"masked":{"type":"boolean"},"clientId":{"type":"string"},"e2ee":{"$ref":"#/components/schemas/Envelope"}}},"example":{"text":"Hello from the API 👋"}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"$ref":"#/components/schemas/Message"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"404":{"$ref":"#/components/responses/E404"},"409":{"$ref":"#/components/responses/E409"},"423":{"$ref":"#/components/responses/E423"},"429":{"$ref":"#/components/responses/E429"}},"operationId":"sendMessage"}},"/conversations/{id}/voice":{"post":{"tags":["Messages"],"summary":"Send a voice message","description":"Body: the audio file itself (max 3 MB), with its Content-Type (e.g. `audio/ogg`, `audio/webm`, `audio/mp4`, `audio/mpeg`).","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Conversation id. URL-encode it: direct chat ids contain colons, e.g. `dm%3Aalice%3Abob`."},{"name":"duration","in":"query","schema":{"type":"number"},"description":"seconds"},{"name":"Idempotency-Key","in":"header","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"audio/*":{"schema":{"type":"string","format":"binary"}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"$ref":"#/components/schemas/Message"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"},"413":{"$ref":"#/components/responses/E413"},"415":{"$ref":"#/components/responses/E415"},"423":{"$ref":"#/components/responses/E423"}},"operationId":"sendVoiceMessage"}},"/conversations/{id}/polls":{"post":{"tags":["Messages"],"summary":"Ask the chat a question (poll)","description":"Free: one choice, up to 6 options (admin setting), votes visible by name. **Premium**: `multi` (several choices), `anonymous` (no names), `closesAt` (closes by itself; 1 minute to 30 days ahead, ms since 1970) and up to 12 options (`402 premium_required` otherwise). Polls are readable by the server (not end-to-end encrypted). Everyone in the chat gets a `poll:update` event when votes change.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Conversation id. URL-encode it: direct chat ids contain colons, e.g. `dm%3Aalice%3Abob`."},{"name":"Idempotency-Key","in":"header","schema":{"type":"string","maxLength":64}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"question":{"type":"string","maxLength":200},"options":{"type":"array","items":{"type":"string","maxLength":80},"minItems":2,"maxItems":12},"multi":{"type":"boolean"},"anonymous":{"type":"boolean"},"closesAt":{"type":"integer"}},"required":["question","options"]},"example":{"question":"Where to eat?","options":["Pizza","Sushi","Thali"]}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"$ref":"#/components/schemas/Message"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"402":{"$ref":"#/components/responses/E402"},"403":{"$ref":"#/components/responses/E403"},"404":{"$ref":"#/components/responses/E404"},"423":{"$ref":"#/components/responses/E423"}},"operationId":"createPoll"}},"/polls/{id}/vote":{"put":{"tags":["Messages"],"summary":"Vote in a poll","description":"`options` is a list of option ids: one (or several in a multiple-choice poll). Voting again replaces the earlier vote; an empty list withdraws it. `409 poll_closed` once closed.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"options":{"type":"array","items":{"type":"string"}}},"required":["options"]},"example":{"options":["o1"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"$ref":"#/components/schemas/Message"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"},"409":{"$ref":"#/components/responses/E409"},"423":{"$ref":"#/components/responses/E423"}},"operationId":"votePoll"}},"/polls/{id}/close":{"post":{"tags":["Messages"],"summary":"Close a poll now","description":"Only the person who made it. The results stay visible.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"$ref":"#/components/schemas/Message"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"404":{"$ref":"#/components/responses/E404"},"423":{"$ref":"#/components/responses/E423"}},"operationId":"closePoll"}},"/conversations/{id}/events":{"post":{"tags":["Messages"],"summary":"Plan an event","description":"A card with title, date and time, place and description. Everyone in the chat can answer yes / maybe / no (the maker starts as \"yes\"), and people who said yes or maybe get a reminder (default 60 minutes before; also as a push notification). **Premium**: choose the reminder (`reminderMinutes` 0, 15, 30, 60, 120 or 1440) and `repeat` (`weekly` or `monthly`: after it is over the card moves to the next date and answers start again). Events are readable by the server (not end-to-end encrypted). Changes arrive as `event:update`, reminders as `event:reminder`.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Conversation id. URL-encode it: direct chat ids contain colons, e.g. `dm%3Aalice%3Abob`."},{"name":"Idempotency-Key","in":"header","schema":{"type":"string","maxLength":64}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","maxLength":100},"description":{"type":"string","maxLength":1000},"location":{"type":"string","maxLength":200},"startsAt":{"type":"integer","description":"ms since 1970, in the future, within 2 years"},"endsAt":{"type":"integer"},"reminderMinutes":{"type":"integer","enum":[0,15,30,60,120,1440]},"repeat":{"type":"string","enum":["none","weekly","monthly"]}},"required":["title","startsAt"]},"example":{"title":"Sunday trek","location":"Sinhagad","startsAt":1893456000000}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"$ref":"#/components/schemas/Message"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"402":{"$ref":"#/components/responses/E402"},"403":{"$ref":"#/components/responses/E403"},"404":{"$ref":"#/components/responses/E404"},"423":{"$ref":"#/components/responses/E423"}},"operationId":"createEvent"}},"/events/{id}/rsvp":{"put":{"tags":["Messages"],"summary":"Answer an event","description":"`status`: `yes`, `maybe`, `no`, or `null` to take the answer back. `409 event_canceled` after cancelling.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":["string","null"],"enum":["yes","maybe","no",null]}},"required":["status"]},"example":{"status":"yes"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"$ref":"#/components/schemas/Message"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"},"409":{"$ref":"#/components/responses/E409"},"423":{"$ref":"#/components/responses/E423"}},"operationId":"answerEvent"}},"/events/{id}":{"patch":{"tags":["Messages"],"summary":"Change an event","description":"Only the person who made it. Send any of `title`, `description`, `location`, `startsAt` (the end moves with it unless `endsAt` is sent), `endsAt`, `reminderMinutes`, `repeat`. A new start brings the reminder back.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"location":{"type":"string"},"startsAt":{"type":"integer"},"endsAt":{"type":["integer","null"]},"reminderMinutes":{"type":"integer"},"repeat":{"type":"string"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"$ref":"#/components/schemas/Message"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"402":{"$ref":"#/components/responses/E402"},"403":{"$ref":"#/components/responses/E403"},"404":{"$ref":"#/components/responses/E404"},"409":{"$ref":"#/components/responses/E409"},"423":{"$ref":"#/components/responses/E423"}},"operationId":"updateEvent"}},"/events/{id}/cancel":{"post":{"tags":["Messages"],"summary":"Cancel an event","description":"Only the person who made it. The card stays, marked as canceled.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"$ref":"#/components/schemas/Message"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"404":{"$ref":"#/components/responses/E404"},"423":{"$ref":"#/components/responses/E423"}},"operationId":"cancelEvent"}},"/conversations/{id}/location":{"post":{"tags":["Messages"],"summary":"Share a location (once or live)","description":"Share where you are: once (`live` 0 or absent), or **live** for 900 (15 min), 3600 (1 h) or 28800 (8 h) seconds. In an end-to-end encrypted chat send the position inside an `e2ee` envelope (payload `{\"t\":\"Location\",\"loc\":{\"lat\":…,\"lon\":…,\"acc\":…}}`); otherwise send `location`. A live share is moved with `PUT /live-locations/{id}` (at most every 4 seconds), ends by itself at `live.until` or with `DELETE /live-locations/{id}`, and the position is then erased from the message. One live share per person per chat: a new one ends the earlier one. No trail is kept: each update replaces the previous position.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Conversation id. URL-encode it: direct chat ids contain colons, e.g. `dm%3Aalice%3Abob`."},{"name":"Idempotency-Key","in":"header","schema":{"type":"string","maxLength":64}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"live":{"type":"integer","enum":[0,900,3600,28800]},"location":{"type":"object","properties":{"lat":{"type":"number"},"lon":{"type":"number"},"acc":{"type":"number"}},"required":["lat","lon"]},"e2ee":{"$ref":"#/components/schemas/Envelope"}}},"example":{"live":3600,"location":{"lat":12.9716,"lon":77.5946,"acc":20}}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"$ref":"#/components/schemas/Message"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"404":{"$ref":"#/components/responses/E404"},"409":{"$ref":"#/components/responses/E409"},"423":{"$ref":"#/components/responses/E423"}},"operationId":"shareLocation"}},"/live-locations/{id}":{"put":{"tags":["Messages"],"summary":"Update my live location","description":"`id` is the message id returned when the share started. Send the new `location` (or a new `e2ee` envelope). Answers `429` if sent more often than every 4 seconds and `409 live_ended` once the time is up.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"location":{"type":"object","properties":{"lat":{"type":"number"},"lon":{"type":"number"},"acc":{"type":"number"}},"required":["lat","lon"]},"e2ee":{"$ref":"#/components/schemas/Envelope"}}},"example":{"location":{"lat":12.9718,"lon":77.595,"acc":18}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"updatedAt":{"type":"integer"},"until":{"type":"integer"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"404":{"$ref":"#/components/responses/E404"},"409":{"$ref":"#/components/responses/E409"},"429":{"$ref":"#/components/responses/E429"}},"operationId":"updateLiveLocation"},"delete":{"tags":["Messages"],"summary":"Stop sharing my live location","description":"The position is erased and the message shows that the share ended.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ended":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"stopLiveLocation"}},"/me/photos":{"get":{"tags":["Profile photos"],"summary":"My profile photos","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfilePhotos"}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"listMyPhotos"},"post":{"tags":["Profile photos"],"summary":"Add a profile photo","description":"Send `image` (the picture, at most 1024 px and 400 KB) and `thumb` (a thumbnail of at most 256 px and 48 KB), both JPEG, PNG or WebP, as base64. The server reads the real type and size from the bytes. The app crops a square and sends 512 px and 128 px. **Limits**: 2 photos for a free account, 5 with Premium (`402 premium_required` when a free account is full but Premium would allow more; `409 limit_reached` at the Premium maximum). The first photo is shown as your picture. Photos are not end-to-end encrypted.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"image":{"type":"string","format":"byte"},"thumb":{"type":"string","format":"byte"}},"required":["image","thumb"]}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfilePhotos"}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"402":{"$ref":"#/components/responses/E402"},"403":{"$ref":"#/components/responses/E403"},"409":{"$ref":"#/components/responses/E409"},"413":{"$ref":"#/components/responses/E413"},"415":{"$ref":"#/components/responses/E415"}},"operationId":"addProfilePhoto"}},"/me/photos/{id}/main":{"put":{"tags":["Profile photos"],"summary":"Make a photo the one shown as my picture","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfilePhotos"}}}},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"setMainProfilePhoto"}},"/me/photos/{id}":{"delete":{"tags":["Profile photos"],"summary":"Remove one of my photos","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfilePhotos"}}}},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"deleteProfilePhoto"}},"/me/photo-rotation":{"put":{"tags":["Profile photos"],"summary":"Change my picture by itself every 7 days (Premium)","description":"Needs a full set of photos (the Premium limit, 5 by default): the next photo becomes your picture a week after switching on, then every 7 days. `402 premium_required` without Premium; `409 not_enough_photos` with fewer photos. If Premium ends, rotation stops until it returns.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"on":{"type":"boolean"}},"required":["on"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfilePhotos"}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"402":{"$ref":"#/components/responses/E402"},"409":{"$ref":"#/components/responses/E409"}},"operationId":"setPhotoRotation"}},"/me/login-pin":{"put":{"tags":["Account"],"summary":"Set, change or remove my login PIN","description":"A 6-digit PIN asked at every login (SMS code or browser passcode), so a stolen SMS code alone is not enough. `pin` is the new PIN, or `null` to remove it; `current` is the present PIN and is required to change or remove one. A forgotten PIN can only be removed by support.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pin":{"type":["string","null"],"pattern":"^\\d{6}$"},"current":{"type":"string"}},"required":["pin"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"hasPin":{"type":"boolean"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"429":{"$ref":"#/components/responses/E429"}},"operationId":"setLoginPin"}},"/me/sessions":{"get":{"tags":["Account"],"summary":"Devices logged in to my account","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"sessions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"via":{"type":"string","enum":["sms","link"]},"at":{"type":"integer"},"seen":{"type":"integer"},"current":{"type":"boolean"}}}},"hasPin":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"listSessions"},"delete":{"tags":["Account"],"summary":"Log out everywhere except this device","description":"Ends every other login session and closes their live connections. API keys are not affected (revoke them separately).","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ended":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"endOtherSessions"}},"/me/link-code":{"post":{"tags":["Account"],"summary":"Make a passcode to log in on a browser","description":"Only from the phone you signed in on with the SMS code (`403 primary_required` from a browser or API key). Returns a 6-digit `code` valid for 2 minutes, used once, with 5 wrong tries; 5 passcodes an hour. The browser then calls `POST /auth/login` with `{ phone, linkCode, clientType: \"web\" }`.","responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string"},"expiresAt":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"}},"operationId":"createLinkCode"}},"/me/linked-devices":{"get":{"tags":["Account"],"summary":"Browsers logged in with a passcode","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"devices":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"at":{"type":"integer"},"current":{"type":"boolean"}}}},"primary":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"listLinkedDevices"}},"/me/linked-devices/{id}":{"delete":{"tags":["Account"],"summary":"Sign a linked browser out","description":"The primary phone can sign out any browser; a browser only itself.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Done (no body)"},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"unlinkDevice"}},"/me/photo-privacy":{"put":{"tags":["Profile photos"],"summary":"Who may see my photos","description":"`everyone`, `contacts` (people I chat with, the default) or `nobody`. Blocked people never see them. For someone under 18 `everyone` is applied as `contacts`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"who":{"type":"string","enum":["everyone","contacts","nobody"]}},"required":["who"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfilePhotos"}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"}},"operationId":"setPhotoPrivacy"}},"/users/{username}/photos":{"get":{"tags":["Profile photos"],"summary":"Someone's photos, as far as I may see them","description":"An empty list when the person's privacy setting, a block, or the photo feature being off keeps you from seeing them. Download the pictures from `GET /media/{id}` with your token.","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"},"photos":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"image":{"type":"string"},"thumb":{"type":"string"},"ts":{"type":"integer"}}}},"canReport":{"type":"boolean"},"reasons":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"}}}}}}}}},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"getUserPhotos"}},"/users/{username}/photos/{id}/report":{"post":{"tags":["Profile photos"],"summary":"Report a profile photo","description":"`reason`: `nudity`, `child_safety`, `harassment`, `violence`, `impersonation` or `other`. One report per person per photo (`409` again), at most 20 a day. **Three different people, or one `child_safety` report, hide the photo from everyone but its owner** until an administrator has looked (they can remove it or restore it).","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","enum":["nudity","child_safety","harassment","violence","impersonation","other"]},"detail":{"type":"string","maxLength":300}},"required":["reason"]},"example":{"reason":"harassment"}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"reported":{"type":"boolean"},"hidden":{"type":"boolean"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"},"409":{"$ref":"#/components/responses/E409"},"429":{"$ref":"#/components/responses/E429"}},"operationId":"reportProfilePhoto"}},"/guardian/{token}":{"get":{"tags":["Parent or guardian"],"summary":"The parent or guardian page: a child and what the guardian allows","description":"For the parent or guardian of someone under 18. The `token` is the private link sent to them when the child signed up (the page at `/guardian/{token}` uses these calls). No account is needed: the link is the key. It stops working when it is replaced by a newer one, the account is deleted, or the child turns 18 (`404 not_found`). Guardians never see messages.","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuardianPage"}}}},"404":{"$ref":"#/components/responses/E404"},"429":{"$ref":"#/components/responses/E429"}},"operationId":"getGuardianPage"}},"/guardian/{token}/consent":{"post":{"tags":["Parent or guardian"],"summary":"Agree: allow the child to use the app","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"agree":{"const":true}},"required":["agree"]},"example":{"agree":true}}}},"security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuardianPage"}}}},"400":{"$ref":"#/components/responses/E400"},"404":{"$ref":"#/components/responses/E404"},"429":{"$ref":"#/components/responses/E429"}},"operationId":"guardianConsent"}},"/guardian/{token}/controls":{"put":{"tags":["Parent or guardian"],"summary":"Choose what the child can do","description":"Only after agreeing, and only while the child's account has **Premium** (`402 premium_required` otherwise: the safe defaults apply). Send any of `calls`, `location`, `discoverable`, `channels`. The child's apps update at once. Controls already set keep applying if Premium ends. Agreeing, disabling, enabling and deleting are always free.","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuardianControls"},"example":{"location":true}}}},"security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuardianPage"}}}},"400":{"$ref":"#/components/responses/E400"},"402":{"$ref":"#/components/responses/E402"},"404":{"$ref":"#/components/responses/E404"},"409":{"$ref":"#/components/responses/E409"},"429":{"$ref":"#/components/responses/E429"}},"operationId":"setGuardianControls"}},"/guardian/{token}/disable":{"post":{"tags":["Parent or guardian"],"summary":"Disable the account for now","description":"The child is signed out everywhere and cannot log in until it is enabled again.","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuardianPage"}}}},"404":{"$ref":"#/components/responses/E404"},"429":{"$ref":"#/components/responses/E429"}},"operationId":"guardianDisableAccount"}},"/guardian/{token}/enable":{"post":{"tags":["Parent or guardian"],"summary":"Enable the account again","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuardianPage"}}}},"404":{"$ref":"#/components/responses/E404"},"429":{"$ref":"#/components/responses/E429"}},"operationId":"guardianEnableAccount"}},"/guardian/{token}/delete":{"post":{"tags":["Parent or guardian"],"summary":"Delete the account","description":"The account is permanently disabled at once; the chats and personal data are erased within 24 hours.","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"confirmed":{"const":true}},"required":["confirmed"]},"example":{"confirmed":true}}}},"security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean"}}}}}},"400":{"$ref":"#/components/responses/E400"},"404":{"$ref":"#/components/responses/E404"},"429":{"$ref":"#/components/responses/E429"}},"operationId":"guardianDeleteAccount"}},"/me/guardian":{"get":{"tags":["Parent or guardian"],"summary":"My parent or guardian (under 18 only)","description":"`guardian` is null for adults. For a child: whether the parent or guardian has agreed (`state`), the masked contacts, and what they allow.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"guardian":{"oneOf":[{"$ref":"#/components/schemas/GuardianView"},{"type":"null"}]}}}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"getMyGuardian"},"put":{"tags":["Parent or guardian"],"summary":"Fix the guardian's details while waiting","description":"Only while the parent or guardian has not agreed yet. A new private link goes to the new contacts and the old link stops working.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuardianDetails"},"example":{"name":"Meera Iyer","relationship":"mother","phone":"+919876543210"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"guardian":{"$ref":"#/components/schemas/GuardianView"},"delivery":{"type":"object","properties":{"delivered":{"type":"boolean"},"to":{"type":"array","items":{"type":"string"}},"error":{"type":"string"}}}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"409":{"$ref":"#/components/responses/E409"},"429":{"$ref":"#/components/responses/E429"},"503":{"$ref":"#/components/responses/E503"}},"operationId":"updateMyGuardian"}},"/me/guardian/resend":{"post":{"tags":["Parent or guardian"],"summary":"Send the guardian their link again","description":"A new link; the old one stops working. At most one message per 30 seconds and 5 per hour to the same number or address.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"guardian":{"$ref":"#/components/schemas/GuardianView"},"delivery":{"type":"object","properties":{"delivered":{"type":"boolean"},"to":{"type":"array","items":{"type":"string"}},"error":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/E401"},"409":{"$ref":"#/components/responses/E409"},"429":{"$ref":"#/components/responses/E429"},"503":{"$ref":"#/components/responses/E503"}},"operationId":"resendGuardianLink"}},"/push":{"get":{"tags":["Notifications"],"summary":"Notification settings and the key to subscribe with","description":"Browsers: ask permission (from a button tap), subscribe with `applicationServerKey` = `publicKey`, then `PUT /push/subscription`. `enabled` is false when the administrator turned notifications off.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean"},"publicKey":{"type":["string","null"]},"devices":{"type":"integer"},"preferences":{"$ref":"#/components/schemas/PushPreferences"}}}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"getPushSettings"}},"/push/subscription":{"put":{"tags":["Notifications"],"summary":"Save this device's push subscription","description":"Send the object from `PushManager.subscribe()` (`toJSON()`). Only addresses of the browser makers' push services (Google, Mozilla, Apple, Microsoft) are accepted. Up to 8 devices per account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"subscription":{"type":"object","properties":{"endpoint":{"type":"string"},"keys":{"type":"object","properties":{"p256dh":{"type":"string"},"auth":{"type":"string"}},"required":["p256dh","auth"]}},"required":["endpoint","keys"]},"userAgent":{"type":"string"}},"required":["subscription"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"devices":{"type":"integer"},"preferences":{"$ref":"#/components/schemas/PushPreferences"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"}},"operationId":"savePushSubscription"}},"/push/unsubscribe":{"post":{"tags":["Notifications"],"summary":"Forget a device's push subscription","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"endpoint":{"type":"string"}},"required":["endpoint"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"devices":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"removePushSubscription"}},"/push/settings":{"patch":{"tags":["Notifications"],"summary":"Choose what to be notified about","description":"`messages`, `calls`, and `preview` (show the sender's name; off: just \"New message\"). Chats that are locked or hidden never show a name. Notifications never contain message text.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushPreferences"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"preferences":{"$ref":"#/components/schemas/PushPreferences"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"}},"operationId":"updatePushSettings"}},"/push/test":{"post":{"tags":["Notifications"],"summary":"Send a test notification to my devices","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"sent":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"409":{"$ref":"#/components/responses/E409"}},"operationId":"sendTestPush"}},"/conversations/{id}/read":{"post":{"tags":["Messages"],"summary":"Mark everything in the chat as read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Conversation id. URL-encode it: direct chat ids contain colons, e.g. `dm%3Aalice%3Abob`."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"updated":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"markConversationRead"}},"/conversations/{id}/typing":{"post":{"tags":["Messages"],"summary":"Show \"typing…\" / \"recording audio…\" to the others","description":"Clients hide the indicator after ~5 s, so repeat every few seconds while it applies.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Conversation id. URL-encode it: direct chat ids contain colons, e.g. `dm%3Aalice%3Abob`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"isTyping":{"type":"boolean","default":true},"activity":{"type":"string","enum":["typing","recording"]}}}}}},"responses":{"204":{"description":"Done (no body)"},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"sendTyping"}},"/conversations/{id}/disappearing":{"put":{"tags":["Conversations"],"summary":"Turn disappearing messages on/off for a chat","description":"New messages are deleted for everyone `seconds` after they are sent (60 … 31536000, i.e. 1 minute to 365 days; 0 or null = off). Any member can change it; everyone sees a notice.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Conversation id. URL-encode it: direct chat ids contain colons, e.g. `dm%3Aalice%3Abob`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"seconds":{"type":["integer","null"]}},"required":["seconds"]},"example":{"seconds":187200}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"conversation":{"$ref":"#/components/schemas/Conversation"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"},"423":{"$ref":"#/components/responses/E423"}},"operationId":"setDisappearing"}},"/conversations/{id}/call":{"get":{"tags":["Conversations"],"summary":"The call running in this chat, if any","description":"Placing and answering calls needs WebRTC in a client connected over Socket.IO (see README).","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Conversation id. URL-encode it: direct chat ids contain colons, e.g. `dm%3Aalice%3Abob`."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"call":{"oneOf":[{"$ref":"#/components/schemas/ActiveCall"},{"type":"null"}]}}}}}},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"getActiveCall"}},"/messages/{id}":{"delete":{"tags":["Messages"],"summary":"Delete my message for everyone","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Done (no body)"},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"deleteMessage"}},"/messages/{id}/star":{"put":{"tags":["Messages"],"summary":"Star a message (private to you)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"$ref":"#/components/schemas/Message"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"starMessage"},"delete":{"tags":["Messages"],"summary":"Unstar a message","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"$ref":"#/components/schemas/Message"}}}}}},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"unstarMessage"}},"/starred":{"get":{"tags":["Messages"],"summary":"My starred messages (newest first)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"messages":{"type":"array","items":{"$ref":"#/components/schemas/Message"}}}}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"listStarredMessages"}},"/media/{id}":{"get":{"tags":["Messages"],"summary":"Download voice-message audio or a status photo","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The file","content":{"audio/*":{},"image/*":{}}},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"},"423":{"$ref":"#/components/responses/E423"}},"operationId":"downloadMedia"}},"/chat-lock":{"get":{"tags":["Chat lock"],"summary":"Chat lock settings","description":"Lock chats with a secret code. A locked chat's messages are withheld (`locked: true`, no content; `423 chat_locked` for history, sending and its voice notes) until it is unlocked **in that session**.\n\nFor the REST API a session is the credential, optionally narrowed with an `X-App-Session` header; for the web app it is one page load. New logins start locked.\n\nWebhooks never receive the content of locked chats. 5 wrong codes lock out further tries, for 1 minute and then doubling up to an hour.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatLock"}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"getChatLock"}},"/chat-lock/code":{"put":{"tags":["Chat lock"],"summary":"Set or change the secret code","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","minLength":4,"maxLength":32},"currentCode":{"type":"string","description":"required when changing an existing code"}},"required":["code"]},"example":{"code":"2468"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatLock"}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"}},"operationId":"setChatLockCode"}},"/chat-lock/reset/otp":{"post":{"tags":["Chat lock"],"summary":"Forgot the code: get an SMS code on my mobile number","description":"Then call `POST /chat-lock/reset` with the `requestId`, the `code` and a `newCode`.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OtpSent"}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"429":{"$ref":"#/components/responses/E429"},"502":{"$ref":"#/components/responses/E502"}},"operationId":"requestChatLockResetOtp"}},"/chat-lock/reset":{"post":{"tags":["Chat lock"],"summary":"Forgot the code: set a new one","description":"Prove it is you with the SMS code from `POST /chat-lock/reset/otp` (`requestId` + `code`), or, for accounts created before mobile login, the account `password`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"requestId":{"type":"string"},"code":{"type":"string"},"password":{"type":"string"},"newCode":{"type":"string","minLength":4,"maxLength":32}},"required":["newCode"]},"example":{"requestId":"otp_77d0…","code":"391024","newCode":"2468"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatLock"}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"}},"operationId":"resetChatLockCode"}},"/chat-lock/relock":{"post":{"tags":["Chat lock"],"summary":"Lock again every chat unlocked in this session","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatLock"}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"relockChats"}},"/conversations/{id}/lock":{"put":{"tags":["Chat lock"],"summary":"Lock a chat","description":"Needs a secret code to have been set. Takes effect immediately in every session. Free accounts can lock up to 3 chats (`402 premium_required` after that).","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Conversation id. URL-encode it: direct chat ids contain colons, e.g. `dm%3Aalice%3Abob`."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatLock"}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"402":{"$ref":"#/components/responses/E402"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"lockChat"},"delete":{"tags":["Chat lock"],"summary":"Remove the lock from a chat","description":"Send the secret code or a fingerprint/face proof.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Conversation id. URL-encode it: direct chat ids contain colons, e.g. `dm%3Aalice%3Abob`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Proof"},"example":{"code":"1234"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatLock"}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"404":{"$ref":"#/components/responses/E404"},"429":{"$ref":"#/components/responses/E429"}},"operationId":"removeChatLock"}},"/conversations/{id}/unlock":{"post":{"tags":["Chat lock"],"summary":"Unlock a locked chat for this session","description":"Send the secret code or a fingerprint/face proof. For a hidden chat this reveals all hidden chats (like `POST /hidden-chats/reveal`).","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Conversation id. URL-encode it: direct chat ids contain colons, e.g. `dm%3Aalice%3Abob`."},{"name":"X-App-Session","in":"header","schema":{"type":"string"},"description":"optional: scope the unlock to one app session of this credential"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Proof"},"example":{"code":"1234"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"conversation":{"$ref":"#/components/schemas/Conversation"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"404":{"$ref":"#/components/responses/E404"},"429":{"$ref":"#/components/responses/E429"}},"operationId":"unlockChat"}},"/chat-lock/clear":{"post":{"tags":["Chat lock"],"summary":"Remove the lock from all locked chats","description":"Needs the secret code or a fingerprint/face proof. Returns how many chats were unlocked for good (`cleared`).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Proof"},"example":{"code":"1234"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"cleared":{"type":"integer"},"hasCode":{"type":"boolean"},"lockedChats":{"type":"array","items":{"type":"string"}},"hiddenCount":{"type":"integer"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"}},"operationId":"clearChatLocks"}},"/conversations/{id}/hide":{"put":{"tags":["Hide chat"],"summary":"Hide a chat","description":"Takes the chat out of the chat list. Free accounts can hide 1 chat (`402 premium_required` after that). Needs a secret code to have been set (400 `code_required`). A hidden chat sends nothing (messages, previews, typing, webhooks) to a session until it reveals hidden chats. Hiding replaces the chat's own lock. Takes effect in every session.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Conversation id. URL-encode it: direct chat ids contain colons, e.g. `dm%3Aalice%3Abob`."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatLock"}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"402":{"$ref":"#/components/responses/E402"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"hideChat"},"delete":{"tags":["Hide chat"],"summary":"Unhide a chat (put it back in the chat list)","description":"The session must have revealed hidden chats (423 `chat_locked` otherwise).","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Conversation id. URL-encode it: direct chat ids contain colons, e.g. `dm%3Aalice%3Abob`."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"conversation":{"$ref":"#/components/schemas/Conversation"}}}}}},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"},"423":{"$ref":"#/components/responses/E423"}},"operationId":"unhideChat"}},"/e2ee/devices":{"get":{"tags":["Encryption"],"summary":"My devices with encryption keys","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"devices":{"type":"array","items":{"$ref":"#/components/schemas/Device"}}}}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"listDevices"},"post":{"tags":["Encryption"],"summary":"Register a device public key","description":"Each device (browser, phone, bot) makes its own P-256 key pair and sends only the public key. Registering the same key again returns the same device. Up to 5 devices; a device can only read messages sent after it was registered.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":40},"publicKey":{"type":"string","description":"raw P-256 point (65 bytes), base64url"}},"required":["publicKey"]},"example":{"name":"Laptop","publicKey":"BAcS…"}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"device":{"$ref":"#/components/schemas/Device"},"devices":{"type":"array","items":{"$ref":"#/components/schemas/Device"}}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"409":{"$ref":"#/components/responses/E409"}},"operationId":"registerDevice"}},"/e2ee/devices/{id}":{"delete":{"tags":["Encryption"],"summary":"Remove a device","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"devices":{"type":"array","items":{"$ref":"#/components/schemas/Device"}}}}}}},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"removeDevice"}},"/conversations/{id}/keys":{"get":{"tags":["Encryption"],"summary":"Public keys of everyone in a chat","description":"The devices of every member (yours included): encrypt for all of them, and use them to check who sent a message. Anyone without a device has not set up encryption.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Conversation id. URL-encode it: direct chat ids contain colons, e.g. `dm%3Aalice%3Abob`."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"keys":{"type":"object","additionalProperties":{"type":"array","items":{"$ref":"#/components/schemas/Device"}}}}}}}},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"getConversationKeys"}},"/hidden-contacts/{username}":{"put":{"tags":["Hide chat"],"summary":"Hide the chat with a specific contact","description":"Like `PUT /conversations/{id}/hide`, but by username, and it works before the contact has ever messaged you: their first message stays hidden too. Returns the settings and the `convId`. Needs a secret code (400 `code_required`).","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatLock"}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"402":{"$ref":"#/components/responses/E402"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"hideContactChat"}},"/hidden-chats":{"get":{"tags":["Hide chat"],"summary":"My hidden chats","description":"Only after `POST /hidden-chats/reveal` in this session (423 `chat_locked` otherwise).","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"conversations":{"type":"array","items":{"$ref":"#/components/schemas/Conversation"}},"users":{"$ref":"#/components/schemas/Users"}}}}}},"401":{"$ref":"#/components/responses/E401"},"423":{"$ref":"#/components/responses/E423"}},"operationId":"listHiddenChats"}},"/hidden-chats/reveal":{"post":{"tags":["Hide chat"],"summary":"Open hidden chats for this session","description":"Prove it is you with the secret code or a fingerprint/face proof. Lasts for this app session (`X-App-Session`, or the credential). Wrong tries share the chat-lock limit (5, then a growing wait).","parameters":[{"name":"X-App-Session","in":"header","schema":{"type":"string"},"description":"optional: scope to one app session of this credential"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Proof"},"example":{"code":"1234"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"conversations":{"type":"array","items":{"$ref":"#/components/schemas/Conversation"}},"users":{"$ref":"#/components/schemas/Users"}}}}}},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"}},"operationId":"revealHiddenChats"}},"/hidden-chats/close":{"post":{"tags":["Hide chat"],"summary":"Close hidden chats again in this session","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatLock"}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"closeHiddenChats"}},"/hidden-chats/clear":{"post":{"tags":["Hide chat"],"summary":"Bring all hidden chats back to the chat list","description":"Needs the secret code or a fingerprint/face proof. Nothing is deleted; the chats simply stop being hidden. Returns how many (`cleared`).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Proof"},"example":{"code":"1234"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"cleared":{"type":"integer"},"hasCode":{"type":"boolean"},"lockedChats":{"type":"array","items":{"type":"string"}},"hiddenCount":{"type":"integer"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"}},"operationId":"clearHiddenChats"}},"/biometric/challenge":{"post":{"tags":["Hide chat"],"summary":"Get a challenge for fingerprint/face lock","description":"WebAuthn: pass `challenge` to `navigator.credentials.create` (to set up a device) or `.get` (to prove it is you, with `credentials` as `allowCredentials`). One use, valid for 2 minutes. The rpId is the site's host name.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"challenge":{"type":"string"},"userId":{"type":"string"},"credentials":{"type":"array","items":{"type":"string"}}}}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"biometricChallenge"}},"/biometric":{"post":{"tags":["Hide chat"],"summary":"Set up fingerprint/face lock on a device","description":"Confirm with the secret code, and send the result of `navigator.credentials.create` (platform authenticator, user verification required): `id`, `clientDataJSON`, `authenticatorData` (`response.getAuthenticatorData()`), `publicKey` (`response.getPublicKey()`) and `alg`, all base64url. Up to 5 devices.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string","description":"device name, e.g. \"My phone\""},"credential":{"type":"object","properties":{"id":{"type":"string"},"clientDataJSON":{"type":"string"},"authenticatorData":{"type":"string"},"publicKey":{"type":"string"},"alg":{"type":"integer","enum":[-7,-257]}},"required":["id","clientDataJSON","authenticatorData","publicKey","alg"]}},"required":["code","credential"]}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatLock"}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"}},"operationId":"addBiometric"}},"/biometric/{id}":{"delete":{"tags":["Hide chat"],"summary":"Remove fingerprint/face lock from a device","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"credential id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Proof"},"example":{"code":"1234"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatLock"}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"403":{"$ref":"#/components/responses/E403"},"429":{"$ref":"#/components/responses/E429"}},"operationId":"removeBiometric"}},"/statuses":{"get":{"tags":["Status"],"summary":"Status updates I can see","description":"Live (not expired) updates from people you have a one-to-one chat with, plus your own (with `views`). Oldest first.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"statuses":{"type":"array","items":{"$ref":"#/components/schemas/Status"}},"users":{"$ref":"#/components/schemas/Users"}}}}}},"401":{"$ref":"#/components/responses/E401"}},"operationId":"listStatuses"},"post":{"tags":["Status"],"summary":"Post a status update (visible for 24 hours)","description":"Text: JSON `{ \"type\": \"text\", \"text\": \"…\", \"bg\": \"#0670DE\" }`. Photo: send the image itself (`image/jpeg|png|webp|gif`, max 3 MB) with `?caption=`.","parameters":[{"name":"caption","in":"query","schema":{"type":"string"},"description":"photo updates only"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"const":"text"},"text":{"type":"string","maxLength":700},"bg":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"}},"required":["text"]},"example":{"type":"text","text":"Hello world","bg":"#25a3e0"}},"image/*":{"schema":{"type":"string","format":"binary"}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/Status"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"413":{"$ref":"#/components/responses/E413"},"415":{"$ref":"#/components/responses/E415"}},"operationId":"postStatus"}},"/statuses/{id}":{"delete":{"tags":["Status"],"summary":"Delete my status update","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Done (no body)"},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"deleteStatus"}},"/statuses/{id}/view":{"post":{"tags":["Status"],"summary":"Mark a status update as seen","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Done (no body)"},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"viewStatus"}},"/statuses/{id}/replies":{"post":{"tags":["Status"],"summary":"Reply to a status update","description":"Sent as a message in your one-to-one chat with its owner.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"}},"required":["text"]}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"$ref":"#/components/schemas/Message"},"conversation":{"$ref":"#/components/schemas/Conversation"}}}}}},"400":{"$ref":"#/components/responses/E400"},"401":{"$ref":"#/components/responses/E401"},"404":{"$ref":"#/components/responses/E404"}},"operationId":"replyToStatus"}}},"webhooks":{"message:new":{"post":{"summary":"\"message:new\" event","description":"Headers: `X-VartiQ-Event`, `X-VartiQ-Delivery`, `X-VartiQ-Timestamp` and `X-VartiQ-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + \".\" + raw body)>`. Reply 2xx within 5 s; failures are retried after 2 s, 10 s and 60 s.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Received"}},"operationId":"onMessageNew"}},"message:status":{"post":{"summary":"\"message:status\" event","description":"Headers: `X-VartiQ-Event`, `X-VartiQ-Delivery`, `X-VartiQ-Timestamp` and `X-VartiQ-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + \".\" + raw body)>`. Reply 2xx within 5 s; failures are retried after 2 s, 10 s and 60 s.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Received"}},"operationId":"onMessageStatus"}},"message:deleted":{"post":{"summary":"\"message:deleted\" event","description":"Headers: `X-VartiQ-Event`, `X-VartiQ-Delivery`, `X-VartiQ-Timestamp` and `X-VartiQ-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + \".\" + raw body)>`. Reply 2xx within 5 s; failures are retried after 2 s, 10 s and 60 s.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Received"}},"operationId":"onMessageDeleted"}},"message:starred":{"post":{"summary":"\"message:starred\" event","description":"Headers: `X-VartiQ-Event`, `X-VartiQ-Delivery`, `X-VartiQ-Timestamp` and `X-VartiQ-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + \".\" + raw body)>`. Reply 2xx within 5 s; failures are retried after 2 s, 10 s and 60 s.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Received"}},"operationId":"onMessageStarred"}},"conversation:new":{"post":{"summary":"\"conversation:new\" event","description":"Headers: `X-VartiQ-Event`, `X-VartiQ-Delivery`, `X-VartiQ-Timestamp` and `X-VartiQ-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + \".\" + raw body)>`. Reply 2xx within 5 s; failures are retried after 2 s, 10 s and 60 s.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Received"}},"operationId":"onConversationNew"}},"typing":{"post":{"summary":"\"typing\" event","description":"Headers: `X-VartiQ-Event`, `X-VartiQ-Delivery`, `X-VartiQ-Timestamp` and `X-VartiQ-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + \".\" + raw body)>`. Reply 2xx within 5 s; failures are retried after 2 s, 10 s and 60 s.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Received"}},"operationId":"onTyping"}},"presence":{"post":{"summary":"\"presence\" event","description":"Headers: `X-VartiQ-Event`, `X-VartiQ-Delivery`, `X-VartiQ-Timestamp` and `X-VartiQ-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + \".\" + raw body)>`. Reply 2xx within 5 s; failures are retried after 2 s, 10 s and 60 s.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Received"}},"operationId":"onPresence"}},"user:updated":{"post":{"summary":"\"user:updated\" event","description":"Headers: `X-VartiQ-Event`, `X-VartiQ-Delivery`, `X-VartiQ-Timestamp` and `X-VartiQ-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + \".\" + raw body)>`. Reply 2xx within 5 s; failures are retried after 2 s, 10 s and 60 s.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Received"}},"operationId":"onUserUpdated"}},"blocked:update":{"post":{"summary":"\"blocked:update\" event","description":"Headers: `X-VartiQ-Event`, `X-VartiQ-Delivery`, `X-VartiQ-Timestamp` and `X-VartiQ-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + \".\" + raw body)>`. Reply 2xx within 5 s; failures are retried after 2 s, 10 s and 60 s.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Received"}},"operationId":"onBlockedUpdate"}},"call:incoming":{"post":{"summary":"\"call:incoming\" event","description":"Headers: `X-VartiQ-Event`, `X-VartiQ-Delivery`, `X-VartiQ-Timestamp` and `X-VartiQ-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + \".\" + raw body)>`. Reply 2xx within 5 s; failures are retried after 2 s, 10 s and 60 s.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Received"}},"operationId":"onCallIncoming"}},"call:ended":{"post":{"summary":"\"call:ended\" event","description":"Headers: `X-VartiQ-Event`, `X-VartiQ-Delivery`, `X-VartiQ-Timestamp` and `X-VartiQ-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + \".\" + raw body)>`. Reply 2xx within 5 s; failures are retried after 2 s, 10 s and 60 s.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Received"}},"operationId":"onCallEnded"}},"call:state":{"post":{"summary":"\"call:state\" event","description":"Headers: `X-VartiQ-Event`, `X-VartiQ-Delivery`, `X-VartiQ-Timestamp` and `X-VartiQ-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + \".\" + raw body)>`. Reply 2xx within 5 s; failures are retried after 2 s, 10 s and 60 s.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Received"}},"operationId":"onCallState"}},"status:new":{"post":{"summary":"\"status:new\" event","description":"Headers: `X-VartiQ-Event`, `X-VartiQ-Delivery`, `X-VartiQ-Timestamp` and `X-VartiQ-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + \".\" + raw body)>`. Reply 2xx within 5 s; failures are retried after 2 s, 10 s and 60 s.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Received"}},"operationId":"onStatusNew"}},"status:viewed":{"post":{"summary":"\"status:viewed\" event","description":"Headers: `X-VartiQ-Event`, `X-VartiQ-Delivery`, `X-VartiQ-Timestamp` and `X-VartiQ-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + \".\" + raw body)>`. Reply 2xx within 5 s; failures are retried after 2 s, 10 s and 60 s.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Received"}},"operationId":"onStatusViewed"}},"status:deleted":{"post":{"summary":"\"status:deleted\" event","description":"Headers: `X-VartiQ-Event`, `X-VartiQ-Delivery`, `X-VartiQ-Timestamp` and `X-VartiQ-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + \".\" + raw body)>`. Reply 2xx within 5 s; failures are retried after 2 s, 10 s and 60 s.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Received"}},"operationId":"onStatusDeleted"}},"chatlock:update":{"post":{"summary":"\"chatlock:update\" event","description":"Headers: `X-VartiQ-Event`, `X-VartiQ-Delivery`, `X-VartiQ-Timestamp` and `X-VartiQ-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + \".\" + raw body)>`. Reply 2xx within 5 s; failures are retried after 2 s, 10 s and 60 s.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Received"}},"operationId":"onChatlockUpdate"}},"conversation:updated":{"post":{"summary":"\"conversation:updated\" event","description":"Headers: `X-VartiQ-Event`, `X-VartiQ-Delivery`, `X-VartiQ-Timestamp` and `X-VartiQ-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + \".\" + raw body)>`. Reply 2xx within 5 s; failures are retried after 2 s, 10 s and 60 s.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Received"}},"operationId":"onConversationUpdated"}},"message:expired":{"post":{"summary":"\"message:expired\" event","description":"Headers: `X-VartiQ-Event`, `X-VartiQ-Delivery`, `X-VartiQ-Timestamp` and `X-VartiQ-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + \".\" + raw body)>`. Reply 2xx within 5 s; failures are retried after 2 s, 10 s and 60 s.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Received"}},"operationId":"onMessageExpired"}},"location:update":{"post":{"summary":"\"location:update\" event","description":"Headers: `X-VartiQ-Event`, `X-VartiQ-Delivery`, `X-VartiQ-Timestamp` and `X-VartiQ-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + \".\" + raw body)>`. Reply 2xx within 5 s; failures are retried after 2 s, 10 s and 60 s.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Received"}},"operationId":"onLocationUpdate"}},"poll:update":{"post":{"summary":"\"poll:update\" event","description":"Headers: `X-VartiQ-Event`, `X-VartiQ-Delivery`, `X-VartiQ-Timestamp` and `X-VartiQ-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + \".\" + raw body)>`. Reply 2xx within 5 s; failures are retried after 2 s, 10 s and 60 s.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Received"}},"operationId":"onPollUpdate"}},"event:update":{"post":{"summary":"\"event:update\" event","description":"Headers: `X-VartiQ-Event`, `X-VartiQ-Delivery`, `X-VartiQ-Timestamp` and `X-VartiQ-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + \".\" + raw body)>`. Reply 2xx within 5 s; failures are retried after 2 s, 10 s and 60 s.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Received"}},"operationId":"onEventUpdate"}},"event:reminder":{"post":{"summary":"\"event:reminder\" event","description":"Headers: `X-VartiQ-Event`, `X-VartiQ-Delivery`, `X-VartiQ-Timestamp` and `X-VartiQ-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + \".\" + raw body)>`. Reply 2xx within 5 s; failures are retried after 2 s, 10 s and 60 s.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Received"}},"operationId":"onEventReminder"}},"ping":{"post":{"summary":"\"ping\" event","description":"Headers: `X-VartiQ-Event`, `X-VartiQ-Delivery`, `X-VartiQ-Timestamp` and `X-VartiQ-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + \".\" + raw body)>`. Reply 2xx within 5 s; failures are retried after 2 s, 10 s and 60 s.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Received"}},"operationId":"onPing"}}},"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","description":"Session token or API key (cak_…)"}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"not_found"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]},"User":{"type":"object","properties":{"username":{"type":"string","example":"alice"},"displayName":{"type":"string","example":"Alice Smith"},"about":{"type":"string"},"lastSeen":{"type":["integer","null"],"description":"ms since epoch; null when hidden by blocking"},"online":{"type":"boolean"},"business":{"$ref":"#/components/schemas/BusinessPublic"},"verified":{"type":"boolean","description":"present (true) when the person passed identity verification; absent = not verified"},"premium":{"type":"boolean","description":"present (true) while the person has an active Premium subscription"},"photo":{"type":"object","properties":{"id":{"type":"string","description":"media id of the picture (512 px)"},"thumb":{"type":"string","description":"media id of the thumbnail (128 px)"},"count":{"type":"integer","description":"how many photos you may see"}},"required":["id","thumb"]}},"required":["username","displayName"]},"DayHours":{"oneOf":[{"type":"object","properties":{"mode":{"const":"hours"},"open":{"type":"string","pattern":"^\\d{2}:\\d{2}$","example":"09:00"},"close":{"type":"string","example":"18:00"}},"required":["mode","open","close"]},{"type":"object","properties":{"mode":{"enum":["open24","closed"]}},"required":["mode"]}],"description":"A close time at or before the open time means open past midnight."},"BusinessHours":{"type":"object","properties":{"mon":{"$ref":"#/components/schemas/DayHours"},"tue":{"$ref":"#/components/schemas/DayHours"},"wed":{"$ref":"#/components/schemas/DayHours"},"thu":{"$ref":"#/components/schemas/DayHours"},"fri":{"$ref":"#/components/schemas/DayHours"},"sat":{"$ref":"#/components/schemas/DayHours"},"sun":{"$ref":"#/components/schemas/DayHours"}}},"BusinessPublic":{"type":"object","properties":{"category":{"type":"string","enum":["Automotive","Beauty, Spa & Salon","Clothing & Apparel","Education","Entertainment","Event Planning","Finance & Banking","Food & Grocery","Health & Medical","Hotel & Lodging","Non-profit","Professional Services","Public Service","Restaurant","Retail","Shopping","Travel & Transportation","Other"]},"description":{"type":"string","maxLength":512},"address":{"type":"string","maxLength":256},"email":{"type":"string","format":"email"},"website":{"type":"string","format":"uri"},"timezone":{"type":"string","example":"Asia/Kolkata","description":"IANA time zone the hours are in"},"hours":{"oneOf":[{"$ref":"#/components/schemas/BusinessHours"},{"type":"null"}]},"since":{"type":"integer"},"productCount":{"type":"integer","description":"products in the catalogue (see `GET /users/{username}/catalogue`)"}},"required":["category","timezone"]},"AutoReply":{"type":"object","properties":{"enabled":{"type":"boolean"},"text":{"type":"string","maxLength":1000}}},"Business":{"allOf":[{"$ref":"#/components/schemas/BusinessPublic"},{"type":"object","properties":{"greeting":{"$ref":"#/components/schemas/AutoReply","description":"Sent to customers who message you for the first time, or after 14 days without messages"},"away":{"$ref":"#/components/schemas/AutoReply","description":"Sent outside your business hours (at most once every 6 hours per chat)"}}}]},"Users":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/User"},"description":"Users keyed by username"},"Channel":{"type":"object","properties":{"id":{"type":"string"},"handle":{"type":"string","description":"public name, 3-30 letters, numbers or _"},"name":{"type":"string","maxLength":60},"description":{"type":"string","maxLength":300},"category":{"type":"string","maxLength":30},"owner":{"type":"string","description":"username of the owner"},"admins":{"type":"array","items":{"type":"string"}},"followers":{"type":"integer"},"following":{"type":"boolean"},"role":{"type":["string","null"],"enum":["owner","admin",null],"description":"your role in the channel"},"createdAt":{"type":"integer"},"lastPost":{"oneOf":[{"type":"object","properties":{"text":{"type":"string"},"ts":{"type":"integer"}}},{"type":"null"}]},"unread":{"type":"integer","description":"followers only: posts you have not read"}},"required":["id","handle","name","owner","followers","following"]},"ChannelPost":{"type":"object","properties":{"id":{"type":"string"},"channelId":{"type":"string"},"text":{"type":"string","maxLength":4000},"by":{"type":"string"},"ts":{"type":"integer"}},"required":["id","channelId","text","by","ts"]},"Product":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","maxLength":80},"description":{"type":"string","maxLength":1000},"price":{"type":["number","null"],"description":"in the catalogue currency, at most 2 decimals; null = price on request"},"unit":{"type":"string","maxLength":20,"description":"e.g. \"per kg\", \"per hour\""},"category":{"type":"string","maxLength":40},"sku":{"type":"string","maxLength":40},"link":{"type":"string","description":"where to buy or read more (http/https)"},"available":{"type":"boolean","description":"false = shown as out of stock"},"image":{"type":"string","description":"media id: download it from `GET /media/{id}` with your token"},"createdAt":{"type":"integer"},"updatedAt":{"type":"integer"}},"required":["id","name","price","available"]},"Catalogue":{"type":"object","properties":{"owner":{"type":"string"},"currency":{"type":"string","example":"INR"},"products":{"type":"array","items":{"$ref":"#/components/schemas/Product"}}},"required":["owner","currency","products"]},"Envelope":{"type":"object","properties":{"v":{"const":1},"sd":{"type":"string","description":"id of the sending device"},"iv":{"type":"string","description":"AES-GCM nonce for `ct` (12 bytes, base64url)"},"ct":{"type":"string","description":"AES-GCM ciphertext of the message JSON (base64url)"},"eph":{"type":"string","description":"one-time P-256 public key (65 bytes, base64url) used to wrap the content key"},"mi":{"type":"string","description":"voice messages: nonce for the encrypted audio"},"w":{"type":"object","additionalProperties":{"type":"object","properties":{"iv":{"type":"string"},"k":{"type":"string"}},"required":["iv","k"]},"description":"the content key, wrapped once per recipient device id (a recipient only receives its own devices)"}},"required":["v","sd","iv","ct","eph","w"]},"Device":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"publicKey":{"type":"string","description":"P-256 public key, 65 bytes uncompressed, base64url"},"keyId":{"type":"string","description":"short fingerprint of the key"},"createdAt":{"type":"integer"}},"required":["id","name","publicKey","keyId"]},"ProfilePhotos":{"type":"object","properties":{"photos":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"image":{"type":"string","description":"media id (download from `GET /media/{id}`)"},"thumb":{"type":"string"},"ts":{"type":"integer"},"main":{"type":"boolean","description":"the one shown as your picture"},"hidden":{"type":"boolean","description":"under review after reports: only you can see it"}}}},"limit":{"type":"integer","description":"how many you may keep now"},"freeLimit":{"type":"integer"},"premiumLimit":{"type":"integer"},"premium":{"type":"boolean"},"privacy":{"type":"string","enum":["everyone","contacts","nobody"]},"appliedPrivacy":{"type":"string","enum":["everyone","contacts","nobody"],"description":"what is really applied: under 18 never goes beyond `contacts`"},"minor":{"type":"boolean"},"reasons":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"}}}},"rotation":{"type":"object","properties":{"on":{"type":"boolean"},"active":{"type":"boolean","description":"on, Premium, and a full set of photos"},"full":{"type":"boolean"},"nextAt":{"type":["integer","null"],"description":"when the picture changes next"}}}}},"GuardianDetails":{"type":"object","properties":{"name":{"type":"string","maxLength":60},"relationship":{"type":"string","enum":["mother","father","guardian","other"]},"phone":{"type":"string","description":"international format; at least one of phone and email is required"},"email":{"type":"string","format":"email"}},"required":["name","relationship"]},"GuardianControls":{"type":"object","properties":{"calls":{"type":"boolean","description":"voice and video calls (default on)"},"location":{"type":"boolean","description":"sharing a location (default off)"},"discoverable":{"type":"boolean","description":"found by, and open to chats and groups from, people the child is not already chatting with (default off)"},"channels":{"type":"boolean","description":"public channels (default on)"}}},"GuardianPage":{"type":"object","properties":{"child":{"type":"object","properties":{"username":{"type":"string"},"displayName":{"type":"string"}}},"relationship":{"type":"string"},"state":{"type":"string","enum":["pending","active"]},"consentedAt":{"type":["integer","null"]},"disabled":{"type":"boolean","description":"the guardian has disabled the account"},"premium":{"type":"boolean","description":"the child's account has Premium, so the controls can be changed"},"controls":{"$ref":"#/components/schemas/GuardianControls"},"controlList":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"help":{"type":"string"}}}}}},"GuardianView":{"type":"object","properties":{"state":{"type":"string","enum":["pending","active"]},"premium":{"type":"boolean","description":"the account has Premium, so the parent or guardian can change the controls"},"name":{"type":["string","null"]},"contacts":{"type":"array","items":{"type":"string"}},"controls":{"$ref":"#/components/schemas/GuardianControls"},"controlList":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"help":{"type":"string"}}}}}},"PushPreferences":{"type":"object","properties":{"messages":{"type":"boolean"},"calls":{"type":"boolean"},"preview":{"type":"boolean","description":"show the sender's name"}}},"Message":{"type":"object","properties":{"id":{"type":"string"},"e2ee":{"oneOf":[{"$ref":"#/components/schemas/Envelope"}],"description":"end-to-end encrypted message: `text` is absent and the content is in this envelope. Messages without it (REST API, business auto replies, older messages) are not end-to-end encrypted."},"convId":{"type":"string"},"from":{"type":"string","description":"username of the sender"},"type":{"type":"string","enum":["text","voice","call","system","location","poll","event"],"description":"absent for text messages; `poll` / `event` = see `poll` / `event` (readable by the server, not end-to-end encrypted); `system` = a notice such as a timer change; `location` = a shared position (in an encrypted chat the coordinates are inside `e2ee` as `{\"t\":\"Location\",\"loc\":{\"lat\":…,\"lon\":…,\"acc\":…}}`)"},"expiresAt":{"type":"integer","description":"disappearing messages: when it will be deleted (ms since epoch)"},"auto":{"type":"string","enum":["greeting","away"],"description":"a business's automatic reply"},"system":{"type":"object","properties":{"event":{"const":"disappearing"},"seconds":{"type":["integer","null"]}}},"text":{"type":"string"},"ts":{"type":"integer","description":"ms since epoch"},"deliveredTo":{"type":"array","items":{"type":"string"}},"readBy":{"type":"array","items":{"type":"string","description":"for the sender: everybody who read it; for anyone else: only yourself, if you did"}},"clientId":{"type":"string","description":"the clientId / Idempotency-Key it was sent with"},"masked":{"type":"boolean","description":"recipients should hide the text until tapped"},"starred":{"type":"boolean","description":"starred by you"},"deleted":{"type":"boolean"},"poll":{"type":"object","properties":{"question":{"type":"string"},"options":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"text":{"type":"string"},"votes":{"type":"integer"},"voters":{"type":"array","items":{"type":"string"}}}}},"multi":{"type":"boolean"},"anonymous":{"type":"boolean","description":"no `voters` lists"},"closesAt":{"type":["integer","null"]},"closed":{"type":"boolean"},"closedAt":{"type":"integer"},"total":{"type":"integer","description":"people who voted"},"mine":{"type":"array","items":{"type":"string"}}}},"event":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"location":{"type":"string"},"startsAt":{"type":"integer"},"endsAt":{"type":["integer","null"]},"reminder":{"type":"integer","description":"minutes before the start"},"repeat":{"type":"string","enum":["none","weekly","monthly"]},"canceled":{"type":"boolean"},"counts":{"type":"object","properties":{"yes":{"type":"integer"},"maybe":{"type":"integer"},"no":{"type":"integer"}}},"going":{"type":"array","items":{"type":"string"}},"maybe":{"type":"array","items":{"type":"string"}},"notGoing":{"type":"array","items":{"type":"string"}},"mine":{"type":["string","null"],"enum":["yes","maybe","no",null]}}},"location":{"type":"object","properties":{"lat":{"type":"number"},"lon":{"type":"number"},"acc":{"type":"integer","description":"accuracy in metres"}},"required":["lat","lon"]},"live":{"type":"object","properties":{"startedAt":{"type":"integer"},"until":{"type":"integer","description":"when the share stops by itself"},"updatedAt":{"type":"integer"},"endedAt":{"type":"integer","description":"set when the share has ended; the position is then erased"}},"required":["startedAt","until"]},"voice":{"type":"object","properties":{"mediaId":{"type":"string"},"duration":{"type":"integer","description":"seconds"},"mime":{"type":"string"}}},"call":{"type":"object","properties":{"kind":{"type":"string","enum":["audio","video"]},"status":{"type":"string","enum":["completed","missed","declined"]},"duration":{"type":"integer"},"group":{"type":"boolean"},"joined":{"type":"array","items":{"type":"string"}}}},"statusReply":{"type":"object","properties":{"statusId":{"type":"string"},"owner":{"type":"string"},"type":{"type":"string"},"preview":{"type":"string"},"bg":{"type":"string"}}},"locked":{"type":"boolean","description":"true: the chat is locked and not unlocked in this session, so the content is withheld"}},"required":["id","convId","from","ts"]},"ActiveCall":{"type":"object","properties":{"callId":{"type":"string"},"kind":{"type":"string","enum":["audio","video"]},"group":{"type":"boolean"},"participants":{"type":"array","items":{"type":"string"}},"startedBy":{"type":"string"}}},"Conversation":{"type":"object","properties":{"id":{"type":"string","example":"dm:alice:bob"},"type":{"type":"string","enum":["direct","group"]},"name":{"type":"string","description":"groups only"},"members":{"type":"array","items":{"type":"string"}},"owner":{"type":"string"},"admins":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"integer"},"lastMessage":{"oneOf":[{"$ref":"#/components/schemas/Message"},{"type":"null"}]},"unread":{"type":"integer"},"translateTo":{"type":"string","description":"language code received messages are shown in (private to you)"},"activeCall":{"oneOf":[{"$ref":"#/components/schemas/ActiveCall"},{"type":"null"}]},"disappearAfter":{"type":"integer","description":"disappearing messages timer in seconds (absent = off)"},"disappearSetBy":{"type":"string"},"locked":{"type":"boolean","description":"you locked this chat with your secret code (or it is hidden and not yet revealed)"},"unlocked":{"type":"boolean","description":"locked chats only: unlocked in this session"},"hidden":{"type":"boolean","description":"the chat is hidden (left out of `GET /conversations`; `locked` is true until revealed)"}},"required":["id","type","members"]},"ChatLock":{"type":"object","properties":{"hasCode":{"type":"boolean","description":"a secret code has been set"},"lockedChats":{"type":"array","items":{"type":"string"}},"hiddenCount":{"type":"integer","description":"how many chats are hidden (their ids are only listed by `GET /hidden-chats` once revealed)"},"biometric":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"createdAt":{"type":"integer"}}}}},"required":["hasCode","lockedChats"]},"Proof":{"type":"object","properties":{"code":{"type":"string","description":"the secret code"},"biometric":{"type":"object","properties":{"id":{"type":"string","description":"credential id (base64url)"},"clientDataJSON":{"type":"string"},"authenticatorData":{"type":"string"},"signature":{"type":"string"}},"required":["id","clientDataJSON","authenticatorData","signature"]}}},"Status":{"type":"object","properties":{"id":{"type":"string"},"owner":{"type":"string"},"type":{"type":"string","enum":["text","image"]},"text":{"type":"string"},"bg":{"type":"string","example":"#0670DE"},"mediaId":{"type":"string","description":"image: download from /media/{id}"},"caption":{"type":"string"},"ts":{"type":"integer"},"expiresAt":{"type":"integer"},"viewed":{"type":"boolean","description":"someone else's update you've seen"},"views":{"type":"array","items":{"type":"object","properties":{"username":{"type":"string"},"ts":{"type":"integer"}}}}},"required":["id","owner","type","ts","expiresAt"]},"ApiKey":{"type":"object","properties":{"id":{"type":"string","example":"key_3f9a1c2b7d4e"},"name":{"type":"string"},"preview":{"type":"string","example":"cak_AbCd…wXyZ"},"createdAt":{"type":"integer"},"lastUsedAt":{"type":["integer","null"]}}},"Webhook":{"type":"object","properties":{"id":{"type":"string","example":"wh_8e1d2c3b4a5f"},"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string"}},"description":{"type":"string"},"active":{"type":"boolean"},"failures":{"type":"integer","description":"failed deliveries in a row (switched off at 20)"},"lastDelivery":{"type":["object","null"]},"createdAt":{"type":"integer"}}},"WebhookEvent":{"type":"object","properties":{"id":{"type":"string","example":"evt_9b8a7c6d5e4f3a2b"},"event":{"type":"string","enum":["message:new","message:status","message:deleted","message:starred","conversation:new","typing","presence","user:updated","blocked:update","call:incoming","call:ended","call:state","status:new","status:viewed","status:deleted","chatlock:update","conversation:updated","message:expired","location:update","poll:update","event:update","event:reminder","ping"]},"createdAt":{"type":"integer"},"user":{"type":"string","description":"the webhook owner this event is for"},"data":{"type":"object","description":"Same payload as the Socket.IO event of that name"}},"required":["id","event","createdAt","user","data"]},"Session":{"type":"object","properties":{"token":{"type":"string"},"user":{"$ref":"#/components/schemas/User"}},"required":["token","user"]},"ConsentAccept":{"type":"object","properties":{"terms":{"type":"boolean","description":"must be true: read and accepted the Terms of Use and Privacy Notice"},"version":{"type":"string","description":"the current `version` from `GET /legal` (sign-up only)"},"lang":{"type":"string","description":"optional: language code of the notice the person read (`GET /legal?lang=`, e.g. `hi`, `ta`); recorded with the acceptance"}},"required":["terms"]},"ConsentStatus":{"type":"object","properties":{"version":{"type":"string"},"upToDate":{"type":"boolean","description":"false: show the current texts and ask again (`POST /me/consent`); until then most calls answer `403 consent_required`"},"acceptedVersion":{"type":["string","null"]},"acceptedAt":{"type":["integer","null"]},"optional":{"type":"object","additionalProperties":{"type":"boolean"},"description":"`translation`, `kyc`: whether consent is given"},"summary":{"type":"object","description":"same as `GET /legal`"}}},"AppearancePrefs":{"type":"object","properties":{"mode":{"type":"string","enum":["auto","light","dark"]},"theme":{"type":"string","example":"ocean"},"appIcon":{"type":"string","example":"classic"},"avatarShape":{"type":"string","enum":["circle","rounded","square"]}}},"PaymentReceipt":{"type":"object","properties":{"id":{"type":"string","description":"the payment service's payment id (Razorpay: pay_…)"},"plan":{"type":"string"},"amount":{"type":"number"},"currency":{"type":"string"},"at":{"type":"integer","description":"when the payment was received (ms since epoch)"},"periodEnd":{"type":"integer","description":"Premium until (ms since epoch)"},"refunded":{"type":"number","description":"present when part or all of it was refunded"}},"required":["id","plan","amount","currency","at"]},"Subscription":{"type":"object","properties":{"perks":{"type":"array","items":{"type":"string"}},"premium":{"type":"boolean"},"plan":{"type":["string","null"],"enum":["daily","monthly","quarterly","halfyearly","yearly","granted",null]},"expiresAt":{"type":["integer","null"],"description":"ms since epoch"},"expired":{"type":"boolean","description":"had Premium before, and it has ended"},"limits":{"type":"object","properties":{"free":{"type":"object","properties":{"chatLocks":{"type":"integer"},"hiddenChats":{"type":"integer"}}},"premium":{"const":"unlimited"}}},"usage":{"type":"object","properties":{"chatLocks":{"type":"integer"},"hiddenChats":{"type":"integer"}}},"plans":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","enum":["daily","monthly","quarterly","halfyearly","yearly"]},"label":{"type":"string"},"price":{"type":"number"},"currency":{"type":"string"},"months":{"type":["integer","null"]},"perMonth":{"type":["number","null"],"description":"price per month"},"savePct":{"type":"integer","description":"saving compared with paying monthly"}}}},"provider":{"type":["string","null"],"description":"who takes the payment (`razorpay`, `relay`, `sandbox`, …)"},"providerLabel":{"type":["string","null"],"description":"its name for people, e.g. Razorpay"},"payments":{"type":"array","items":{"$ref":"#/components/schemas/PaymentReceipt"}},"available":{"type":"boolean","description":"payments are configured on this server"},"sandbox":{"type":"boolean","description":"development stand-in: orders are marked paid without payment"}},"required":["premium","limits","usage","plans"]},"OtpSent":{"type":"object","properties":{"requestId":{"type":"string","description":"send this back with the code"},"channel":{"type":"string","enum":["sms","email"]},"sentTo":{"type":"string","description":"masked destination, e.g. `+91•••••••210`"},"expiresIn":{"type":"integer","description":"seconds the code stays valid (300)"},"resendIn":{"type":"integer","description":"seconds before another code can be sent (30)"},"devCode":{"type":"string","description":"only when the server runs with OTP_DEV_CODES=1 (development)"}},"required":["requestId","channel","sentTo","expiresIn","resendIn"]},"OtpProof":{"type":"object","properties":{"requestId":{"type":"string"},"code":{"type":"string","pattern":"^\\d{6}$"}},"required":["requestId","code"]},"Contact":{"type":"object","properties":{"phone":{"type":["string","null"],"description":"E.164, e.g. +919876543210"},"email":{"type":["string","null"]}}}},"responses":{"E400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"E401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"E403":{"description":"Not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"E404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"E409":{"description":"Conflict (e.g. username taken, limit reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"E413":{"description":"Body too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"E415":{"description":"Unsupported media type","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"E423":{"description":"The chat is locked: unlock it with POST /conversations/{id}/unlock (code `chat_locked`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"E429":{"description":"Rate limited, or a code was requested too soon (`otp_too_soon`, `otp_limit`); see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"E503":{"description":"Not available: the service is in maintenance (`maintenance`) or the feature is not configured on this server (e.g. `kyc_unavailable`, `payments_unavailable`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"E426":{"description":"The app is older than the minimum version the administrator allows (`update_required`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"E402":{"description":"Premium required (`premium_required`: free accounts can lock 3 chats and hide 1) or payment not received yet (`payment_pending`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"E502":{"description":"The SMS/email gateway could not send the code (`otp_delivery_failed`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}