{"openapi":"3.1.0","info":{"title":"pixevel.chat API","version":"0.51.0","description":"The pixevel.chat API for a business's own server: push customers and their orders, build your own support pages on top of the desk (tickets API), and import history. Webhooks are what pixevel.chat sends back.\n\n**Authentication.** Every request carries the business's API key — `Authorization: Bearer sk_…` — made in the dashboard under «تنظیمات کسب‌وکار ← اتصال به سایت». The key names the business: no URL carries a business id. Call it from your server only, never from a browser.\n\n**Envelope.** A JSON answer is `{ \"ok\": true, \"data\": … }`; a refusal is `{ \"ok\": false, \"error\": { \"code\", \"message\" } }` with the HTTP status. Codes are stable; messages are English and may change. The event stream and attachment downloads are not wrapped.\n\n**Limits.** At most 600 requests a minute per key on the customers and import routes and 1200 on the conversation routes (two separate counters); over that is 429 RATE_LIMITED with Retry-After. A body is at most 1 MB (413 PAYLOAD_TOO_LARGE).\n\n**Idempotency.** Writes that create something take an optional `Idempotency-Key` (1–255 visible ASCII, kept 24 hours per business): a repeat answers the recorded result with `Idempotent-Replayed: true`; the same key on a different request is 422 IDEMPOTENCY_KEY_REUSED."},"servers":[{"url":"https://pixevel.chat"}],"security":[{"apiKey":[]}],"tags":[{"name":"Account","x-title":"کلید و کسب‌وکار","description":"Which business an API key belongs to."},{"name":"Customers","x-title":"مشتریان و سفارش‌ها","description":"Push your customers and their recent orders so the support desk sees who it is talking to."},{"name":"Tickets","x-title":"تیکت‌ها و گفت‌وگوها","description":"Build your own support pages: list, read, open, answer, resolve and rate a customer's conversations."},{"name":"Import","x-title":"انتقال تاریخچه","description":"Bring conversations over from a previous support system."},{"name":"Webhooks","x-title":"وب‌هوک‌ها","description":"What pixevel.chat POSTs to your server for each desk event you subscribe to."}],"paths":{"/api/v1/staff/{userId}/activity":{"get":{"summary":"Staff activity aggregates scoped by the business API key","description":"Public replies exclude deleted, internal and test traffic. Resolved counts distinct conversations transitioned by this actor. Open assignments are current. Completed answered calls credit the final handling agent. Dates are inclusive UTC days, maximum 93 days. No message bodies, customer data or recordings are returned.","parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"from","in":"query","required":true,"schema":{"type":"string","format":"date"}},{"name":"to","in":"query","required":true,"schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Staff activity","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["workspaceId","userId","from","to","replies","conversations","resolved","openAssigned","answeredCalls","talkSeconds"],"properties":{"workspaceId":{"type":"string"},"userId":{"type":"string"},"from":{"type":"string","format":"date"},"to":{"type":"string","format":"date"},"replies":{"type":"integer","minimum":0},"conversations":{"type":"integer","minimum":0},"resolved":{"type":"integer","minimum":0},"openAssigned":{"type":"integer","minimum":0},"answeredCalls":{"type":"integer","minimum":0},"talkSeconds":{"type":"integer","minimum":0}}}}}}}},"400":{"description":"VALIDATION (UUID or date window)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"UNAUTHORIZED (business API key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"RATE_LIMITED (more than 600 requests a minute on this API key's ingest budget)","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"operationId":"getStaffUserIdActivity","tags":["Customers"],"x-rate-limit":{"budget":"ingest","perMinute":600}}},"/api/v1/me":{"get":{"summary":"Which business the API key is for, and how many customers it has. `lastUsedAt` moves at most once a minute.","responses":{"200":{"description":"IngestMe","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/IngestMe"}}}}}},"401":{"description":"UNAUTHORIZED (wrong, rotated, or a deleted business's key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"RATE_LIMITED (more than 600 requests a minute on this API key's ingest budget)","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"operationId":"getMe","tags":["Account"],"x-rate-limit":{"budget":"ingest","perMinute":600}}},"/api/v1/contacts/batch":{"post":{"summary":"Up to 100 customers in one call, upserted like PUT. One result per row, in the order sent: a bad row (VALIDATION, CONTACT_LIMIT) fails only itself. A body that is not `{ contacts: [...] }`, holds more than 100 rows, or is over 1 MB fails as a whole.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchRequest"}}}},"responses":{"200":{"description":"BatchResponse","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/BatchResponse"}}}}}},"400":{"description":"VALIDATION (the body as a whole)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"PAYLOAD_TOO_LARGE","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"RATE_LIMITED (more than 600 requests a minute on this API key's ingest budget)","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"operationId":"postContactsBatch","tags":["Customers"],"x-rate-limit":{"budget":"ingest","perMinute":600}}},"/api/v1/contacts/{externalId}":{"put":{"summary":"Create or fully replace one customer (missing = empty). `orders` upserts those orders and never deletes others. Idempotent on externalId. Afterwards the business's unlinked conversations with this customer's phone (else email) are linked to them, at most 500.","parameters":[{"$ref":"#/components/parameters/ExternalId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactInput"}}}},"responses":{"200":{"description":"ContactResponse","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/ContactResponse"}}}}}},"400":{"description":"VALIDATION (the message names the field, like orders[2].amount)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"CONTACT_LIMIT (customers it already has can still be updated)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"PAYLOAD_TOO_LARGE","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"RATE_LIMITED (more than 600 requests a minute on this API key's ingest budget)","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"operationId":"putContactsExternalId","tags":["Customers"],"x-rate-limit":{"budget":"ingest","perMinute":600}},"delete":{"summary":"Remove one customer and their orders; their conversations are unlinked, never deleted — and one they started signed in leaves the widget for good (no visitor token reaches it again). The only way a customer leaves pixevel.chat.","parameters":[{"$ref":"#/components/parameters/ExternalId"}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/Deleted"}}}}}},"401":{"description":"UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"RATE_LIMITED (more than 600 requests a minute on this API key's ingest budget)","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"operationId":"deleteContactsExternalId","tags":["Customers"],"x-rate-limit":{"budget":"ingest","perMinute":600}}},"/api/v1/contacts/{externalId}/orders/{orderExternalId}":{"put":{"summary":"Create or fully replace one order of a customer. The customer keeps their 20 newest orders by placedAt (older ones are dropped in the same transaction). Idempotent.","parameters":[{"$ref":"#/components/parameters/ExternalId"},{"$ref":"#/components/parameters/OrderExternalId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderInput"}}}},"responses":{"200":{"description":"OrderResponse","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/OrderResponse"}}}}}},"400":{"description":"VALIDATION","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"NOT_FOUND (the customer has not been pushed)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"PAYLOAD_TOO_LARGE","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"RATE_LIMITED (more than 600 requests a minute on this API key's ingest budget)","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"operationId":"putContactsExternalIdOrdersOrderExternalId","tags":["Customers"],"x-rate-limit":{"budget":"ingest","perMinute":600}},"delete":{"summary":"Remove one order of a customer.","parameters":[{"$ref":"#/components/parameters/ExternalId"},{"$ref":"#/components/parameters/OrderExternalId"}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/Deleted"}}}}}},"401":{"description":"UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"RATE_LIMITED (more than 600 requests a minute on this API key's ingest budget)","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"operationId":"deleteContactsExternalIdOrdersOrderExternalId","tags":["Customers"],"x-rate-limit":{"budget":"ingest","perMinute":600}}},"/api/v1/orders/{orderExternalId}/conversations":{"get":{"summary":"Conversations about one order, newest activity first (API key).","parameters":[{"$ref":"#/components/parameters/OrderExternalId"}],"responses":{"200":{"description":"OrderConversationList","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/OrderConversationList"}}}}}},"400":{"description":"VALIDATION","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"RATE_LIMITED (more than 1200 requests a minute on this API key's conversations budget)","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"operationId":"getOrdersOrderExternalIdConversations","tags":["Tickets"],"x-rate-limit":{"budget":"conversations","perMinute":1200}}},"/api/v1/categories":{"get":{"summary":"The business's live categories, for the customer's form (tickets API).","responses":{"200":{"description":"TicketsCategoryList","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/TicketsCategoryList"}}}}}},"401":{"description":"UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"RATE_LIMITED (more than 1200 requests a minute on this API key's conversations budget)","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"operationId":"getCategories","tags":["Tickets"],"x-rate-limit":{"budget":"conversations","perMinute":1200}}},"/api/v1/contacts/{externalId}/conversations":{"get":{"summary":"The contact's conversations the tickets API shows them — signed in (the widget's, or this API's) or staff-linked and held by no widget visitor's token — the most recently active first. An unknown contact has an empty page.","parameters":[{"$ref":"#/components/parameters/ExternalId"},{"name":"kind","in":"query","required":false,"schema":{"type":"string","enum":["ticket","live"]}},{"name":"status","in":"query","required":false,"description":"Comma-separated: open, pending, resolved, closed.","schema":{"type":"string"}},{"name":"cursor","in":"query","required":false,"description":"A page's nextCursor.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":20}}],"responses":{"200":{"description":"TicketsConversationPage","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/TicketsConversationPage"}}}}}},"400":{"description":"VALIDATION (names the query parameter)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"RATE_LIMITED (more than 1200 requests a minute on this API key's conversations budget)","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"operationId":"getContactsExternalIdConversations","tags":["Tickets"],"x-rate-limit":{"budget":"conversations","perMinute":1200}},"post":{"summary":"Open a NEW conversation for the contact (live or ticket), signed in, channel api: auto-assigned by category, staff alerted, conversation.created emitted. An unknown externalId gets a contact made from `contact`. Answers its thread, marked read.","parameters":[{"$ref":"#/components/parameters/ExternalId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TicketsCreateRequest"}}}},"responses":{"201":{"description":"TicketsThread","headers":{"Idempotent-Replayed":{"description":"`true` on an answer rebuilt from a recorded Idempotency-Key.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/TicketsThread"}}}}}},"400":{"description":"VALIDATION (names the field: contact.phone, attachments: unknown upload id, categoryId: the category is archived, …)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"DESK_UNAVAILABLE (the business is off or suspended)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"NOT_FOUND (categoryId: no such category)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"CONTACT_LIMIT","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"PAYLOAD_TOO_LARGE","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"IDEMPOTENCY_KEY_REUSED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"RATE_LIMITED (more than 1200 requests a minute on this API key's conversations budget)","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"operationId":"postContactsExternalIdConversations","tags":["Tickets"],"x-rate-limit":{"budget":"conversations","perMinute":1200}}},"/api/v1/contacts/{externalId}/conversations/{number}":{"get":{"summary":"One conversation with its public messages (after `after`, a message id, for a poll), marked read up to the newest returned.","parameters":[{"$ref":"#/components/parameters/ExternalId"},{"$ref":"#/components/parameters/Number"},{"name":"after","in":"query","required":false,"description":"A message id; an unknown one returns every message.","schema":{"type":"string","maxLength":36}}],"responses":{"200":{"description":"TicketsThread","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/TicketsThread"}}}}}},"400":{"description":"VALIDATION (`after: …` — not text, or longer than an id)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"NOT_FOUND (another contact's or business's, hidden, or none)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"RATE_LIMITED (more than 1200 requests a minute on this API key's conversations budget)","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"operationId":"getContactsExternalIdConversationsNumber","tags":["Tickets"],"x-rate-limit":{"budget":"conversations","perMinute":1200}},"patch":{"summary":"The customer resolves (`resolved`: open/pending → resolved; resolved or closed: no-op) or reopens (`open`: resolved → open, staff alerted; open/pending: no-op; closed: CONVERSATION_CLOSED). Allowed while suspended.","parameters":[{"$ref":"#/components/parameters/ExternalId"},{"$ref":"#/components/parameters/Number"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TicketsStatusRequest"}}}},"responses":{"200":{"description":"TicketsConversationResult","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/TicketsConversationResult"}}}}}},"400":{"description":"VALIDATION","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"NOT_FOUND","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"CONVERSATION_CLOSED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"RATE_LIMITED (more than 1200 requests a minute on this API key's conversations budget)","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"operationId":"patchContactsExternalIdConversationsNumber","tags":["Tickets"],"x-rate-limit":{"budget":"conversations","perMinute":1200}}},"/api/v1/contacts/{externalId}/conversations/{number}/messages":{"post":{"summary":"The customer's message: the conversation needs staff again, a resolved one reopens. `message` is the one this request wrote.","parameters":[{"$ref":"#/components/parameters/ExternalId"},{"$ref":"#/components/parameters/Number"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TicketsReplyRequest"}}}},"responses":{"201":{"description":"TicketsReplyResult","headers":{"Idempotent-Replayed":{"description":"`true` on an answer rebuilt from a recorded Idempotency-Key.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/TicketsReplyResult"}}}}}},"400":{"description":"VALIDATION","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"DESK_UNAVAILABLE","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"NOT_FOUND","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"CONVERSATION_CLOSED (start a new one)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"PAYLOAD_TOO_LARGE","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"IDEMPOTENCY_KEY_REUSED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"RATE_LIMITED (more than 1200 requests a minute on this API key's conversations budget)","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"operationId":"postContactsExternalIdConversationsNumberMessages","tags":["Tickets"],"x-rate-limit":{"budget":"conversations","perMinute":1200}}},"/api/v1/contacts/{externalId}/stream":{"get":{"summary":"Live event stream of the conversations the tickets API shows this contact (never a note); an unknown contact NOT_FOUND","parameters":[{"$ref":"#/components/parameters/ExternalId"},{"$ref":"#/components/parameters/LastEventId"}],"responses":{"200":{"description":"text/event-stream: `retry: 3000`, `event: ready` ({resumed}), then `id: <boot>-<seq>` / `event: conversation.updated | message.created | message.updated` / `data: <json>` frames (CustomerStreamConversation, CustomerStreamMessage). message.created includes `message` (TicketsMessage); message.updated does not. `message.delta` (StreamDelta) and `message.aborted` (StreamAborted) have no id, are never replayed, and never carry a note. A `: ping` comment every stream.heartbeat. It ends on shutdown, after stream.max_age, or when it falls 64 events behind — reconnect with Last-Event-ID.","content":{"text/event-stream":{"schema":{"type":"string"}}}},"401":{"description":"UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"NOT_FOUND","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"RATE_LIMITED (more than 1200 requests a minute on this API key's conversations budget)","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"STREAM_UNAVAILABLE — shutting down or too many open streams; keep polling","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"operationId":"getContactsExternalIdStream","tags":["Tickets"],"x-rate-limit":{"budget":"conversations","perMinute":1200}}},"/api/v1/contacts/{externalId}/conversations/{number}/attachments/{aid}":{"get":{"summary":"A file of a public message in this conversation of this contact; anything else NOT_FOUND","parameters":[{"$ref":"#/components/parameters/ExternalId"},{"$ref":"#/components/parameters/Number"},{"name":"aid","in":"path","required":true,"description":"The attachment id.","schema":{"type":"string"}},{"name":"download","in":"query","required":false,"description":"1 = always an attachment disposition.","schema":{"type":"string"}}],"responses":{"200":{"description":"The bytes, Content-Type as sniffed, X-Content-Type-Options nosniff, a sandbox Content-Security-Policy, Cache-Control private no-store, Content-Disposition attachment (filename and RFC 5987 filename*) unless the type may show inline (JPEG, PNG, GIF, WebP, BMP, audio, MP4, WebM, PDF) and download=1 was not asked.","content":{"*/*":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"NOT_FOUND","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"RATE_LIMITED (more than 1200 requests a minute on this API key's conversations budget)","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"ATTACHMENTS_UNAVAILABLE","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"operationId":"getContactsExternalIdConversationsNumberAttachmentsAid","tags":["Tickets"],"x-rate-limit":{"budget":"conversations","perMinute":1200}}},"/api/v1/uploads":{"post":{"summary":"An upload for a customer's message through the tickets API; PUT the file to uploadUrl, then name uploadId in attachments","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadRequest"}}}},"responses":{"201":{"description":"Upload","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/Upload"}}}}}},"400":{"description":"VALIDATION","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"PLAN_LIMIT (limit storage: the plan's file storage is used)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"ATTACHMENT_TOO_LARGE","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"RATE_LIMITED (more than 1200 requests a minute on this API key's conversations budget)","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"ATTACHMENTS_UNAVAILABLE (media is not configured or did not answer)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"operationId":"postUploads","tags":["Tickets"],"x-rate-limit":{"budget":"conversations","perMinute":1200}}},"/api/v1/import/conversations":{"post":{"summary":"The import API (#189): up to 50 conversations of the business's old support history. Idempotent on sourceId — a re-sent conversation gains only the messages it lacks (by message sourceId) and its status is never changed again. History fires nothing: no webhook, staff alert, assistant turn, customer notice, channel delivery or live event. `dryRun` judges every row and writes nothing (not even a contact). One result per row, in the order sent: a bad row is `refused` alone.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportRequest"}}}},"responses":{"200":{"description":"ImportResponse","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/ImportResponse"}}}}}},"400":{"description":"VALIDATION (the body as a whole: not an object, no conversations, more than 50)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"PAYLOAD_TOO_LARGE","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"RATE_LIMITED (more than 600 requests a minute on this API key's ingest budget)","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"operationId":"postImportConversations","tags":["Import"],"x-rate-limit":{"budget":"ingest","perMinute":600}}},"/api/v1/import/aliases/lookup":{"post":{"summary":"What each old name (an import's sourceId, the old system's number) names now — the conversation it was merged into, when it was. Only the aliases that name a conversation still there are answered.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["aliases"],"properties":{"aliases":{"type":"array","maxItems":100,"items":{"type":"string","maxLength":120}}}}}}},"responses":{"200":{"description":"ImportAliasList","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/ImportAliasList"}}}}}},"400":{"description":"VALIDATION","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"RATE_LIMITED (more than 1200 requests a minute on this API key's conversations budget)","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"operationId":"postImportAliasesLookup","tags":["Import"],"x-rate-limit":{"budget":"conversations","perMinute":1200}}},"/api/v1/contacts/{externalId}/conversations/{number}/rating":{"post":{"summary":"Rate the conversation's current resolution — once per resolution (reopened and resolved again, it may be rated again). Emits conversation.rated. Allowed while suspended.","parameters":[{"$ref":"#/components/parameters/ExternalId"},{"$ref":"#/components/parameters/Number"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TicketsRatingRequest"}}}},"responses":{"201":{"description":"TicketsRatingResult","headers":{"Idempotent-Replayed":{"description":"`true` on an answer rebuilt from a recorded Idempotency-Key.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/TicketsRatingResult"}}}}}},"400":{"description":"VALIDATION (score: must be an integer from 1 to 5)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"NOT_FOUND","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"NOT_RESOLVED (open or pending) or ALREADY_RATED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"422":{"description":"IDEMPOTENCY_KEY_REUSED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"RATE_LIMITED (more than 1200 requests a minute on this API key's conversations budget)","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"operationId":"postContactsExternalIdConversationsNumberRating","tags":["Tickets"],"x-rate-limit":{"budget":"conversations","perMinute":1200}}}},"webhooks":{"deskEvent":{"post":{"summary":"What pixevel.chat sends to a business's endpoint for each event it subscribed to (and for «send test»). At least once: dedupe on X-Pixevel-Delivery. Answer 2xx fast and do the work afterwards.","description":"Signature: `X-Pixevel-Signature: v1=` + lowercase hex of HMAC-SHA256(secret, `<X-Pixevel-Timestamp>` + \".\" + the raw body), over the exact bytes received. Recompute it, compare in constant time, and refuse a timestamp more than 300 seconds from your clock. Each attempt is stamped and signed anew; the body and X-Pixevel-Delivery never change. Within one endpoint rows go out in order of when they are due; nothing is ordered across endpoints or per conversation — order by `occurredAt` / `conversation.updatedAt` when it matters. Only https on port 443, to a public address. Verification code in several languages: https://pixevel.chat/docs/webhooks","parameters":[{"name":"X-Pixevel-Event","in":"header","required":true,"schema":{"type":"string"}},{"name":"X-Pixevel-Delivery","in":"header","required":true,"description":"The delivery's id (UUIDv7): the same on every retry and redeliver.","schema":{"type":"string"}},{"name":"X-Pixevel-Timestamp","in":"header","required":true,"description":"Unix seconds of this attempt.","schema":{"type":"string"}},{"name":"X-Pixevel-Signature","in":"header","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookPayload"}}}},"responses":{"410":{"description":"Gone: the endpoint is switched off (disabledReason gone) and its other queued deliveries fail."},"2XX":{"description":"Delivered."},"default":{"description":"Anything else — a 3xx (never followed), any other 4xx, a 5xx, a timeout (10 s), no answer — is retried after 1m, 2m, 4m … then every 6 hours, for 24 hours from when the event was queued; one picked up past that still gets one last try. A 429, 502, 503, 504 or no answer holds the whole endpoint — its queued deliveries and every new one — for 1m, 2m, 4m … 6h, stepping with its run of such answers until it takes a delivery (or a «send test»), so a dead endpoint gets one request per step; when a step fails again, the deliveries whose 24 hours ran out during the hold fail with it, unsent. Among deliveries due together the least tried go first. A 500 or any other 5xx holds nothing. An answer from a URL the endpoint has since been moved off says nothing about the endpoint: only that delivery's own result is kept."}},"tags":["Webhooks"],"operationId":"deskEvent","security":[]}}},"components":{"schemas":{"IngestMe":{"type":"object","required":["workspace","contacts"],"properties":{"workspace":{"type":"object","required":["id","name","siteUrl","accountId"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"siteUrl":{"type":"string"},"accountId":{"type":"string","description":"The business's billing account (#190): the company's id, else the owner's user id. With id it addresses the dashboard: /dashboard/{accountId}/business/{id}/…."}}},"contacts":{"type":"integer"}}},"BatchRequest":{"type":"object","required":["contacts"],"properties":{"contacts":{"type":"array","maxItems":100,"description":"Each row is a ContactInput plus its `externalId`. Each is judged on its own.","items":{"allOf":[{"$ref":"#/components/schemas/ContactInput"},{"type":"object","required":["externalId"],"properties":{"externalId":{"type":"string","maxLength":120}}}]}}}},"BatchResponse":{"type":"object","required":["results"],"properties":{"results":{"type":"array","description":"One result per row sent, in the order sent. A bad row does not fail the others.","items":{"type":"object","required":["externalId","ok"],"properties":{"externalId":{"type":"string","description":"Empty when the row carried no readable one."},"ok":{"type":"boolean"},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}}}},"ContactInput":{"type":"object","description":"One customer. A PUT is a full replace of the customer's own fields (missing or null = empty). Lengths are counted in code points after trimming; a `null` reads as not sent.","properties":{"name":{"type":["string","null"],"maxLength":120},"phone":{"type":["string","null"],"description":"An Iranian mobile in any usual spelling; stored as 09…. One that is sent but is not a mobile number is VALIDATION."},"email":{"type":["string","null"],"maxLength":200,"description":"Lowercased on the way in."},"url":{"type":["string","null"],"maxLength":500,"description":"Absolute https URL."},"telegramId":{"type":["string","null"],"pattern":"^[0-9]{0,20}$","description":"The customer's Telegram user id as the business knows it (its own Telegram login). A Telegram chat with one of the business's bots is linked to the ONE contact with it, whichever came first (#158). Never shown back."},"flags":{"type":["array","null"],"maxItems":10,"description":"Full replace. Each flag is a key, a short label, and a tone. A reason is VALIDATION.","items":{"$ref":"#/components/schemas/ContactFlag"}},"attributes":{"type":["object","null"],"description":"Full replace. At most 30 facts; each value is at most 200 characters.","additionalProperties":{"type":"string","maxLength":200}},"orders":{"type":["array","null"],"maxItems":20,"description":"Upserts these orders; never deletes the ones it leaves out.","items":{"allOf":[{"$ref":"#/components/schemas/OrderInput"},{"type":"object","required":["externalId"],"properties":{"externalId":{"type":"string","maxLength":120}}}]}}}},"ContactResponse":{"type":"object","required":["contact"],"properties":{"contact":{"$ref":"#/components/schemas/Contact"}}},"Deleted":{"type":"object","required":["deleted"],"properties":{"deleted":{"type":"boolean","description":"false when there was nothing to delete (a delete is idempotent)."}}},"OrderInput":{"type":"object","required":["amount","currency","placedAt"],"properties":{"number":{"type":["string","null"],"maxLength":60,"description":"Defaults to the order's externalId (required when that is longer than 60)."},"title":{"type":["string","null"],"maxLength":200},"status":{"type":["string","null"],"maxLength":40},"amount":{"type":"string","pattern":"^\\d{1,15}(\\.\\d{1,4})?$","description":"A decimal string (the service also reads a bare JSON number by its digits; the public door and SDK take a string)."},"currency":{"type":"string","pattern":"^[A-Z]{3,8}$"},"url":{"type":["string","null"],"maxLength":500,"description":"Absolute https URL."},"placedAt":{"type":"string","format":"date-time","description":"ISO 8601 with a time zone."}}},"OrderResponse":{"type":"object","required":["order"],"properties":{"order":{"$ref":"#/components/schemas/Order"}}},"OrderConversationList":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/OrderConversation"}}}},"TicketsCategoryList":{"type":"object","required":["categories"],"properties":{"categories":{"type":"array","items":{"$ref":"#/components/schemas/TicketsCategory"},"description":"The live ones, in the dashboard's order."}}},"TicketsConversationPage":{"type":"object","required":["items","nextCursor"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/TicketsConversation"}},"nextCursor":{"type":["string","null"],"description":"Opaque; null on the last page."}}},"TicketsCreateRequest":{"type":"object","required":["kind"],"properties":{"kind":{"type":"string","enum":["ticket","live"]},"subject":{"type":"string","maxLength":200,"description":"Required for a ticket."},"body":{"type":"string","maxLength":5000},"categoryId":{"type":["string","null"]},"orderExternalId":{"type":["string","null"],"maxLength":120},"attachments":{"type":"array","maxItems":6,"items":{"type":"string"},"description":"Upload ids from POST /api/v1/uploads, not sent yet; with any, body may be empty."},"contact":{"type":"object","description":"Used only when the business has no contact with this externalId: the contact is made from it (counted against the contact ceiling). Judged like a push on every request — a malformed one is VALIDATION even for an existing contact, which is never changed by it.","properties":{"name":{"type":["string","null"],"maxLength":120},"phone":{"type":["string","null"]},"email":{"type":["string","null"],"maxLength":200}}}}},"TicketsThread":{"type":"object","required":["conversation","messages"],"properties":{"conversation":{"$ref":"#/components/schemas/TicketsConversation"},"messages":{"type":"array","items":{"$ref":"#/components/schemas/TicketsMessage"},"description":"Public messages, oldest first."}}},"TicketsStatusRequest":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["resolved","open"]}}},"TicketsConversationResult":{"type":"object","required":["conversation"],"properties":{"conversation":{"$ref":"#/components/schemas/TicketsConversation"}}},"TicketsReplyRequest":{"type":"object","properties":{"body":{"type":"string","maxLength":5000,"description":"Required unless attachments are sent."},"attachments":{"type":"array","maxItems":6,"items":{"type":"string"},"description":"Upload ids from POST /api/v1/uploads, not sent yet."}}},"TicketsReplyResult":{"type":"object","required":["conversation","message","messages"],"properties":{"conversation":{"$ref":"#/components/schemas/TicketsConversation"},"message":{"$ref":"#/components/schemas/TicketsMessage"},"messages":{"type":"array","items":{"$ref":"#/components/schemas/TicketsMessage"}}}},"UploadRequest":{"type":"object","description":"The file about to be PUT. contentType and sizeBytes only pick the cap (a video's or a file's) and refuse an obviously too large file early; the stored bytes decide the type and size.","properties":{"name":{"type":"string","description":"Kept (cleaned, at most 200 characters) as the file's display and download name."},"contentType":{"type":"string"},"sizeBytes":{"type":"integer","minimum":0},"durationMs":{"type":"integer","minimum":0,"maximum":120000,"description":"Optional client-measured recording length in milliseconds."}}},"Upload":{"type":"object","description":"PUT the file's bytes to uploadUrl (any Content-Type; at most maxBytes) before expiresAt (5 minutes), then name uploadId in a message's attachments within a day. Media answers 413 over the cap and 415 for a type it does not take.","required":["uploadId","uploadUrl","expiresAt","maxBytes"],"properties":{"uploadId":{"type":"string"},"uploadUrl":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"},"maxBytes":{"type":"integer"},"visitorToken":{"type":"string","description":"Widget only: present when this call minted the visitor's token."}}},"ImportRequest":{"type":"object","required":["conversations"],"properties":{"dryRun":{"type":"boolean","description":"Judge and plan only; nothing is written."},"conversations":{"type":"array","maxItems":50,"items":{"$ref":"#/components/schemas/ImportConversation"}}}},"ImportResponse":{"type":"object","required":["dryRun","results","staffPhones"],"properties":{"dryRun":{"type":"boolean"},"staffPhones":{"type":"object","required":["known","unknown"],"description":"The request's distinct staff phones: on the team (written as that person) or not («کارمند قبلی»). On a dry run too.","properties":{"known":{"type":"integer"},"unknown":{"type":"integer"}}},"results":{"type":"array","description":"One per row, in the order sent.","items":{"type":"object","required":["sourceId","status","conversationId","number","newMessages","error"],"properties":{"sourceId":{"type":"string"},"status":{"type":"string","enum":["created","appended","unchanged","skipped","refused"],"description":"On a dry run, what would happen. skipped: imported before, erased or merged away since."},"conversationId":{"type":"string","nullable":true},"number":{"type":"integer","nullable":true},"newMessages":{"type":"array","items":{"type":"string"},"description":"The source ids of the messages written (would be)."},"error":{"type":"object","nullable":true,"required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}}}},"ImportAliasList":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"type":"object","required":["alias","conversationId","number","kind","status","channel","contactExternalId","messageCount"],"properties":{"alias":{"type":"string"},"conversationId":{"type":"string"},"number":{"type":"integer"},"kind":{"type":"string","enum":["ticket","live"]},"status":{"type":"string","enum":["open","pending","resolved","closed"]},"channel":{"type":"string"},"contactExternalId":{"type":"string","nullable":true},"messageCount":{"type":"integer","description":"Every message, internal notes and deleted ones included."}}}}}},"TicketsRatingRequest":{"type":"object","required":["score"],"properties":{"score":{"type":"integer","minimum":1,"maximum":5},"comment":{"type":["string","null"],"maxLength":1000}}},"TicketsRatingResult":{"type":"object","required":["rating","conversation"],"properties":{"rating":{"$ref":"#/components/schemas/TicketsRating"},"conversation":{"$ref":"#/components/schemas/TicketsConversation"}}},"WebhookPayload":{"description":"The body of a delivery, byte for byte what X-Pixevel-Signature covers.","oneOf":[{"$ref":"#/components/schemas/WebhookConversationPayload"},{"$ref":"#/components/schemas/WebhookTestPayload"}],"discriminator":{"propertyName":"event","mapping":{"conversation.created":"#/components/schemas/WebhookConversationPayload","conversation.customer_replied":"#/components/schemas/WebhookConversationPayload","conversation.staff_replied":"#/components/schemas/WebhookConversationPayload","conversation.status_changed":"#/components/schemas/WebhookConversationPayload","conversation.assigned":"#/components/schemas/WebhookConversationPayload","conversation.rated":"#/components/schemas/WebhookConversationPayload","conversation.message_edited":"#/components/schemas/WebhookConversationPayload","conversation.message_deleted":"#/components/schemas/WebhookConversationPayload","conversation.merged":"#/components/schemas/WebhookConversationPayload","webhook.test":"#/components/schemas/WebhookTestPayload"}}},"Staff":{"type":"object","required":["userId","role"],"properties":{"userId":{"type":"string","format":"uuid"},"role":{"$ref":"#/components/schemas/Role"}}},"TicketsMessage":{"type":"object","description":"A public message; one staff deleted is never listed (0.32.0).","required":["replyTo","id","author","body","blocks","attachments","createdAt","editedAt"],"properties":{"replyTo":{"oneOf":[{"$ref":"#/components/schemas/MessageReplyTo"},{"type":"null"}]},"id":{"type":"string"},"author":{"type":"string","enum":["customer","agent","assistant","system"]},"sender":{"oneOf":[{"$ref":"#/components/schemas/WidgetTeamMember"},{"type":"null"}],"description":"Public first name and opaque avatar URL only; omitted without consent or for non-agent messages."},"body":{"type":"string"},"blocks":{"type":"array","items":{"$ref":"#/components/schemas/MessageBlock"},"description":"Rich staff/assistant content; [] for customer messages or none."},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/Attachment"}},"createdAt":{"type":"string","format":"date-time"},"editedAt":{"type":["string","null"],"format":"date-time","description":"When staff last edited it (0.32.0); null when never."}}},"StreamDelta":{"type":"object","description":"message.delta, the same JSON on every audience that may see it. A signal: no id, never replayed, never stored, never sent to a channel or webhook. seq is 1, 2, 3… for this messageId and is contiguous. chunk is decoded reply text, never the model's JSON. A visitor hears it only on that conversation's own stream. A note's delta never leaves the staff stream.","required":["conversationId","messageId","seq","chunk"],"properties":{"conversationId":{"type":"string"},"messageId":{"type":"string"},"seq":{"type":"integer","minimum":1},"chunk":{"type":"string"}}},"StreamAborted":{"type":"object","description":"message.aborted, the same JSON on every audience that may see it. A signal: no id, never replayed, never stored. That reply will not be stored. The stored reply, when one is written, is an ordinary message.created. A note's abort never leaves the staff stream.","required":["conversationId","messageId"],"properties":{"conversationId":{"type":"string"},"messageId":{"type":"string"}}},"CustomerStreamConversation":{"type":"object","description":"A business server's conversation.updated; number is what the tickets API's routes take.","required":["id","number","status"],"properties":{"id":{"type":"string"},"number":{"type":"integer"},"status":{"type":"string","enum":["open","pending","resolved","closed"]}}},"CustomerStreamMessage":{"type":"object","description":"A business server's message.created or message.updated (0.32.0 — read the whole thread again; never a note). `message` is present on every message.created (TicketsMessage) and absent on message.updated.","required":["conversationId","number","messageId","author"],"properties":{"conversationId":{"type":"string"},"number":{"type":"integer"},"messageId":{"type":"string"},"author":{"type":"string","enum":["customer","agent","assistant","system"]},"message":{"description":"The tickets-API public message. Present on every message.created and absent on message.updated. Never a note.","$ref":"#/components/schemas/TicketsMessage"}}},"ContactFlag":{"type":"object","required":["key","label","tone"],"properties":{"key":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,39}$","description":"A short key such as aml. Not a sentence."},"label":{"type":"string","maxLength":80,"description":"What staff read. Not a case narrative."},"tone":{"type":"string","enum":["info","warning","danger"]}}},"Contact":{"type":"object","description":"One customer of a business, as the business's server pushed it. Every field is a string; \"\" when there is none.","required":["id","externalId","name","phone","email","url","createdAt","updatedAt"],"properties":{"id":{"type":"string"},"externalId":{"type":"string","description":"The business's own id for the customer; unique per business."},"name":{"type":"string"},"phone":{"type":"string","description":"09… (11 digits), or empty."},"email":{"type":"string","description":"Lowercase, or empty."},"url":{"type":"string","description":"This customer in the business's own admin (absolute https), or empty."},"flags":{"type":"array","maxItems":10,"description":"Labels the business pushed. Omitted when none. A flag has no reason.","items":{"$ref":"#/components/schemas/ContactFlag"}},"attributes":{"type":"object","additionalProperties":{"type":"string"},"description":"Short facts the business pushed. Omitted when none."},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time","description":"When the business last pushed the customer."}}},"Order":{"type":"object","description":"One order of a contact as the business describes it — a summary, no line items. A contact keeps its 20 newest by placedAt.","required":["id","externalId","number","title","status","amount","currency","url","placedAt","createdAt","updatedAt"],"properties":{"id":{"type":"string"},"externalId":{"type":"string"},"number":{"type":"string","description":"What the business calls it on screen; defaults to externalId."},"title":{"type":"string"},"status":{"type":"string","description":"The business's own label, free text."},"amount":{"type":"string","pattern":"^\\d{1,15}(\\.\\d{1,4})?$","description":"A decimal string in `currency`."},"currency":{"type":"string","pattern":"^[A-Z]{3,8}$","description":"IRT, IRR, USD, USDT, …"},"url":{"type":"string","description":"This order in the business's own admin (absolute https), or empty."},"placedAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"OrderConversation":{"type":"object","description":"One conversation about an order, for the business's admin.","required":["id","number","kind","status","subject","orderExternalId","contactId","contactExternalId","assistant","lastActivityAt","createdAt"],"properties":{"id":{"type":"string"},"number":{"type":"integer"},"kind":{"type":"string","enum":["live","ticket"]},"status":{"type":"string","enum":["open","pending","resolved","closed"]},"subject":{"type":"string"},"orderExternalId":{"type":"string"},"contactId":{"type":["string","null"]},"contactExternalId":{"type":["string","null"],"description":"The business's own id for the customer (#190), for reading the thread through the customer's routes; null with no contact or one staff made at the desk."},"assistant":{"description":"The AI assistant on it (#190); null when it never answered.","oneOf":[{"$ref":"#/components/schemas/ConversationAssistant"},{"type":"null"}]},"lastActivityAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"}}},"TicketsCategory":{"type":"object","required":["id","name","description"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"}}},"TicketsConversation":{"type":"object","description":"A conversation as the customer's support UI shows it. Never a staff id, an internal note, the assignee, the priority, or the customer's phone and email.","required":["id","number","kind","status","channel","openedByStaff","subject","category","orderExternalId","lastMessage","lastAuthor","messageCount","unreadCount","awaitingCustomer","canRate","rating","assistant","lastActivityAt","resolvedAt","createdAt"],"properties":{"id":{"type":"string"},"number":{"type":"integer"},"kind":{"type":"string","enum":["live","ticket"]},"status":{"type":"string","enum":["open","pending","resolved","closed"]},"channel":{"type":"string","enum":["widget","phone","email","manual","api"]},"openedByStaff":{"type":"boolean","description":"Staff opened it at the desk (channel phone, email or manual)."},"subject":{"type":"string"},"category":{"type":["object","null"],"required":["id","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"}}},"orderExternalId":{"type":["string","null"],"description":"The business's order it is about, as sent."},"lastMessage":{"type":"string"},"lastAuthor":{"type":"string","enum":["customer","agent","assistant","system"]},"messageCount":{"type":"integer","description":"Public messages."},"unreadCount":{"type":"integer","description":"Staff / assistant messages after the customer's last read through this API."},"awaitingCustomer":{"type":"boolean","description":"status pending: staff answered."},"canRate":{"type":"boolean","description":"Resolved or closed, and that resolution not rated yet."},"rating":{"oneOf":[{"$ref":"#/components/schemas/TicketsRating"},{"type":"null"}]},"assistant":{"description":"The AI assistant on it (#190), for the business's own staff — its state, replies and why it handed off; null when it never answered.","oneOf":[{"$ref":"#/components/schemas/ConversationAssistant"},{"type":"null"}]},"lastActivityAt":{"type":"string","format":"date-time"},"resolvedAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"}}},"ImportConversation":{"type":"object","required":["sourceId","kind","channel","status","createdAt","messages"],"properties":{"sourceId":{"type":"string","maxLength":120,"description":"The old system's id; the import's idempotency key and an alias."},"aliases":{"type":"array","maxItems":5,"items":{"type":"string","maxLength":120},"description":"Other old names («PX-123»)."},"kind":{"type":"string","enum":["ticket","live"]},"channel":{"type":"string","enum":["widget","api","email","telegram","bale","phone","manual"]},"status":{"type":"string","enum":["open","pending","resolved","closed"]},"subject":{"type":"string","maxLength":200},"createdAt":{"type":"string","format":"date-time"},"resolvedAt":{"type":"string","format":"date-time","description":"Defaults to the last activity for a resolved or closed one."},"orderExternalId":{"type":"string","maxLength":120},"assigneePhone":{"type":"string","description":"A staff member's mobile; anyone not on the team is left unassigned."},"contact":{"type":"object","required":["externalId"],"description":"The customer by the business's own id — made from the stub when missing, never changed. A ticket with one is shown to that customer through the tickets API; a live chat never is.","properties":{"externalId":{"type":"string","maxLength":120},"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"}}},"customer":{"type":"object","description":"A guest's own details (no contact).","properties":{"name":{"type":"string","maxLength":80},"phone":{"type":"string"},"email":{"type":"string","maxLength":200}}},"channelIdentity":{"type":"object","required":["channel","externalChatId"],"description":"Live only: the chat the conversation continues on — the customer's next message there joins it while it is not closed.","properties":{"channel":{"type":"string","enum":["telegram","bale"]},"externalChatId":{"type":"string","maxLength":320},"externalUserId":{"type":"string","maxLength":320,"description":"Defaults to externalChatId (a one-to-one chat)."},"username":{"type":"string","maxLength":64},"connectionId":{"type":"string","description":"One of the business's connections on that channel; none = its own channel (the API key)."}}},"messages":{"type":"array","items":{"type":"object","required":["sourceId","author","createdAt"],"properties":{"sourceId":{"type":"string","maxLength":120},"author":{"type":"string","enum":["customer","agent","system"]},"staffPhone":{"type":"string","description":"An agent's mobile; anyone not on the team is written with no author id («کارمند قبلی»)."},"body":{"type":"string","maxLength":5000,"description":"Required unless the message carries attachments."},"internal":{"type":"boolean","description":"A staff-only note (agent only)."},"createdAt":{"type":"string","format":"date-time"},"editedAt":{"type":"string","format":"date-time"},"deletedAt":{"type":"string","format":"date-time"},"attachments":{"type":"array","maxItems":6,"items":{"type":"string"},"description":"Upload ids (POST /api/v1/uploads)."},"revisions":{"type":"array","maxItems":50,"items":{"type":"object","required":["body","createdAt"],"properties":{"body":{"type":"string","maxLength":5000},"staffPhone":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}}}}}},"rating":{"type":"object","required":["score","createdAt"],"properties":{"score":{"type":"integer","minimum":1,"maximum":5},"comment":{"type":"string","maxLength":1000},"createdAt":{"type":"string","format":"date-time"}}}}},"TicketsRating":{"type":"object","required":["id","score","comment","createdAt"],"properties":{"id":{"type":"string"},"score":{"type":"integer","minimum":1,"maximum":5},"comment":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"WebhookConversationPayload":{"type":"object","description":"Every conversation event. `message` is set on created (the first message, when public) and the two *_replied events; `change` on status_changed and assigned; each null otherwise. conversation.rated has both null and adds `rating` — the last key, absent from every other event.","required":["event","occurredAt","workspaceId","conversation","message","change"],"properties":{"event":{"type":"string","enum":["conversation.created","conversation.customer_replied","conversation.staff_replied","conversation.status_changed","conversation.assigned","conversation.rated","conversation.message_edited","conversation.message_deleted","conversation.merged"]},"occurredAt":{"type":"string","format":"date-time","description":"UTC, milliseconds."},"workspaceId":{"type":"string"},"conversation":{"$ref":"#/components/schemas/WebhookConversation"},"message":{"oneOf":[{"$ref":"#/components/schemas/WebhookMessage"},{"type":"null"}]},"change":{"oneOf":[{"$ref":"#/components/schemas/WebhookChange"},{"type":"null"}]},"rating":{"$ref":"#/components/schemas/WebhookRating","description":"conversation.rated only."}}},"WebhookTestPayload":{"type":"object","required":["event","occurredAt","workspaceId","webhookId"],"properties":{"event":{"type":"string","enum":["webhook.test"]},"occurredAt":{"type":"string","format":"date-time"},"workspaceId":{"type":"string"},"webhookId":{"type":"string"}}},"Role":{"type":"string","enum":["OWNER","ADMIN","MEMBER"],"description":"The VIEWER's role — OWNER on their own workspace, OWNER / ADMIN as a company's representative, ADMIN / MEMBER as a member of the business's team."},"MessageReplyTo":{"type":"object","required":["id","author","excerpt","deleted"],"properties":{"id":{"type":"string"},"author":{"type":"string","enum":["customer","agent","assistant","system"]},"excerpt":{"type":"string","maxLength":120},"deleted":{"type":"boolean"}}},"WidgetTeamMember":{"type":"object","additionalProperties":false,"required":["firstName","avatarUrl"],"properties":{"firstName":{"type":"string"},"avatarUrl":{"type":"string"}}},"MessageBlock":{"description":"One rich block next to a message's body: a buttons row (at most one, and it is last), a card, a notice, or a copy block. Button and card URLs are https. At most four cards, four notices and four copies.","oneOf":[{"type":"object","required":["type","items"],"properties":{"type":{"type":"string","enum":["buttons"]},"items":{"type":"array","minItems":1,"maxItems":6,"items":{"$ref":"#/components/schemas/MessageButton"}},"rows":{"type":"array","maxItems":6,"items":{"type":"integer","minimum":1,"maximum":6},"description":"Per-row button counts summing to items.length. Absent or empty means one button per row. All-1 layouts are omitted in canonical output. No per-row cap beyond the total of 6 buttons."}}},{"type":"object","required":["type","title"],"properties":{"type":{"type":"string","enum":["card"]},"title":{"type":"string","minLength":1,"maxLength":80},"subtitle":{"type":"string","minLength":1,"maxLength":120},"url":{"type":"string","maxLength":500},"imageAttachmentId":{"type":"string","minLength":1,"maxLength":64}}},{"type":"object","required":["type","tone","text"],"properties":{"type":{"type":"string","enum":["notice"]},"tone":{"type":"string","enum":["info","success","warning","danger"]},"text":{"type":"string","minLength":1,"maxLength":300}}},{"type":"object","required":["type","value"],"properties":{"type":{"type":"string","enum":["copy"]},"label":{"type":"string","minLength":1,"maxLength":40},"value":{"type":"string","minLength":1,"maxLength":200}}},{"type":"object","description":"A phone call (0.43.0) — only on the internal system message the calls write; never accepted from a sender. It mirrors the call's status as it moves; read the call itself for times and recordings.","required":["type","callId","direction","number","status"],"properties":{"type":{"type":"string","enum":["call"]},"callId":{"type":"string"},"direction":{"type":"string","enum":["outbound","inbound"],"description":"inbound since 0.45.0."},"number":{"type":"string","description":"The customer's number: dialled (09…), or an inbound caller's — empty when they withheld it."},"status":{"$ref":"#/components/schemas/CallStatus"}}}]},"Attachment":{"type":"object","description":"One file of a message. contentType is sniffed from the stored bytes, never the uploader's claim; kind follows from it. Download through the surface's attachments route — never by id alone.","required":["id","name","contentType","kind","sizeBytes","width","height","durationMs"],"properties":{"id":{"type":"string"},"name":{"type":"string","maxLength":200,"description":"The uploader's file name (display and download name)."},"contentType":{"type":"string"},"kind":{"type":"string","enum":["image","audio","video","file"]},"sizeBytes":{"type":"integer"},"width":{"type":["integer","null"],"description":"An image's width when it could be read."},"height":{"type":["integer","null"]},"durationMs":{"type":["integer","null"],"minimum":0,"maximum":120000,"description":"Client-measured recording length; null when absent."}}},"ConversationAssistant":{"type":"object","required":["state","replies","handoffReason"],"properties":{"state":{"type":"string","enum":["active","paused","handoff"],"description":"active — it answers; paused — staff stopped it; handoff — it handed the conversation to staff."},"replies":{"type":"integer","minimum":0,"description":"Replies it wrote in this conversation."},"handoffReason":{"type":"string","enum":["","customer_asked","no_knowledge","sensitive","unsure","limit","budget","hours","error"],"description":"Why it handed off (\"\" otherwise). budget also covers a plan that no longer allows the assistant (grace, suspended)."}}},"WebhookConversation":{"type":"object","description":"The conversation after the change. Never the customer's name, phone, email, page or visitor token.","required":["id","number","kind","channel","status","priority","subject","categoryId","assigneeUserId","contact","createdAt","updatedAt","lastActivityAt"],"properties":{"id":{"type":"string"},"number":{"type":"integer"},"kind":{"type":"string","enum":["live","ticket"]},"channel":{"type":"string"},"status":{"type":"string","enum":["open","pending","resolved","closed"]},"priority":{"type":"string","enum":["low","normal","high","urgent"]},"subject":{"type":"string"},"categoryId":{"type":["string","null"]},"assigneeUserId":{"type":["string","null"]},"contact":{"type":["object","null"],"description":"The business's customer the conversation is linked to — our id, theirs, and whether the link is proven.","required":["id","externalId","verified"],"properties":{"id":{"type":"string"},"externalId":{"type":"string"},"verified":{"type":"boolean","description":"true only when the conversation was started by a visitor the business's page signed in as this customer (a signed widget identity), or by the business's own server through the tickets API, and is still linked to them; then every customer-authored event in it (created, customer_replied) was written by that customer. false for every other link — a phone or email someone typed into the widget, a channel, or staff (a staff-linked ticket is shown to its customer by the tickets API, but is not verified) — which proves nothing about who is writing: a receiver must never treat it as the customer's identity."}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"lastActivityAt":{"type":"string","format":"date-time"},"mergedInto":{"type":"object","description":"The conversation staff merged this one into (0.33.0, #171) — its messages are there now. The last key, absent when never merged.","required":["id","number"],"properties":{"id":{"type":"string"},"number":{"type":"integer"}}}}},"WebhookMessage":{"type":"object","description":"A public message (never an internal note).","required":["id","author","authorUserId","body","blocks","attachments","createdAt"],"properties":{"id":{"type":"string"},"author":{"type":"string","enum":["customer","agent","assistant"]},"authorUserId":{"type":["string","null"]},"body":{"type":"string"},"blocks":{"type":"array","items":{"$ref":"#/components/schemas/MessageBlock"},"description":"Rich staff/assistant content; [] for customer messages or none."},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/Attachment"},"description":"Its files; download one through the tickets API (GET …/conversations/{number}/attachments/{id})."},"createdAt":{"type":"string","format":"date-time"},"editedAt":{"type":"string","format":"date-time","description":"message_edited only (absent otherwise): when staff edited it; body is the new text."},"deletedAt":{"type":"string","format":"date-time","description":"message_deleted only (absent otherwise): when staff deleted it; body is empty and blocks and attachments [] — the customer no longer sees it."}}},"WebhookChange":{"type":"object","required":["field","from","to"],"properties":{"field":{"type":"string","enum":["status","kind","assignee"],"description":"kind: a live chat staff turned into a ticket (0.33.0) — from live, to ticket."},"from":{"type":["string","null"]},"to":{"type":["string","null"]}}},"WebhookRating":{"type":"object","description":"The rating a conversation.rated is about.","required":["id","score","comment","source","resolvedAt","createdAt"],"properties":{"id":{"type":"string"},"score":{"type":"integer","minimum":1,"maximum":5},"comment":{"type":"string","description":"\"\" for none."},"source":{"type":"string","enum":["api","widget"],"description":"api: the tickets API; widget: the widget on the site (0.29.0)"},"resolvedAt":{"type":"string","format":"date-time","description":"The resolution rated."},"createdAt":{"type":"string","format":"date-time"}}},"MessageButton":{"description":"One button of a buttons row, Telegram-style: `reply` sends its value (else its label) as the customer's message; `url` opens an https page.","oneOf":[{"type":"object","required":["kind","label"],"properties":{"kind":{"type":"string","enum":["reply"]},"label":{"type":"string","minLength":1,"maxLength":40},"value":{"type":"string","minLength":1,"maxLength":200}}},{"type":"object","required":["kind","label","url"],"properties":{"kind":{"type":"string","enum":["url"]},"label":{"type":"string","minLength":1,"maxLength":40},"url":{"type":"string","maxLength":500}}}]},"CallStatus":{"type":"string","enum":["queued","ringing","connecting","answered","completed","no_answer","busy","failed","canceled","missed"],"description":"Outbound: queued → ringing → answered → completed; a call never answered ends no_answer (no pickup), busy (rejected), failed (trunk or LiveKit), or canceled (the staff member hung up first). Inbound (0.45.0): ringing (the caller holds, the desk rings) → connecting (a staff member answered; the voice gateway puts the caller through) → answered → completed; one nobody talked to ends missed, one that could not be put through failed. Only ever moves forward."},"ErrorEnvelope":{"type":"object","required":["ok","error"],"properties":{"ok":{"type":"boolean","const":false},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable, e.g. VALIDATION."},"message":{"type":"string","description":"English, for the developer; may change."}}}}}},"parameters":{"ExternalId":{"name":"externalId","in":"path","required":true,"description":"The business's own id for the customer: any text up to 120 characters, percent-encoded in the URL (spaces, slashes and Persian letters are fine).","schema":{"type":"string"}},"OrderExternalId":{"name":"orderExternalId","in":"path","required":true,"description":"The business's own id for the order (up to 120 characters), percent-encoded.","schema":{"type":"string"}},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional, 1–255 visible ASCII characters (else VALIDATION `Idempotency-Key: …`; the public door refuses one sent blank, which reaches this service as none). Per business, for 24 hours: the same key with the same request (method, path and body bytes) is done once — a repeat answers 201 rebuilt from what the first one made (the thread as it is now; a reply's own message; a rating) with `Idempotent-Replayed: true`, and NOT_FOUND if staff unlinked the conversation since. The same key with another request is IDEMPOTENCY_KEY_REUSED (422). A refused or failed request keeps no key.","schema":{"type":"string","maxLength":255}},"Number":{"name":"number","in":"path","required":true,"description":"The conversation's per-business number (#42 is `42`). Anything but a positive integer, or a conversation the API does not show this contact, is NOT_FOUND.","schema":{"type":"string"}},"LastEventId":{"name":"Last-Event-ID","in":"header","required":false,"description":"The id of the last stream event the client got (EventSource sends it on a reconnect). When it can be resumed — this process's, and still within stream.replay — every matching event after it is replayed and `ready` says resumed true; otherwise resumed false: refetch.","schema":{"type":"string"}}},"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"sk_ + 40 hex","description":"The business's API key, from the dashboard («اتصال به سایت ← کلید API»)."}}}}