API reference
The REST API behind the console: 349 endpoints. Anything the console does, a program can do with an API key, except connecting Telegram accounts and managing people, which a signed-in owner or admin does.
The whole reference is also an OpenAPI 3.1 spec, for client generators, Postman and other tools. GET /api/v1/openapi.json on your Oqim host redirects to it. For TypeScript, use the SDK.
Conventions#
- Base URL:
https://app.example.com/api/v1, your console's host plus/api/v1. Paths below are relative to it. - Authenticate with
Authorization: Bearer <API key or access token>. See Authentication. - Requests and responses are JSON, except uploads (
multipart/form-data), attachment downloads and the event stream (text/event-stream). JSON bodies are limited to 2 MB; imports take up to 10 MB and attachments up to 20 MB. - Timestamps are RFC 3339 in UTC. Optional fields are
nullwhen unset; a few appear only in one response, such as an API key'ssecretwhen it's created. - Lists take
limit(default 50, at most 500) andoffset, and return{"data": [...], "total": 1840, "limit": 50, "offset": 0}. - Errors share one envelope with a stable
code; see Errors. Limits are in Rate limits. - Every response carries
X-Request-Id, the ID the API logged the request under: the one you sent in that header, or a new one. Quote it when you report a problem. - Examples are abbreviated: long objects show their most useful fields.
IDs are strings: a type prefix and a time-sortable suffix, such as cmp_01j9r4c6e8g0j2m4p6r8t0w2y4. IDs from another organization return 404 not_found, the same as IDs that don't exist.
A first request:
All endpoints#
Jump to a resource, or open an endpoint's example.
- GET/accounts
- POST/accounts
- POST/accounts/login/{flow_id}/code
- POST/accounts/login/{flow_id}/password
- GET/accounts/{id}
- PATCH/accounts/{id}
- DELETE/accounts/{id}
- POST/accounts/{id}/pause
- POST/accounts/{id}/resume
- POST/accounts/{id}/check
- POST/accounts/{id}/reauth
- GET/accounts/{id}/activity
- POST/accounts/{id}/test-message
- POST/accounts/{id}/history/import
- GET/accounts/{id}/history
- POST/accounts/{id}/history/cancel
- GET/channels/providers
- POST/channels/instagram/connect
- GET/channels/instagram/callback
- POST/channels/facebook/connect
- GET/channels/facebook/callback
- GET/channels/accounts
- GET/channels/accounts/{id}
- PATCH/channels/accounts/{id}
- DELETE/channels/accounts/{id}
- POST/channels/accounts/{id}/check
- POST/channels/accounts/{id}/history/import
- GET/channels/accounts/{id}/history
- POST/channels/accounts/{id}/history/cancel
- GET/channels/automations
- POST/channels/automations
- GET/channels/automations/{id}
- PATCH/channels/automations/{id}
- DELETE/channels/automations/{id}
- GET/channels/accounts/{id}/private-replies
- GET/channels/accounts/{id}/marketing-topics
- GET/channels/accounts/{id}/marketing-subscriptions
- GET/channels/meta/webhook
- POST/channels/meta/webhook
- POST/channels/meta/deauthorize
- POST/channels/meta/data-deletion
- GET/channels/meta/data-deletion/{code}
- GET/campaigns
- POST/campaigns
- GET/campaigns/{id}
- PATCH/campaigns/{id}
- DELETE/campaigns/{id}
- POST/campaigns/{id}/preview
- POST/campaigns/{id}/validate
- POST/campaigns/{id}/start
- POST/campaigns/{id}/pause
- POST/campaigns/{id}/resume
- POST/campaigns/{id}/cancel
- POST/campaigns/{id}/stop-repeating
- POST/campaigns/{id}/duplicate
- GET/campaigns/{id}/audience-preview
- GET/campaigns/{id}/statistics
- GET/campaigns/{id}/recipients
- GET/ai/businesses
- POST/ai/businesses
- GET/ai/businesses/{id}
- PATCH/ai/businesses/{id}
- DELETE/ai/businesses/{id}
- GET/ai/businesses/{id}/offers
- POST/ai/businesses/{id}/offers
- PATCH/ai/offers/{id}
- DELETE/ai/offers/{id}
- GET/ai/businesses/{id}/funnels
- POST/ai/businesses/{id}/funnels
- GET/ai/funnels/{id}
- PATCH/ai/funnels/{id}
- DELETE/ai/funnels/{id}
- PUT/ai/funnels/{id}/stages
- GET/ai/businesses/{id}/identities
- POST/ai/businesses/{id}/identities
- PATCH/ai/identities/{id}
- DELETE/ai/identities/{id}
- GET/ai/communication-profiles
- POST/ai/communication-profiles
- PATCH/ai/communication-profiles/{id}
- DELETE/ai/communication-profiles/{id}
- POST/ai/communication-profiles/{id}/copy
- GET/ai/language-profiles
- POST/ai/language-profiles
- PATCH/ai/language-profiles/{id}
- DELETE/ai/language-profiles/{id}
- POST/ai/language-profiles/{id}/copy
- GET/ai/businesses/{id}/seller-settings
- PUT/ai/businesses/{id}/seller-settings
- GET/ai/businesses/{id}/accounts
- PUT/ai/businesses/{id}/accounts
- GET/ai/agents
- GET/ai/agents/{agent}/config
- PATCH/ai/agents/{agent}/config
- DELETE/ai/agents/{agent}/config
- GET/ai/models
- GET/ai/prompts
- POST/ai/prompts
- GET/ai/prompts/{id}/versions
- POST/ai/prompts/{id}/versions
- POST/ai/prompts/{id}/versions/{v}/activate
- GET/ai/executions
- GET/ai/executions/{id}
- GET/ai/costs
- GET/ai/budgets
- PUT/ai/budgets
- GET/ai/conversations
- GET/ai/conversations/{id}
- POST/ai/conversations/{id}/reply
- POST/ai/conversations/{id}/takeover
- POST/ai/conversations/{id}/resume
- POST/ai/conversations/{id}/read
- POST/ai/conversations/{id}/platform-read
- GET/ai/conversations/{id}/attachments/{attachment_id}
- POST/ai/conversations/{id}/uploads
- PUT/ai/conversations/{id}/personal
- GET/ai/conversations/{id}/insights
- POST/ai/conversations/{id}/analyze
- POST/ai/conversations/simulate
- POST/ai/playground/run
- GET/ai/handoffs
- PATCH/ai/handoffs/{id}
- POST/ai/conversations/{id}/drafts/{draft}/approve
- POST/ai/conversations/{id}/drafts/{draft}/discard
- GET/ai/leads
- GET/ai/sales-pipeline
- GET/ai/analytics
- GET/ai/performance
- GET/ai/knowledge/sources
- POST/ai/knowledge/sources
- GET/ai/knowledge/sources/{id}
- PATCH/ai/knowledge/sources/{id}
- DELETE/ai/knowledge/sources/{id}
- POST/ai/knowledge/sources/{id}/reindex
- GET/ai/knowledge/search
- GET/ai/memory
- DELETE/ai/memory/{id}
- POST/ai/research
- GET/ai/research
- GET/ai/research/{id}
- POST/ai/research/{id}/save-to-knowledge
- POST/ai/feedback
- GET/ai/feedback
- GET/ai/feedback/{id}
- POST/ai/feedback/{id}/review
- POST/ai/eval-datasets
- GET/ai/eval-datasets
- GET/ai/eval-datasets/{id}
- DELETE/ai/eval-datasets/{id}
- POST/ai/eval-datasets/{id}/items
- GET/ai/eval-datasets/{id}/items
- POST/ai/evaluations
- GET/ai/evaluations
- GET/ai/evaluations/{id}
- POST/ai/evaluations/{id}/run
- GET/ai/evaluations/{id}/results
- POST/ai/experiments
- GET/ai/experiments
- GET/ai/experiments/{id}
- POST/ai/experiments/{id}/variants
- POST/ai/experiments/{id}/winner
- GET/ai/businesses/{id}/cases
- POST/ai/businesses/{id}/cases
- POST/ai/businesses/{id}/cases/generate
- GET/ai/businesses/{id}/cases/generation
- PATCH/ai/cases/{id}
- DELETE/ai/cases/{id}
- GET/ai/conversations/{id}/analysis
- POST/ai/conversations/{id}/analysis/refresh
- POST/ai/conversations/{id}/analysis/explain
- PUT/ai/conversations/{id}/cases/{dimension}
- GET/ai/insights/boards/{board}
- POST/ai/insights/boards/{board}/narrative
- GET/ai/insights/boards/{board}/cases/{case_id}/conversations
- GET/ai/insights/backfill
- POST/ai/insights/backfill
- POST/ai/insights/backfill/cancel
- GET/admin/overview
- GET/admin/organizations
- POST/admin/organizations
- GET/admin/organizations/{id}
- PATCH/admin/organizations/{id}
- POST/admin/organizations/{id}/suspend
- POST/admin/organizations/{id}/reactivate
- GET/admin/users
- POST/admin/users/{id}/disable
- POST/admin/users/{id}/enable
- GET/admin/accounts
- POST/admin/accounts/{id}/disable
- POST/admin/accounts/{id}/enable
- GET/admin/proxies
- GET/admin/proxies/{id}
- POST/admin/proxies/{id}/check
- POST/admin/proxies/{id}/disable
- POST/admin/proxies/{id}/enable
- GET/admin/campaigns
- GET/admin/campaigns/{id}
- POST/admin/campaigns/{id}/pause
- POST/admin/campaigns/{id}/resume
- POST/admin/campaigns/{id}/cancel
- GET/admin/workers
- GET/admin/queues
- POST/admin/queues/{name}/pause
- POST/admin/queues/{name}/resume
- GET/admin/queues/{name}/tasks
- POST/admin/queues/{name}/tasks/{task}/retry
- POST/admin/queues/{name}/tasks/{task}/drop
- POST/admin/queues/{name}/retry-archived
- POST/admin/queues/{name}/drop-archived
- GET/admin/system
- POST/admin/organizations/{id}/support-sessions
- GET/admin/support-sessions
- POST/admin/support-sessions/{id}/end
- GET/admin/support-sessions/{id}/accesses
- GET/admin/organizations/{id}/limits
- PUT/admin/organizations/{id}/limits
- GET/admin/organizations/{id}/ai/budgets
- PUT/admin/organizations/{id}/ai/budgets
- GET/admin/organizations/{id}/ai/costs
- GET/admin/abuse-reports
- POST/admin/abuse-reports
- PATCH/admin/abuse-reports/{id}
- GET/admin/audit-logs
- GET/admin/settings
- PUT/admin/settings
- GET/admin/billing
- GET/admin/ai/overview
- GET/admin/ai/models
- POST/admin/ai/models
- PATCH/admin/ai/models/{id}
Authentication#
Sign-up, sign-in and the API-key token exchange. These public endpoints are limited to 30 requests per minute per IP address.
/auth/signupPublicCreate a user and their organization, then start a console session. Refused with signups_closed when sign-ups are off.
Example
/auth/loginPublicStart a console session and set the oqim_session cookie. With two-factor authentication on, the session is limited until POST /auth/mfa/verify.
Example
/auth/tokenPublicExchange an API key for a one-hour access token (oqat_…). Use it as a bearer token exactly like the key.
Example
/auth/mfa/verifySigned inComplete sign-in with a 6-digit TOTP code. One 30-second step of clock drift is accepted.
Example
/auth/logoutSigned inEnd the console session and clear the cookie.
Example
Profile#
The signed-in user or API key. The two-factor endpoints are for people; API keys get not_applicable.
/meSigned inWho is calling: the user or API key, the active organization, role, permissions and the current policy version.
Example
/meSigned inChange your name or password. A new password needs the current one and at least 10 characters.
Example
/me/switch-organizationSigned inMake another of your organizations the session's active one.
Example
/me/support/endSigned inEnd your own support session: the record closes, the organization is told, and the cookie is cleared. 409 not_in_support_session for any other session. Signing out does the same. While in a support session this and sign-out are the only writes allowed; everything else is 403 support_read_only.
Example
/me/organizationsSigned inCreate an organization with you as its owner and switch to it.
Example
/me/mfa/setupSigned inStart two-factor enrollment. Returns the TOTP secret and an otpauth:// URL for a QR code; nothing changes until you enable it.
Example
/me/mfa/enableSigned inTurn two-factor authentication on by confirming a code from the authenticator. The current session counts as verified.
Example
/me/mfa/disableSigned inTurn two-factor authentication off. Needs a current code. Platform administrators can't open the admin console without it.
Example
Organization#
The active organization, its policy acceptance, usage and dashboard data.
/organizationorg:readThe active organization.
Example
/organizationorg:manageRename the organization, change its default time zone, or turn its Conversation Intelligence (summaries, memory and the case analysis of conversations) off and on with conversation_intelligence_enabled.
Example
/organization/accept-policyorg:manageAccept the acceptable-use policy. version must be the current one (policy_version in GET /me); anything else is a 422. Required before connecting accounts, launching or resuming.
Example
/organization/usagebilling:readThe plan's limits, current usage, and 30 days of metered usage (messages sent, API requests, and AI requests and tokens when used) per UTC day. A limit of 0 means unlimited.
Example
/dashboardanalytics:readOverview numbers, running campaigns, account health, queue state and 14 days of delivery outcomes.
Example
/analytics/messagesanalytics:readDelivery outcomes over the last days (1 to 365, default 30) in the organization's time zone: per day, per account and per campaign.
Query: days
Example
Team#
Members, their roles and invitations. Owners can only be changed by owners.
/membersmembers:readMembers of the organization with their role and sign-in details.
Example
/members/{id}members:manageChange a member's role: OWNER, ADMIN, MANAGER, OPERATOR or VIEWER. Only owners grant or remove the owner role, and the last owner can't be demoted (409 last_owner).
Example
/members/{id}members:manageRemove a member from the organization. Their user remains. The last owner can't be removed.
Example
/invitationsmembers:readPending and past invitations.
Example
/invitationsmembers:manageInvite someone by email. Oqim doesn't send email: share invite_url, which appears only in this response. Invitations expire after 7 days.
Example
/invitations/{id}members:manageRevoke an invitation. One that was already accepted can't be revoked (409 invitation_accepted); remove the member instead.
Example
/invitations/lookupPublicDescribe an invitation from its token before accepting it. Unknown tokens are 404 invalid_invitation; used, revoked or expired ones are 410.
Query: token
Example
/invitations/acceptPublicAccept an invitation and start a session. New users set their name and password here; existing users must be signed in as the invited email.
Example
Telegram accounts#
Accounts your organization owns. Connecting is a three-step sign-in that a person completes; API keys can read accounts but not connect them. See Telegram accounts for states and pacing.
/accountsaccounts:readConnected accounts.
Query: status, q, limit, offset
Example
/accountsaccounts:manageStart signing in to an account: Telegram sends a login code, usually to the Telegram app. The flow lasts 15 minutes. Needs an accepted acceptable-use policy. A proxy_id of a disabled proxy is a 422.
Example
/accounts/login/{flow_id}/codeaccounts:manageSubmit the login code. The step becomes PASSWORD_REQUIRED when two-step verification is on, otherwise COMPLETED with the account. A wrong code is 422 phone_code_invalid; a flow allows 5 attempts.
Example
/accounts/login/{flow_id}/passwordaccounts:manageSubmit the two-step verification password to finish signing in. A wrong password is 422 password_hash_invalid.
Example
/accounts/{id}accounts:readOne account.
Example
/accounts/{id}accounts:manageChange pacing, the proxy or inbound. daily_limit and min_interval_seconds must stay within the platform's bounds; proxy_id null detaches the proxy, a proxy already used by another account is 409 proxy_in_use, and a disabled proxy is a 422 on proxy_id. inbound_enabled true keeps a connection open while the account is ACTIVE and records its private conversations for the AI Seller; turning it off stops that, and turning it on again starts from then rather than replaying the pause.
Example
/accounts/{id}accounts:manageLog the session out, delete the encrypted session and remove the account. Refused with 409 account_in_use while a running campaign uses it. Delivery history stays.
Example
/accounts/{id}/pauseaccounts:manageStop sending from an active account. It keeps its session; its campaigns continue on their other accounts.
Example
/accounts/{id}/resumeaccounts:manageResume a paused, disconnected or errored account. A RESTRICTED account needs acknowledge_restriction: true from a signed-in person, never an API key (403 person_required). Accounts that need to sign in again use reauth instead.
Example
/accounts/{id}/checkaccounts:manageAsk a worker to check the session with Telegram now and update the status.
Example
/accounts/{id}/reauthaccounts:manageSign an AUTH_REQUIRED or DISCONNECTED account in again with the same phone number. Continues like POST /accounts, with the code and password steps.
Example
/accounts/{id}/activityaccounts:readThe account's latest events and send jobs.
Example
/accounts/{id}/test-messagecampaigns:executeSend one message from an active account to a recipient (recipient_id) or a public @username (username), to try the account or a draft. Variables are filled in, the account's pacing and daily limit apply, and a failure affects the account just as a campaign send would. Opted-out and suppressed people are refused (422 recipient_opted_out, recipient_suppressed). An organization can send 20 test messages an hour and an account 10 (429 test_limit_reached). Needs an accepted acceptable-use policy.
Example
/accounts/{id}/history/importaccounts:manageImport the account's past private chats so the case analysis can explain conversations from before Oqim. A worker reads them through the account's own session and only reads: nothing is sent and nothing is marked read. It takes private chats with people whose last message is on or after since (YYYY-MM-DD in your organization's time zone, at most 365 days back, 90 by default), at most 500 messages per chat, the newest first; messages from the minute before the import started on are left to the live listener. Groups, channels, bots, Saved Messages, Telegram's service accounts and deleted accounts are skipped, and so are personal chats: the account's contacts (unless include_contacts is true, and even then their conversations stay personal) and chats marked personal. Imported messages keep Telegram's time and are marked imported: they never start an AI turn, a draft or an analysis, open no reply window, don't count as unread and fire no event or webhook, except that a stop request in them still opts the person out. A re-import adds only what's missing. It pauses exactly as long as Telegram asks (FLOOD_WAIT) and resumes where it stopped after a restart. One import per account at a time (409 history_import_running); only ACTIVE accounts (409 account_not_active); since out of range is a 422. channels.history.imported fires once when it's done. Needs an accepted acceptable-use policy.
Example
/accounts/{id}/historyaccounts:readThe account's latest history import: status IDLE (never imported), RUNNING, DONE, FAILED (error says why, e.g. the account was signed out) or CANCELLED; chats_found are its private chats with people within the period, of which chats_imported were read and chats_skipped_personal left out as personal; messages_imported counts messages added.
Example
/accounts/{id}/history/cancelaccounts:manageStop the running import. The worker stops before its next page; what was imported stays. 409 history_import_not_running when none is running.
Example
Channels: Instagram and Facebook#
Instagram professional accounts (Instagram Login, no Facebook Page needed) and Facebook Pages (Facebook Login for Business) the organization connects, so their direct messages land in the same inbox as Telegram's (GET /ai/conversations?channel=INSTAGRAM|FACEBOOK) and the same AI Seller answers them under Meta's rules: automated replies only within 24 hours of the customer's last message, people up to 7 days with the HUMAN_AGENT tag where the platform has that permission, and never a message to someone who didn't write first. Tokens are encrypted and never returned. An Instagram account or Page is live in one organization at a time. Events: channels.account.updated (connected, status change, disconnected) and channels.history.imported (once, when a history import ends). Until Meta's App Review is done (review_pending), only people with a role on the platform's Meta app can connect. The webhook and the OAuth, deauthorize and data-deletion callbacks are public: Meta and the browser call them, and each verifies what it gets (the signed single-use state, X-Hub-Signature-256, the signed_request).
/channels/providersaccounts:readWhich platforms this Oqim platform can connect: configured (the platform owner set up the Meta app; otherwise connecting is 409 provider_not_configured), review_pending (only accounts with a role on the Meta app can connect yet), human_agent (people may answer up to 7 days after a customer's last message) and history_messages_per_thread (history imports read only this many of a thread's most recent messages: Meta's limit).
Example
/channels/instagram/connectaccounts:manageThe Instagram Login URL to send the person to. Its state is signed, lasts 10 minutes and works once. After they allow access, Instagram returns them to the callback, which connects the account and sends them to the console's /channels page. A person signed in to the console connects accounts (API keys get 403 person_required); 409 provider_not_configured when the platform has no Instagram app. Needs an accepted acceptable-use policy.
Example
/channels/instagram/callbackPublicWhere Instagram returns the browser (public; the state is verified and used once, and the browser must carry the console session of the person who started the flow, so nobody can be tricked into connecting their account to another organization). The code becomes a 60-day token, the account is read, stored with capabilities from the permissions granted, subscribed to its webhooks (messages, messaging_postbacks, message_reactions, messaging_seen, and comments and mentions when granted) and its history import starts. Redirects (302) to PUBLIC_APP_URL/channels?connected=<id>, or ?error= denied, state_invalid, already_connected_elsewhere, missing_permissions (messaging must be allowed) or provider_error.
Query: code, state, error, error_reason
Example
/channels/facebook/connectaccounts:manageThe Facebook Login for Business URL (with the platform's login configuration when it has one, else the pages_* permissions). Same rules as Instagram's.
Example
/channels/facebook/callbackPublicWhere Facebook returns the browser (public; state-verified and bound to the starter's console session, as for Instagram). Every Page the person granted is connected with its Page token and subscribed to messages, messaging_postbacks, message_echoes, message_reactions, message_reads (and feed), and its history import starts. Pages live in another organization are skipped. Redirects to PUBLIC_APP_URL/channels?connected=<id,id>, or ?error= as for Instagram.
Query: code, state, error, error_reason
Example
/channels/accountsaccounts:readConnected accounts (Instagram, Facebook and YouTube), oldest first. Instagram accounts and Pages have stats: their conversations and those whose 24-hour window is open. Filter by platform (INSTAGRAM, FACEBOOK, YOUTUBE) and status (CONNECTED, PUBLIC, AUTH_REQUIRED, ERROR, DISCONNECTED).
Query: platform, status, limit, offset
Example
/channels/accounts/{id}accounts:readOne connected account, with its stats.
Example
/channels/accounts/{id}accounts:manageThe business the account serves (business_id; null clears it), whether its AI Seller answers the account's direct messages (ai_enabled: needs a business and the MESSAGING capability, otherwise a 422 on ai_enabled) and settings (an object). Audited.
Example
/channels/accounts/{id}accounts:manageDisconnect the account: its webhook subscription is removed (best effort), its tokens deleted and its status DISCONNECTED. Its conversations and their history stay. It may then be connected by another organization.
Example
/channels/accounts/{id}/checkaccounts:manageCheck the account with Meta now: its profile is read again and its webhook subscription renewed. The status follows: CONNECTED, AUTH_REQUIRED (the token expired or was revoked; reconnect) or ERROR with the reason. 502 provider_unavailable when Meta can't be reached.
Example
/channels/accounts/{id}/history/importaccounts:manageImport the account's past direct messages from Meta's Conversations API: threads active since since (a date, at most 365 days back; 90 days by default), and of each only the 20 most recent messages (Meta's limit), paced at 2 calls a second. Imported messages keep Meta's times, are marked imported, and never start an AI turn, open a reply window, raise the unread count or fire per-message events; a stop request found in them still stops the conversation. Importing again adds only what's missing. An import already running is returned as it is. 409 history_unavailable when the account can't read its messages. It also starts when an account connects.
Example
/channels/accounts/{id}/historyaccounts:readThe account's latest history import: status IDLE (never ran), RUNNING, DONE, FAILED or CANCELLED, with its counts. chats_skipped_personal is always 0 on Instagram and Facebook.
Example
/channels/accounts/{id}/history/cancelaccounts:manageStop a running import; what it stored stays. Returns the import.
Example
/channels/automationsaccounts:readList comment-to-DM automations. ?connected_account_id= filters to one account. Newest first.
Query: connected_account_id, limit, offset
Example
/channels/automationsaccounts:manageCreate a comment-to-DM automation on one Instagram account or Facebook Page. When the trigger fires on a comment on the account's own post (within 7 days), the private reply goes out once, within the hourly cap.
422 validation_error names the field (scope, keywords, templates, threshold, hourly_cap); 404 account_not_found.
Example
/channels/automations/{id}accounts:readOne automation, with sent_last_hour: what went out in the last hour, against the cap.
Example
/channels/automations/{id}accounts:manageChange the fields the body carries; the same rules as on create apply.
Example
/channels/automations/{id}accounts:manageDelete the automation. Its past private replies stay in the log (their automation_id becomes null).
Example
/channels/accounts/{id}/private-repliesaccounts:readThe private-reply log of an account: every comment an automation considered and what it did. ?automation_id= and ?status= (PENDING, SENDING, SENT, FAILED, SKIPPED) filter.
Query: automation_id, status, limit, offset
Example
/channels/accounts/{id}/marketing-topicsaccounts:readA Facebook Page's marketing-message topics: who opted in, who is eligible right now (outside Meta's frequency limits), who stopped or whose token expired. Instagram has no marketing messages.
Example
/channels/accounts/{id}/marketing-subscriptionsaccounts:readThe people who opted in to a Page's marketing messages. ?topic= and ?status= (ACTIVE, STOPPED, EXPIRED) filter. Tokens are never returned.
Query: topic, status, limit, offset
Example
/channels/meta/webhookPublicMeta's subscription handshake: with hub.mode subscribe and the platform's hub.verify_token, the answer is hub.challenge as plain text; otherwise 403.
Query: hub.mode, hub.verify_token, hub.challenge
Example
/channels/meta/webhookPublicMeta's notifications for Instagram (object instagram) and Pages (object page), signed with X-Hub-Signature-256 (401 signature_missing, signature_invalid). Each item (a message, an echo, a reaction, a read, an unsend, an edit) is stored once, deduplicated by its kind and mid, and processed by a worker; the answer comes at once. Items for accounts no organization has live are ignored.
Example
/channels/meta/data-deletionPublicMeta's data-deletion callback (signed_request as above): the person's accounts lose their tokens and profile and become DISCONNECTED, and their conversations and messages are deleted. The answer is the status URL and the confirmation code Meta shows the person.
Example
/channels/meta/data-deletion/{code}PublicThe status of a data-deletion request (RECEIVED or COMPLETED). 404 for an unknown code.
Example
Proxies#
Network routes your organization controls, pinned to at most one account each. Oqim checks reachability; it never rotates proxies.
/proxiesproxies:readYour proxies with their last health check.
Query: status, limit, offset
Example
/proxiesproxies:manageAdd a SOCKS5 or HTTP (CONNECT) proxy with an optional username and password, or an MTProto proxy with its secret. Credentials are encrypted at rest and never returned. An address (protocol, host and port) a platform administrator disabled can't be added again: 409 proxy_disabled.
Example
/proxies/importproxies:manageAdd up to 1,000 proxies at once from text in a JSON body, or from a file in a multipart field named file (2 MB at most). Oqim reads one proxy per line (socks5://user:pass@host:port, host:port, host:port:user:pass or a tg://proxy link), a CSV with a header row, or a JSON list. Lines without a scheme use default_protocol, SOCKS5 or HTTP. Send dry_run=true first for the report; the real import answers 201 and checks each new proxy right away. Proxies you already have are skipped, and an address a platform administrator disabled is reported INVALID.
Example
/proxies/bulkproxies:manageAct on many proxies at once, chosen by proxy_ids (up to 5,000), by status such as UNREACHABLE, or both. action "check" queues a health check for each and answers 202 with queued; "delete" removes them, detaching any account that used one, and answers 200 with deleted; proxies a platform administrator disabled stay, counted in kept_disabled.
Example
/proxies/{id}proxies:readOne proxy.
Example
/proxies/{id}proxies:manageChange a proxy. Omit password to keep the stored one. A proxy a platform administrator disabled (status DISABLED, with disabled_reason) can't be changed, and no proxy can be moved to its address: 409 proxy_disabled.
Example
/proxies/{id}proxies:manageDelete a proxy. An account using it is detached and connects directly. A proxy a platform administrator disabled stays until they enable it: 409 proxy_disabled.
Example
/proxies/{id}/checkproxies:manageDial a Telegram data centre through the proxy now; updates status and latency_ms. A disabled proxy isn't checked: 409 proxy_disabled.
Example
/proxies/{id}/checksproxies:readRecent health checks, newest first.
Example
Recipients#
People you may message and your basis for doing so. See Recipients for consent states and the CSV format.
/recipientsrecipients:readSearch and filter recipients.
Query: q, consent, tag, list_id, source, limit, offset
Example
/recipientsrecipients:manageAdd one recipient. telegram_id or username is required; everything else is optional. A Telegram ID or username that already exists is 409 recipient_exists.
Example
/recipients/importrecipients:manageImport a CSV or JSON file (multipart), or a JSON body with records. Send dry_run=true first for the report without saving anything, then the same data with dry_run=false. Up to 10 MB and 100,000 rows.
Example
/recipients/bulkrecipients:manageApply one action to up to 5,000 recipients: tag, untag, add_to_list, remove_from_list, set_consent, opt_out or delete. set_consent can't opt anyone out; use opt_out. in_flight counts recipients skipped because a message to them was being sent.
Example
/recipients/{id}recipients:readOne recipient, with the lists it belongs to.
Example
/recipients/{id}recipients:manageUpdate a recipient. An opted-out recipient's consent can't be changed (409 recipient_opted_out).
Example
/recipients/{id}recipients:manageDelete a recipient. If they opted out, their suppression entry stays, so they're never messaged again. 409 recipient_busy while a message to them is being sent.
Example
/recipients/{id}/opt-outrecipients:manageRecord an opt-out the person made outside Oqim. Permanent and organization-wide: adds them to the suppression list, skips their pending sends and emits recipient.opted_out.
Example
/recipient-importsrecipients:readPast imports and their counts.
Example
Lists#
Named groups of recipients. Deleting a list keeps its recipients.
/listsrecipients:readLists with member and sendable counts.
Example
/listsrecipients:manageCreate a list.
Example
/lists/{id}recipients:readOne list.
Example
/lists/{id}recipients:manageRename a list or change its description.
Example
/lists/{id}recipients:manageDelete a list. Its recipients stay.
Example
/lists/{id}/membersrecipients:manageAdd recipients to a list.
Example
/lists/{id}/membersrecipients:manageRemove recipients from a list (they stay in your recipients).
Example
Suppression list#
Telegram IDs and usernames your organization must never message. Opt-outs land here automatically.
/suppressionsrecipients:readSuppressed IDs and usernames.
Query: q, limit, offset
Example
/suppressionsrecipients:manageSuppress a Telegram ID or username, for example after a request by email. Existing recipients who match are opted out at once, and future imports of them arrive opted out.
Example
/suppressions/{id}recipients:manageRemove a suppression entry. This never restores consent: recipients who opted out stay opted out.
Example
Templates and attachments#
Reusable message bodies, files to attach, and previews rendered for real recipients.
/templatescampaigns:readSaved message templates.
Query: q, limit, offset
Example
/templatescampaigns:manageSave a template. parse_mode is MARKDOWN, HTML or PLAIN.
Example
/templates/{id}campaigns:manageChange a template. Campaigns already launched keep the text they were launched with.
Example
/templates/{id}campaigns:manageDelete a template.
Example
/attachmentscampaigns:manageUpload a file to attach to a campaign, in a multipart field named file. JPEG, PNG and WebP images become photos (up to 10 MB), video/* becomes a video, anything else a document; 20 MB at most.
Example
/attachments/{id}/contentcampaigns:readDownload an attachment's file, with its original Content-Type.
Example
/messages/previewcampaigns:readRender a message body for one recipient (by default your most recent sendable one): the Telegram HTML, the plain text and its length, the limit, and variables left without a value.
Example
Campaigns#
Create a draft, validate it, launch it, then pause, resume or cancel it. Which fields can change depends on the status; see Campaigns. A repeating campaign runs as a series of campaigns that Oqim adds and launches one at a time; see Scheduling.
/campaignscampaigns:readCampaigns, newest first. status accepts several values separated by commas. A repeating campaign has recurrence and next_run_at; its runs have series_id and occurrence.
Query: status, q, limit, offset
Example
/campaignscampaigns:manageCreate a draft. Everything except name can be filled in later. Set recurrence to repeat it; see Scheduling.
recurrence: frequency (DAILY, WEEKLY or MONTHLY), interval (every N days up to 60, weeks or months up to 12; default 1), weekdays (WEEKLY: MON to SUN, all sending days), month_day (MONTHLY: 1 to 31; shorter months use their last day) and ends: type NEVER, AFTER with count (2 to 365 runs, the first included) or ON with until (a date in the campaign's time zone, at most 2 years ahead). null means it runs once. Errors come back per field, like recurrence.weekdays.
Example
/campaigns/{id}campaigns:readOne campaign with its counters. A run of a repeating campaign also has series: runs launched so far, total_runs when the series ends after a count, whether it's still repeating, the rule, when the next run starts (next_run_at, next_run_id) and its neighbours (previous_id, next_id).
next_run_at on the campaign itself is when the run after it would start, projected from the rule; null when it doesn't repeat. auto_launch_at is set on a run Oqim added: it launches by itself then, 15 minutes before its start.
Example
/campaigns/{id}campaigns:manageChange a campaign. Drafts accept every field; scheduled and paused campaigns only name, description, end_at, the window and account_ids; running ones only name and description. A locked field is 409 field_locked; a finished campaign is 409 campaign_locked.
recurrence changes only while the campaign is a draft; stop a launched series with stop-repeating. Moving the start_at of a run Oqim added moves its auto_launch_at with it.
Example
/campaigns/{id}campaigns:manageDelete a draft. Launched campaigns can't be deleted (409 campaign_not_draft); cancel them instead. Deleting the next run of a repeating campaign ends its series.
Example
/campaigns/{id}/previewcampaigns:readRender the campaign's message for a recipient (by default your most recent sendable one).
Example
/campaigns/{id}/validatecampaigns:readRun the pre-launch checks without launching. Also saved on the campaign as validation.
Example
/campaigns/{id}/startcampaigns:executeLaunch a draft: re-run the checks, freeze the message and audience, and move to SCHEDULED. A failed check is 422 campaign_invalid with the report in validation; a full plan is 403 plan_limit_reached.
A draft with recurrence needs a start_at and becomes the first run of its series: series_id is its own id and occurrence is 1. Oqim launches the later runs itself through the same checks.
Example
/campaigns/{id}/pausecampaigns:executeStop dispatching a running or scheduled campaign. Recipients keep their place. Pausing a paused campaign returns it unchanged.
Example
/campaigns/{id}/resumecampaigns:executeResume a paused campaign: back to RUNNING if it had started, otherwise SCHEDULED. Refused (409) when its end time has passed (campaign_ended), none of its accounts can send (no_active_accounts) or a platform administrator paused it (paused_by_admin).
Example
/campaigns/{id}/cancelcampaigns:executeStop a draft, scheduled, running or paused campaign for good. Recipients not yet sent to are marked cancelled. Cancelling a run of a repeating campaign also stops its series.
Example
/campaigns/{id}/stop-repeatingcampaigns:executeStop a repeating campaign from adding runs. Call it on any run of the series; a run in progress finishes normally. The next run waiting to launch by itself is deleted if nobody edited it, and kept as an ordinary draft otherwise; the run you call it on is always kept. Stopping a stopped series changes nothing; a campaign that never repeated is 409 not_repeating.
Example
/campaigns/{id}/duplicatecampaigns:manageCopy a campaign into a new draft with the same message, audience, accounts and schedule. A repeat rule is copied too, as a new series, unless it no longer fits (its end date passed, say).
Example
/campaigns/{id}/audience-previewcampaigns:readWho an Instagram or Facebook campaign would reach if it sent now, and why the rest wouldn't. Telegram campaigns answer 409: their audience is their recipient list.
Example
/campaigns/{id}/statisticscampaigns:readLive progress: counts by status, a lane per account with its latest sends, a time series, the current rate and an ETA.
Example
/campaigns/{id}/recipientscampaigns:readPer-recipient delivery state, the account that sent it, attempts and the last error.
Query: status, q, limit, offset
Example
Jobs and events#
Individual send jobs, the organization's event history, and a live stream of the same events.
/jobscampaigns:readSend jobs with their result: DELIVERED, FAILED, RECIPIENT_UNAVAILABLE, ACCOUNT_RESTRICTED, AUTH_REQUIRED, TEMPORARY_ERROR or CANCELLED.
Query: campaign_id, account_id, status, limit, offset
Example
/eventscampaigns:readOrganization events, newest first: the same events webhooks deliver. type takes several values separated by commas; since takes a time or a date.
Query: type, since, limit, offset
Example
/events/streamcampaigns:read or ai:readServer-sent events (text/event-stream): every organization event as it happens, with a ping comment every 20 seconds to keep proxies from closing the connection. Readable with campaigns:read or ai:read (for the AI's events: ai.analysis.updated, ai.insights.narrative_ready, ai.cases.generated, ai.insights.backfill and the rest).
Example
API keys#
Keys act as the API_CLIENT role in one organization. The full key appears once, in the create and rotate responses.
/api-keysdevelopers:manageKeys with their prefix and last use. Secrets are never returned.
Example
/api-keysdevelopers:manageCreate a key. expires_in_days is 1 to 3650; leave it out for a key that doesn't expire. The plan must include API access.
Example
/api-keys/{id}/rotatedevelopers:manageIssue a replacement key. The old key keeps working for 24 hours, then stops. A revoked or expired key can't be rotated (409 key_inactive).
Example
/api-keys/{id}developers:manageRevoke a key immediately, along with any access tokens issued from it.
Example
Webhooks#
HTTPS endpoints that receive signed events. See Webhooks for payloads and signature verification.
/webhookswebhooks:readWebhook endpoints and their health.
Example
/webhookswebhooks:manageAdd an endpoint for some event types, or ["*"] for all. In production the URL must be https on a public host. The signing secret appears once, in this response.
Example
/webhooks/{id}webhooks:readOne endpoint.
Example
/webhooks/{id}webhooks:manageChange the URL, description or events, or set status to ACTIVE to re-enable an endpoint that was disabled after repeated failures.
Example
/webhooks/{id}webhooks:manageDelete an endpoint and its delivery history.
Example
/webhooks/{id}/testwebhooks:manageQueue a webhook.test delivery to this endpoint and return it (202). A disabled endpoint is 409 webhook_disabled.
Example
/webhooks/{id}/rotate-secretwebhooks:manageReplace the signing secret. Deliveries are signed with the new secret from now on.
Example
/webhooks/{id}/deliverieswebhooks:readThe latest 100 deliveries with their attempts, the response status, the first 2 KB of the response body and the duration.
Example
AI Seller: configuration#
Configure the AI Seller: the business the AI represents, its offer catalog, sales pipeline, persona, communication style, and runtime settings. Reads require ai:read; writes require ai:manage.
/ai/businessesai:readAll businesses in the organization.
Example
/ai/businessesai:manageCreate a business. A default 9-stage funnel and seller settings are created atomically.
Example
/ai/businesses/{id}ai:readOne business.
Example
/ai/businesses/{id}ai:manageUpdate any subset of business fields. business_hours are the opening hours the calendar books within, in the business's timezone: an object of days mon to sun, each {open, close} as HH:MM (close may be 24:00) or null for closed (a missing day is closed too). calendar_config are the booking rules (all optional): enabled (true), default_duration_minutes (30), min_duration_minutes (15), max_duration_minutes (120), buffer_minutes (0, kept free around each meeting), min_notice_minutes (60), max_days_ahead (60) and require_customer_confirmation (true; false lets autonomy 5 book without asking the customer to confirm the time first). Unknown days or fields and out-of-range values are refused (422).
Example
/ai/businesses/{id}ai:manageDelete a business and all its data (offers, funnels, identities, settings).
Example
/ai/businesses/{id}/offersai:readOffers for a business, ordered by sales_priority.
Example
/ai/businesses/{id}/offersai:manageCreate an offer. type is required: PRODUCT, SERVICE, COURSE, SUBSCRIPTION, EVENT, PACKAGE or OTHER. currency must be a 3-letter ISO-4217 code (e.g. USD, UZS, RUB).
Example
/ai/offers/{id}ai:manageUpdate offer fields. status: ACTIVE or ARCHIVED.
Example
/ai/offers/{id}ai:manageDelete an offer.
Example
/ai/businesses/{id}/funnelsai:readSales funnels for a business, each with its stages.
Example
/ai/businesses/{id}/funnelsai:manageCreate a new funnel (empty stages; use PUT /stages to fill it).
Example
/ai/funnels/{id}ai:readOne funnel with its stages.
Example
/ai/funnels/{id}ai:manageRename a funnel or change is_default.
Example
/ai/funnels/{id}ai:manageDelete a funnel and its stages.
Example
/ai/funnels/{id}/stagesai:manageReplace the entire stage list atomically. stages[] must have unique keys and end with a terminal stage (key CONVERTED, or ending in _DONE, _COMPLETE, _CONVERTED, _CLOSED). Each stage may set objective, allowed_actions and prohibited_actions (the AI Seller only does what the stage allows, and never what it prohibits), recommended_questions, and handoff_rules shaped like the seller settings' ones, which apply on top of them while a conversation is in the stage. Stage ids change with every replace; conversations keep their stage by key.
Example
/ai/businesses/{id}/identitiesai:readAI personas for a business.
Example
/ai/businesses/{id}/identitiesai:manageCreate a persona. display_name is required. biography must not claim the AI is a human (422 if it contains phrases like 'I am a real person', 'я живой человек' or 'men haqiqiy odamman').
Example
/ai/identities/{id}ai:manageUpdate identity fields.
Example
/ai/identities/{id}ai:manageDelete an identity.
Example
/ai/communication-profilesai:readBuilt-in profiles (is_builtin: true) and organization-owned profiles. Built-in profiles are read-only; copy them to customize.
Example
/ai/communication-profilesai:manageCreate a custom communication profile. All style fields are required: tone, formality, message_length, vocabulary, sentence_structure, greeting_style, emoji_policy, technical_depth, sales_aggressiveness, question_frequency, explanation_depth, cta_style, objection_style.
Example
/ai/communication-profiles/{id}ai:manageUpdate a custom profile. 403 if is_builtin.
Example
/ai/communication-profiles/{id}ai:manageDelete a custom profile. 403 if is_builtin.
Example
/ai/communication-profiles/{id}/copyai:manageCopy any profile (built-in or custom) into the organization as a new custom profile.
Example
/ai/language-profilesai:readBuilt-in language profiles (is_builtin: true) and organization-owned ones.
Example
/ai/language-profilesai:manageCreate a custom language profile. language (uz/ru/en), formality (FORMAL/INFORMAL/MIXED) and vocabulary (SIMPLE/STANDARD/RICH) are required.
Example
/ai/language-profiles/{id}ai:manageUpdate a custom language profile. 403 if is_builtin.
Example
/ai/language-profiles/{id}ai:manageDelete a custom language profile. 403 if is_builtin.
Example
/ai/language-profiles/{id}/copyai:manageCopy any language profile into the organization.
Example
/ai/businesses/{id}/seller-settingsai:readRuntime settings for the AI Seller on this business.
Example
/ai/businesses/{id}/seller-settingsai:manageCreate or replace seller settings. autonomy_level 0–5 (0 analytics only, 1 recommendations, 2 drafts a person approves, 3 automatic only for approved_scenarios, 4 automatic, 5 automatic with the tools approved in the agent configuration); reply_window_hours 1–168; followup_limit 0–5. When disclosure_enabled is true, disclosure_text must have non-empty text for uz, ru and en; the first AI message of each conversation starts with it. approved_scenarios for autonomy 3: GREETING, FAQ, PRICING, PRODUCT_INFO, AVAILABILITY, DELIVERY, OBJECTION, CLOSING, SCHEDULING, AI_DISCLOSURE, OTHER (complaints and requests for a person always go to a person). handoff_rules (all optional; a funnel stage's own handoff_rules apply on top while the conversation is in it): triggers turns HUMAN_REQUESTED, COMPLAINT, SENSITIVE_TOPIC, PRICING_EXCEPTION, CALENDAR_AMBIGUITY, POLICY_UNCERTAINTY, LOW_CONFIDENCE, REPEATED_FAILURES on or off (all on by default); keywords hand off (CUSTOM) when the customer's message contains one (a trailing * matches word endings); high_value_min hands off a lead worth at least that (HIGH_VALUE_LEAD, once per conversation); low_confidence_below (default 0.3) and max_failures (default 2) tune LOW_CONFIDENCE and REPEATED_FAILURES. Unknown scenarios, triggers or fields are refused (422).
Example
/ai/businesses/{id}/accountsai:readList Telegram accounts mapped to this business. An account can serve only one business.
Example
/ai/businesses/{id}/accountsai:manageReplace the full set of accounts mapped to this business. Pass an empty array to clear all mappings. Each account must belong to the same organization and cannot be mapped to another business.
Example
AI#
Model providers for the writing assistant, and the assistant itself. Keys are encrypted and never returned. Only the brief or the message text you submit goes to a provider, never recipient data. See AI writing assistant.
/ai/catalogorg:readThe built-in providers with their fixed endpoints and where to get a key, plus CUSTOM. allow_private_endpoints says whether custom endpoints may use private network addresses on this installation.
Example
/ai/providersorg:readConnected providers, the default first. The API key is never returned; key_hint shows its last characters and endpoint where requests go.
Example
/ai/providersorg:manageConnect a provider. Presets (OPENAI, ANTHROPIC, GEMINI, DEEPSEEK, OPENROUTER) need api_key and use their built-in endpoint. CUSTOM needs base_url, the part before /chat/completions, and api_format OPENAI or ANTHROPIC; its key is optional. The first provider becomes the default; up to 20 per organization. Test it next.
Example
/ai/providers/{id}org:manageChange a provider. Omit api_key (or send an empty string) to keep the stored key, send a new one to rotate it, or null to remove it from a custom endpoint. Changing the provider or base URL needs the key again. is_default: true makes it the default; to change the default, make another provider the default. Connection changes reset status to UNTESTED.
Example
/ai/providers/{id}org:manageRemove a provider and delete its key. When it was the default, the oldest remaining provider becomes the default.
Example
/ai/providers/{id}/testorg:manageSend a tiny request and record the outcome in status (OK or ERROR), last_error and last_checked_at. A failed test is still a 200 with status ERROR. Counts toward the provider-check limit.
Example
/ai/providers/{id}/modelsorg:manageThe provider's chat models, listed with the stored key. Lists that include every kind of model (OpenAI, Gemini, OpenRouter) are filtered to chat models.
Example
/ai/modelsorg:manageList models for a configuration that isn't saved yet, so a form can offer a model picker. The key is used for this one call and is neither stored nor logged.
Example
/ai/composecampaigns:manageDraft or edit one message with a connected provider: provider_id, or the default. action DRAFT needs brief (up to 2,000 characters); IMPROVE, SHORTEN, FRIENDLIER, FORMAL, FIX and TRANSLATE need text (up to 4,096), and TRANSLATE needs language en, ru or uz. The result keeps {{variables}}, follows parse_mode and is never sent to anyone. warnings flags dropped or added placeholders and overlong results.
30 requests an hour per person and 100 per organization (429 ai_rate_limited). A declined request is 422 ai_refused; a provider failure is 502 with an ai_provider_ code. While a platform administrator has AI turned off, it's 403 ai_disabled.
Example
AI engine#
The AI Seller's engine: which provider and model each agent uses (with fallbacks), the model registry and its prices, versioned prompts, execution traces, costs and budgets. Model calls go through a router that tries the agent's chain in order, moves on after timeouts, 5xx answers, rate limits and unreachable endpoints (never after a rejected key or a bad request), and skips a provider that keeps failing for a short cool-off. Costs are in US dollars, over UTC days. Every change is written to the audit log.
/ai/agentsai:readThe agents (SELLER, CI, CALENDAR, RESEARCH, JUDGE, EMBEDDER), each with whether the platform lets it run (enabled), the tools it can be granted, the organization's configuration (config, null when it uses the default provider), how many businesses override it, and the effective chain with each entry's health.
Example
/ai/agents/{agent}/configai:readAn agent's configuration for the organization, or with business_id for one business. config is the row of exactly that scope (null and inherited true when it inherits: a business from the organization, the organization from its default provider). effective is the chain the router uses; an entry with problem (a removed provider, no embedding model) is skipped.
Query: business_id
Example
/ai/agents/{agent}/configai:manageSet which provider and model an agent uses, for the organization or (business_id) one business. Fields left out keep their value. provider_id null is the organization's default provider; model null is the provider's own model (for EMBEDDER, the registry's embedding model for that provider). fallbacks: up to 4 {provider_id, model}, tried in order after retryable failures. params: temperature 0 to 2 (used for every call) and max_tokens 16 to 200,000 (a ceiling per call). tool_permissions: tools from the agent's list. A business's new configuration starts as a copy of the organization's; the organization's starts with the default tools.
Example
/ai/agents/{agent}/configai:manageRemove the configuration of one scope: a business then inherits the organization's, and the organization's agent uses its default provider.
Query: business_id
Example
/ai/modelsai:readThe model registry as your organization sees it: models with their capabilities, context window and prices in USD per million tokens. Platform entries are kept by Oqim's administrators; an organization entry overrides the platform's for the same provider and model. priced is false when a price the model needs is missing: its calls are then recorded at cost 0 and marked unpriced.
Query: provider_kind, capability
Example
/ai/promptsai:readThe prompts your organization sees: Oqim's platform defaults (scope PLATFORM, not editable) and your own, per organization or business, each with its active version. effective names the version each agent uses (for business_id, or the organization): the business's active version, else the organization's, else the platform default. placeholders lists the {{placeholders}} a prompt may use.
Query: agent, business_id
Example
/ai/promptsai:manageCreate a prompt for an agent, for the organization or (business_id) one business, with its first version. Without content it starts as a copy of the version the agent uses now. status is DRAFT (default) or TESTING; activate: true makes it the active version at once. Content is up to 32,000 characters and may use only the listed {{placeholders}}.
Example
/ai/prompts/{id}/versionsai:readA prompt's versions, newest first, with their content.
Example
/ai/prompts/{id}/versionsai:manageAdd a version to one of your prompts (platform prompts can't be changed: 403 platform_prompt). status is DRAFT or TESTING; activate: true makes it active at once.
Example
/ai/prompts/{id}/versions/{v}/activateai:manageMake version v the active version for its agent and scope. The version it replaces is archived in the same step: there is never more than one active version per organization, agent and business. Activating an archived version rolls back to it.
Example
/ai/executionsai:readAI runs, newest first: the agent, mode (LIVE, PLAYGROUND or EVAL), status (RUNNING, SUCCEEDED, FAILED or BLOCKED by the platform's switches or a budget), the provider and model of the main model call, tokens, cost in micro-dollars (unpriced when a price was missing), latency and the final action. conversation filters by conversation ID; from and to by start time.
Query: agent, status, mode, conversation, business_id, from, to, limit, offset
Example
/ai/executions/{id}ai:readOne run with its trace: the steps in order (input summary, output, the model call each made with its tokens and cost, errors) and the tool calls the model asked for (REQUESTED, then EXECUTED, REJECTED or FAILED). Step payloads are stored encrypted. No model reasoning is stored.
Example
/ai/costsai:readAI usage and cost for period today, week (the last 7 UTC days) or month (the calendar month so far, the default), in total and by business, agent, provider, model and mode, plus a day-by-day series. Requests and tokens are what providers reported (observed); cost is derived from the registry's prices at the time of each call.
Query: period
Example
/ai/budgetsai:readThe organization's AI budgets (daily and monthly, each null when unset) with this period's spending, and the combined state: ALLOWED, SOFT_ALERT (an alert threshold reached, or the limit passed on a budget without hard stop) or HARD_STOP (runs and model calls are refused until the period resets).
Example
/ai/budgetsai:manageSet the budgets: daily and monthly each take {limit_usd, alert_thresholds, hard_stop}, null removes that budget, and a period you leave out stays as it is. alert_thresholds are percentages of the limit (1 to 1000, default 80 and 100): crossing one notifies the organization once per period. hard_stop (default true) stops AI at 100% until the period resets. Changing the limit or thresholds re-arms this period's alerts.
Example
AI Seller: conversations#
The private Telegram chats of accounts with inbound turned on (PATCH /accounts/{id}, inbound_enabled), and the Instagram Direct and Facebook Messenger threads of connected accounts (see Channels: Instagram and Facebook), in one inbox: channel says which. A worker keeps one connection per such ACTIVE account and records every chat with a person, in both directions: author CUSTOMER, AI, HUMAN (from the console or the API) or OWNER_PHONE (typed in the account's own Telegram app). Groups, channels and bots are left out. Message text is encrypted at rest. People who write in become recipients with consent UNKNOWN (source INBOUND) until the plan's recipient limit. Oqim replies only inside a conversation the customer started, within the reply window (24 hours by default); outside it, only on recorded consent. A stop request ("stop", "стоп", "toʻxtating", "kerak emas", …) opts the person out and suppresses them for good. Live changes arrive on the event stream as ai.conversation.* and ai.handoff.* events. When an account serves a business whose Seller is active (seller-settings active), the AI takes a turn a few seconds after the customer writes: it reads the conversation, what it remembers and the business's knowledge, judges the message (JEV), picks the funnel stage, writes a reply and validates it (honesty, disclosure, facts only from the offers and knowledge, language, length, stage rules, no requests for secrets), then the business's autonomy level decides: 0 analytics only, 1 a recommendation, 2 a draft a person approves, 3 automatic only for the approved scenarios, 4 automatic, 5 automatic with approved tools. A customer who asks for a person or complains is handed to a person (a handoff) instead, and so is one who wants a booking unless the organization connected a Google calendar (then the Calendar agent handles it before the reply: see AI Seller: calendar); one who asks to stop is opted out. Every turn is an execution (GET /ai/executions/{id}) and emits ai.turn.completed; replies emit ai.reply.drafted, ai.reply.blocked and, when delivered, ai.reply.sent; stage moves ai.stage.changed; leads ai.lead.updated; drafts ai.draft.updated. Personal chats (personal true) are never the AI's: the account's Telegram contacts become personal by themselves (personal_source CONTACT) and an owner marks any chat personal or not (OWNER, never overridden automatically). The AI never answers or analyses a personal chat, what it had worked out about one is removed, and the list hides them unless asked; people still read and answer them. Messages imported from a chat's past (POST /accounts/{id}/history/import on Telegram, POST /channels/accounts/{id}/history/import on Instagram and Facebook) have imported true and keep the platform's time: they never start a turn, a draft or an analysis, open no reply window, don't count as unread and fire no event; the AI reads only live messages.
/ai/conversationsai:readThe organization's inbox on every channel, each with its channel (TELEGRAM, INSTAGRAM or FACEBOOK), its account (account: id, channel, name, username, avatar_url; connected_account_id for Instagram and Facebook), the linked recipient's consent, last_message (author, kind, the first 80 characters of the text decrypted, at) and waiting_since (the customer's last message time when they wrote last and nobody replied; null otherwise). Sort with sort=recent (default: last message), unread (unread first), waiting (customer waiting, oldest first) or oldest. Filter by channel, account_id (a Telegram account or a connected account), ai_state (ACTIVE, PAUSED_HANDOFF, STOPPED or OFF), unread (true or false), waiting (true or false), has_media (true or false), personal (true lists only personal chats and needs accounts:manage; false, the default, hides them) and q (name, @username, phone, Telegram ID or Instagram/Page-scoped ID).
Query: channel, account_id, ai_state, unread, waiting, has_media, personal, sort, q, limit, offset
Example
/ai/conversations/{id}ai:readOne conversation with its newest messages (oldest first; limit up to 200, default 50), the linked recipient, the open handoff, and reply: whether a person can reply now and on what basis (REPLY_WINDOW, CONSENT on Telegram, or HUMAN_AGENT on Instagram and Facebook, with window_ends_at and, where the platform's Meta app has the Human Agent permission, human_window_ends_at), or why not (code, reason), and max_length with max_length_unit: the channel's text limit (Telegram 4096 utf16, Instagram 1000 bytes, Facebook 2000 characters). On Instagram and Facebook the window is 24 hours after the customer's last message (the business's reply window can shorten it); after that only a person may answer, up to 7 days, with Meta's HUMAN_AGENT tag, and nothing goes out on consent (code window_closed). Messages carry external_message_id (Meta's mid), imported (from a history import), read_at and reaction where Meta reports them, reply_to_message_id, forwarded_from, reactions ([{emoji, by: CUSTOMER|BUSINESS}]), attachments (id, kind, status, mime, filename, size_bytes, width, height, duration_ms, has_thumb; served by GET …/attachments/{attachment_id}), and metadata describing locations and contacts. Pass next_cursor as before for older messages. A message the customer deleted keeps its metadata but not its text. The AI Seller's side: state (what it knows about the customer: stage, intent with its confidence, interest, lead quality, objections, language, sentiment, next action, and details — needs, pain points, budget, timeline, whether they decide alone — which are encrypted at rest; null before the Seller's first turn), draft (the draft or recommendation waiting for a person, with its text, or null), lead, and stage_events (the last 20 moves between funnel stages, oldest first).
Query: limit, before
Example
/ai/conversations/{id}/replyai:operateReply as a person. The text (plain, up to the channel's max_length: 4,096 characters on Telegram, 1,000 bytes on Instagram, 2,000 characters on Facebook) is queued and sent by a worker from the conversation's account, like the AI's replies: at least a few seconds after the previous reply in the conversation, within the account's cooldowns and daily limit, and never from another account; Instagram and Facebook replies go through Meta's Send API as plain text. The consent gate is checked now and again at sending: 409 reply_not_allowed with details.reason OPTED_OUT, SUPPRESSED, CONVERSATION_STOPPED, NO_CONSENT (the reply window closed and there's no recorded consent) or window_closed (Instagram and Facebook: past 24 hours, or past 7 days with the Human Agent permission); 409 account_not_active, account_auth_required (reconnect the Instagram account or Page) or organization_suspended. A send Meta refuses ends FAILED with error_code window_closed, account_auth_required (the account becomes AUTH_REQUIRED), recipient_unavailable or provider_error; rate limits are retried. Watch ai.conversation.message_updated for sent or failed. Needs an accepted acceptable-use policy.
Example
/ai/conversations/{id}/takeoverai:operateTake the conversation over: the AI pauses (ai_state PAUSED_HANDOFF), its unsent messages are cancelled, and a MANUAL handoff assigned to you is opened (or the open handoff, if unassigned, becomes yours). A conversation the customer stopped is 409 conversation_stopped.
Example
/ai/conversations/{id}/resumeai:operateHand the conversation back to the AI: ai_state ACTIVE and the open handoff resolved. The AI then answers whatever the customer wrote meanwhile. A conversation the customer stopped can't be resumed (409 conversation_stopped).
Example
/ai/conversations/{id}/readai:operateMark the conversation read in Oqim only (unread_count 0). Nothing is marked read on Telegram, Instagram or Facebook: the customer never sees a read receipt. Replying or taking over does this too.
Example
/ai/conversations/{id}/platform-readai:operateSend a read receipt on the platform itself (Telegram readHistory, Meta mark_seen) — the ONLY way a message is ever marked read there, and only when a person asks. Sends it exactly once: a second call answers 200 with already_read: true and sends nothing. 409 when the platform refuses, 503 when the account is unreachable (nothing was sent). Audited.
Example
/ai/conversations/{id}/attachments/{attachment_id}ai:readStream a message's file from Oqim's storage with its content type and HTTP Range (audio and video seek). The platform's URL or file reference is never exposed. ?thumb=1 returns the thumbnail where one exists (404 no_thumbnail otherwise). While the file is still downloading it answers 202 { status: "PENDING" } and queues the download; a file that is TOO_LARGE, UNAVAILABLE or FAILED answers 409 with status and error. In a personal chat the route needs accounts:manage (otherwise 404).
Query: thumb
Events: ai.conversation.attachment_ready
Example
/ai/conversations/{id}/uploadsai:operateUpload one file (multipart field file) to send in the conversation, before replying. Returns upload_id, filename, mime, size_bytes, kind and expires_at (24 h). It expires unused. Kinds allowed by channel: Telegram any file up to the cap (INBOX_MAX_FILE_MB, default 50 MB), Instagram image/video/audio, Facebook image/video/audio/file; 422 kind_not_allowed otherwise, 413 file_too_large. Send it with POST …/reply (attachments: [upload_id]).
Example
/ai/conversations/{id}/personalai:operateMark the chat personal (personal: true) or a business chat (false). The decision is yours (personal_source OWNER): contacts never override it, and history imports respect it. A personal chat is never answered or analysed by the AI: its pending draft is superseded, the AI's unsent messages are cancelled (ai.draft.updated, ai.conversation.message_updated), and its summary, insights, memories, the Seller's state and case analyses are removed; its messages stay and people can still reply. Made a business chat again, the AI answers from the customer's next message, never what was said while it was personal. Audited.
Example
/ai/conversations/{id}/insightsai:readLatest Conversation Intelligence analysis and summary for a conversation, with live: the case analysis's current reading (JEV between summaries: scores, flags and classification; see AI Seller: case analysis and insights). Your model writes the summary and insights at most every 10 new messages and when the conversation ends; in between, sentiment and interest follow the live reading. Returns 404 (no_insights) when neither has run yet.
Example
/ai/conversations/{id}/analyzeai:operateQueue a Conversation Intelligence summary and insights for this conversation immediately (outside the normal 60-second debounce window and the every-10-messages cadence). Returns 202 when queued, or the result directly when no queue is configured. A personal chat is never analysed: 409 conversation_personal. Rate-limited.
Example
/ai/conversations/simulateai:operateSimulator only (TELEGRAM_DRIVER=simulator; otherwise 404 simulator_only): a customer writes to one of your accounts. The message goes through exactly what a real one does: stored encrypted, the recipient linked or created, stop requests applied, events emitted and the AI's turn scheduled. from.contact true simulates someone in the account's Telegram contacts: the chat becomes personal and the AI stays out (personal true, respond false). The account must be ACTIVE with inbound on (409 account_not_active, inbound_disabled).
Example
/ai/playground/runai:operateDry-run the AI Seller pipeline against a business with a synthetic conversation (mode PLAYGROUND, or EVAL for automated evaluations). The pipeline runs in full — JEV decision, strategy, compose, generate, validate, business and platform policy, autonomy gate — but nothing is sent, drafted, handed off, or written to customer states, leads, memories or any mutable table. Only the execution trace is stored (mode=PLAYGROUND) and is inspectable at GET /ai/executions/{id}. The budget gate still applies. Supply at least one CUSTOMER message. Overrides: autonomy_level (–1 uses the stored value), prompt_version_id (a Seller version of any status; an unknown one is 422), communication_profile_id, language_profile_id, language, knowledge_enabled (false to skip retrieval), memory_items (extra facts for this run only), and provider_id and model: the run is pinned to exactly that provider and model (the reply, its regeneration and the decision layer's LLM fallback), with no fallback to the business's chain; provider_id alone uses the provider's own model, model alone runs on the provider the Seller's chain starts with; a provider that isn't the organization's, or a model id with spaces or over 200 characters, is 422 on that field. A pinned model that fails ends the run with final_action FAILED and the reason in error. provider_id and model in the response are what wrote the reply. The run's JEV decision is stored with mode PLAYGROUND (or EVAL) and linked to the run. Rate-limited to 60 runs per org per hour.
Example
/ai/handoffsai:readConversations handed to people, newest first: by the AI (trigger HUMAN_REQUESTED, LOW_CONFIDENCE, COMPLAINT, PRICING_EXCEPTION, …) or taken over (MANUAL). status is open (default), resolved or all; assigned_user_id is a user ID, me or none.
Query: status, assigned_user_id, conversation_id, limit, offset
Example
/ai/handoffs/{id}ai:operateAssign an open handoff to a member (assigned_user_id; null unassigns) and/or resolve it (resolved: true, with an optional resolution_note). Resolving doesn't resume the AI; POST /ai/conversations/{id}/resume does. A resolved handoff can't change (409 handoff_resolved).
Example
/ai/conversations/{id}/drafts/{draft}/approveai:operateSend a draft the AI Seller prepared (autonomy 2, or 3 outside the approved scenarios). It goes out as the AI's message with you as sent_by, through the same consent gate and pacing as every reply; the first AI message of a conversation gets the business's disclosure line when disclosure is on. 409 draft_not_pending when it was approved, discarded or replaced by a newer draft (the customer wrote again); 409 recommendation_not_sendable for recommendations (autonomy 1: guidance, write the reply yourself); 409 reply_not_allowed when the gate refuses (the draft then stays pending). Needs an accepted acceptable-use policy.
Example
/ai/conversations/{id}/drafts/{draft}/discardai:operateSet a pending draft or recommendation aside (status DISCARDED). 409 draft_not_pending when it isn't pending anymore.
Example
/ai/leadsai:readThe AI Seller's leads, the most recently updated first: one per conversation, opened when the customer shows interest, with the funnel stage (and its name), quality (cold, warm, hot), interest, status (OPEN; WON at the funnel's last stage; LOST when the customer opted out) and a value estimate from the prices of the offers the customer is interested in. Filter by business_id, stage (a stage key), quality, status, conversation_id and q (the customer's name).
Query: business_id, stage, quality, status, conversation_id, q, limit, offset
Example
/ai/sales-pipelineai:readFunnel snapshot: for each business's default funnel, counts of open/won/lost leads per stage with estimated total value (sum of value_estimate for open leads) and oldest open lead age in days. Stage-to-stage conversion rates (derived). Filter by business_id. Each pipelineResult has kind 'mixed'; conversion rates have kind 'derived'; value_total is kind 'estimated' (from AI's value_estimate); counts are kind 'observed'.
Query: business_id
Example
/ai/analyticsai:readConversation and sales metrics over a time range (from/to: YYYY-MM-DD or RFC3339, default last 30 days). Each metric carries kind: observed (counted from rows) | estimated (from AI judgements) | derived (computed ratio or average) and a filter_hint pointing at the underlying rows. Metrics: conversations_started, customers_replied, ai_turns_by_action (by final_action), handoffs_by_trigger, opt_outs, leads (opened/won/lost), avg_first_reply_seconds, stage_movements. Filter by business_id.
Query: business_id, from, to
Example
/ai/performanceai:readPer-agent/business/model performance over a time range (from/to: YYYY-MM-DD or RFC3339, default last 30 days). Counts runs, succeeded, failed, blocked; average latency (derived); total cost_micros; fallbacks (JEV → LLM fallback count); jev_runs (runs with a JEV decision). Validator failure codes with counts. All from ai_executions (mode=LIVE).
Query: from, to
Example
AI Seller: knowledge and memory#
What the AI Seller knows about a business and remembers about its customers. Knowledge sources are text, FAQ entries, offers (synced from offers), files and web pages; each change is ingested in the background into a new version (extracted, split into chunks, embedded when an embedding provider is configured), and the Seller only ever reads a source's published version. Memories are sealed customer data. Reading needs ai:read; changes need ai:manage and are audited.
/ai/knowledge/sourcesai:readKnowledge sources, newest first. version is the published version the Seller reads (0 until the first ingest finishes). status is PENDING or PROCESSING while a new version is built, READY, or FAILED with error; the last good version keeps answering meanwhile. embedding_model null means full-text search only. Lists leave out config.text and config.items: get the source for them.
Query: business_id, kind, status, q, limit, offset
Example
/ai/knowledge/sourcesai:manageAdd knowledge to a business: kind TEXT (title, text up to 200,000 characters), FAQ (title, items: up to 500 questions with answers), URL (url of a public page, fetched through the same address guard as custom AI providers; the title defaults to the page's) or FILE (attachment_id of an uploaded attachment). The source starts PENDING and is ingested in the background. OFFER sources come from offers and can't be added here.
To upload a file, send multipart/form-data with business_id, an optional title and file: .txt, .md, .csv, .tsv or .html, UTF-8 (UTF-16 with a byte-order mark and Windows-1251 are read too), up to 5 MB. PDF and Word files are refused with 422. A business can have 500 sources (409 knowledge_source_limit); an organization can start 200 ingests (additions, edits, reindexes) an hour (429 ai_rate_limited).
Example
/ai/knowledge/sources/{id}ai:readA knowledge source with its full config: the text, the FAQ items, the page address or the file.
Example
/ai/knowledge/sources/{id}ai:manageChange a source: title, text (TEXT), items (FAQ, the whole list) or url (URL). A change queues a new version, and the current one keeps answering until it's ready. kind, business_id and the file can't change: add a new source instead. Offer sources follow their offer (409 knowledge_source_managed).
Example
/ai/knowledge/sources/{id}ai:manageDelete a source with every version of it. A file uploaded for it is deleted too, unless a campaign uses it.
Example
/ai/knowledge/sources/{id}/reindexai:manageBuild a new version of a source: its text is chunked again, a page fetched again, and everything embedded with the current embedding model. Answers 202 with the source, now PENDING; it counts toward the hourly ingest limit.
Example
/ai/knowledge/searchai:readRetrieve knowledge the way the Seller does, to test it: q is what a customer might write (up to 1,000 characters). Retrieval is hybrid: full-text matching of the query's words (oʻ, o' and o match, as do ё and е, and fuzzy matching catches word forms and typos) and, with an embedding provider, similarity of meaning. why says what matched: fts (words), vector (meaning only) or both. Hits fit within budget estimated tokens (default 2,000); limit is 1 to 50 (default 5).
Query: business_id, q, kind, limit, budget
mode is hybrid when the query was embedded, else full_text. strategy is exhaustive when every chunk of the business was scored (knowledge bases up to 256 chunks), prefilter when full-text matching chose the candidates. 600 searches per organization per hour.
Example
/ai/memoryai:readWhat the Seller remembers, most recently updated first: facts about customers (LONG_TERM), conversation notes (SHORT_TERM, MEDIUM_TERM), BUSINESS and SYSTEM memories, with importance, confidence and where each came from (source_type MESSAGE, EXECUTION, MANUAL or SYSTEM). status is ACTIVE by default; SUPERSEDED memories were replaced by a changed fact (superseded_by), EXPIRED ones are about to be deleted. type and status take several values separated by commas.
Query: business_id, recipient_id, telegram_user_id, conversation_id, type, status, limit, offset
Example
/ai/memory/{id}ai:manageForget a memory for good, with the older statements of the same fact it replaced: for a privacy request or a wrong fact. The deletion is kept in the audit log and the memory's history, without the content.
Example
/ai/researchai:manageStart a research job for a business subject (company name + optional website). The agent fetches the website, optionally queries a configured search provider, then calls the LLM to produce a structured report. Every fact cites an evidence URL that was actually fetched. The agent refuses private individuals (422 private_individual). Rate-limited to 20 per organization per hour. While the platform's research switch (ai_research_enabled, or AI as a whole) is off, nothing is created: 409 research_disabled. A job queued before the switch went off ends FAILED with the reason in error_message instead of staying PENDING. Audited.
Example
/ai/researchai:readList research jobs for the organization, newest first. Supports ?limit=, ?offset= and ?status= (PENDING | RUNNING | SUCCEEDED | FAILED).
Example
/ai/research/{id}ai:readGet a research job and its report (when status is SUCCEEDED). The report contains a summary, a list of facts each with an evidence URL and confidence score, and a retrieved_at timestamp. Returns job even while PENDING or RUNNING.
Example
/ai/research/{id}/save-to-knowledgeai:manageSave a completed research report as a TEXT knowledge source linked to a business. The report's summary and facts are formatted as readable text. Requires the job to be SUCCEEDED. Audited.
Example
AI Seller: decisions#
The AI Seller's decision layer. For each customer message, TypeSafe's System One model (JEV) answers one batch of typed questions: intent, sales stage, whether to advance it, next action, interest, lead quality, objection, and whether to hand off, book a time, look up knowledge, stop messaging or say it's an AI. It returns probabilities and a confidence per field; it never writes the reply. Fields below their confidence threshold, or all of them when JEV is off, has no key or fails, go to your AI model with the same questions, and each field records its source (JEV or LLM). JEV is opt-in: with it on, each customer message (card numbers, phone numbers and e-mail addresses masked), a short conversation summary, the sales stage and funnel objectives, known needs and objections, offer names and the language go to TypeSafe. Never the conversation history, customer profiles, Telegram ids or keys. Decisions store the questions, answers, probabilities, confidence, thresholds and sources, not the message and no reasoning.
/ai/jevai:readJEV settings: whether it's on, has_key and key_hint (the key itself is never returned), key_source (the key decisions use now: ORGANIZATION, PLATFORM when the platform provides one, or NONE), the effective per-field confidence thresholds with the defaults and your overrides, when data sharing was accepted, and disclosure, which lists what goes to TypeSafe.
Example
/ai/jevai:manageChange the settings; fields you leave out keep their value. Turning JEV on needs accept_data_sharing: true once, which records who accepted and when. api_key: a new TypeSafe key replaces the saved one, "" keeps it, null removes it (decisions then use the platform key, if provided). thresholds replaces your overrides: numbers from 0 to 1 per field; a field left out or null uses its default, and null for the whole object clears them. A choice or score's confidence is JEV's own; a yes/no field's is its distance from 0.5, doubled, so 0.6 needs a probability of at least 0.8 or at most 0.2.
Audited as AI_JEV_SETTINGS_UPDATED, without the key. Turning JEV on without accepting data sharing is 422 with details.accept_data_sharing.
Example
/ai/jev/testai:manageCheck a key with one decision on a synthetic customer message ("Salom, narxi qancha? Yetkazib berish bormi?") and a sample funnel: JEV only, whether or not JEV is on, since nothing of yours leaves Oqim. Uses the saved key, else the platform's; send api_key to try one before saving it. A failed check is still a 200: ok is false and error has the code (auth_failed, rate_limited, timeout, …) and message. The decision is stored in TEST mode.
No key to test is 409 jev_no_key. Counts toward the organization's 60 AI checks an hour (429 ai_rate_limited).
Example
/ai/jev/decisions/{id}ai:readOne stored decision: its mode (LIVE, PLAYGROUND, EVAL or TEST), source (JEV, LLM, MIXED or NONE), jev_status (OK, ERROR, DISABLED or NO_KEY), the questions asked, the answers (JEV's as received, the fallback model's normalized), the decision with every field's value, probabilities, confidence, threshold, trusted flag and source, the thresholds applied, the input's sizes (never its text), latency and tokens.
Example
AI Seller: quality#
Feedback, evaluation datasets and items, evaluation runs, and A/B experiments for the AI Seller. Feedback captures human annotations on AI replies (labels, rating, optional corrected text), with optional review requests. Datasets hold replay items; evaluations run those items through the current or a candidate config and score each reply with JEV or an LLM judge; experiments compare variants. Reading needs ai:read; adding feedback and items needs ai:operate; everything else needs ai:manage.
/ai/feedbackai:operateSubmit feedback on an AI reply: optional execution_id, conversation_id, message_id, labels (GOOD, WRONG_FACT, WRONG_TONE, TOO_LONG, MISSED_OPPORTUNITY, SHOULD_HANDOFF, UNSAFE, OTHER), rating (1–5), tags, explanation, corrected_reply, suggested_response. Set request_review: true to request a human review immediately.
Example
/ai/feedbackai:readList feedback, most-recent first. Optional execution_id and conversation_id filters.
Query: execution_id, conversation_id, limit, offset
Example
/ai/feedback/{id}ai:readOne feedback item. The explanation, corrected_reply and suggested_response fields are unsealed and returned in plain text.
Example
/ai/feedback/{id}/reviewai:operateMark this feedback item as review-requested and enqueue a review card task. Idempotent: a second call on an already-requested item is a no-op.
Example
/ai/eval-datasetsai:manageCreate an evaluation dataset: name (required), optional business_id and description.
Example
/ai/eval-datasetsai:readList evaluation datasets, most-recent first.
Query: limit, offset
Example
/ai/eval-datasets/{id}ai:readOne evaluation dataset.
Example
/ai/eval-datasets/{id}ai:manageDelete a dataset and all its items. Refused while an evaluation referencing it is RUNNING.
Example
/ai/eval-datasets/{id}/itemsai:operateAdd one item to the dataset: source (MANUAL or FEEDBACK), optional feedback_id, meta (topic, notes, tags), conversation (list of {direction, author, text} snapshots), reference_reply (the expected reply to score candidates against). The conversation and reference_reply are sealed at rest.
Example
/ai/eval-datasets/{id}/itemsai:readList items in a dataset, oldest-first. The conversation and reference_reply are unsealed in the response.
Query: limit, offset
Example
/ai/evaluationsai:manageCreate an evaluation run: dataset_id and name (both required), optional business_id (else the dataset's business) and candidate_config: prompt_version_id (a Seller version of any status: DRAFT and TESTING ones are what you try out), communication_profile_id, language_profile_id, autonomy_level (0–5), and provider_id and model (every replay is pinned to exactly that provider and model, with no fallback; provider_id alone uses the provider's own model, model alone the provider the Seller's chain starts with). Missing keys fall back to the business's live configuration; candidate_config that isn't an object, an autonomy_level outside 0–5, a provider_id that isn't one of the organization's providers or a malformed model id is 422 (details name candidate_config.<key>). Status starts PENDING.
Example
/ai/evaluationsai:readList evaluation runs, most-recent first.
Query: limit, offset
Example
/ai/evaluations/{id}ai:readOne evaluation run with all fields. items_total is items_scored + items_skipped; avg_score and pass_rate cover the scored items only (a skipped item has no reply to judge). The same three counters are also served as total_items, scored_items and skipped_items.
Example
/ai/evaluations/{id}/runai:manageEnqueue the evaluation run. The run must be in PENDING status. The worker replays every dataset item through the real Seller pipeline in EVAL mode (the playground's dry run with the candidate_config: nothing is sent, drafted or stored for a customer; each replay is one run at /ai/ops/runs/{execution_id}), and the judge scores the reply against the reference — with the JUDGE prompt resolved for the business (the platform default in internal/prompts/defaults/judge.md unless the organization or business has its own active version), or JEV when the organization has it configured. One result is written per item. An item whose replay couldn't run or finish (the AI budget, the platform's switches, an unknown prompt version, a pinned model that failed, no customer message) is written as SKIPPED with its reason in error_code, isn't scored and doesn't count in pass_rate; when none could be scored, the evaluation ends FAILED with the reason in error. Returns 503 when the task queue is unavailable.
Example
/ai/evaluations/{id}/resultsai:readOne page of evaluation results, oldest-first: one row per dataset item. status is SCORED or SKIPPED. judge_scores is a JSON map of dimension name to {score, source, explanation}. The reply is unsealed in the response. A SKIPPED row has error_code (no_customer_message, budget, blocked, invalid_candidate, replay_failed or no_business) and error instead of scores.
Query: limit, offset
Example
/ai/experimentsai:manageCreate an A/B experiment: dataset_id and name required, optional business_id and description. Status starts DRAFT.
Example
/ai/experimentsai:readList experiments, most-recent first.
Query: limit, offset
Example
/ai/experiments/{id}ai:readOne experiment with its variants and their linked evaluations.
Example
/ai/experiments/{id}/variantsai:manageAdd a variant to an experiment: name required, optional candidate_config (the keys and checks of POST /ai/evaluations).
Example
/ai/experiments/{id}/winnerai:manageRecord the winning variant and set experiment status to SUCCEEDED. variant_id required.
Example
AI Seller: calendar#
Google Calendar for the AI Seller. An organization connects one Google calendar through Google's consent screen (the platform's OAuth client; minimal scopes: the calendar's events, its free/busy times, and the account's e-mail address). The tokens are encrypted at rest and never returned. When a customer asks about a meeting, the Calendar agent (its own run, with the calendar prompt) turns the conversation into one action: CREATE, UPDATE, CANCEL, FIND or CHECK_AVAILABILITY. Before anything reaches Google it's validated: a start in the future in a known time zone, at least the business's notice ahead and within its booking horizon (calendar_config), inside its opening hours (business_hours), an allowed length, and no double booking (Google's free/busy and Oqim's own bookings). Then the gate decides: the customer confirms the exact time first (unless the business sets require_customer_confirmation false, at autonomy 5); the change runs by itself only at autonomy 5 with the tool granted to the Calendar agent (PATCH /ai/agents/CALENDAR/config, tool_permissions: calendar.create_event, calendar.update_event, calendar.cancel_event are off until granted); otherwise it waits for a person here. An ambiguous request hands the conversation to a person (CALENDAR_AMBIGUITY). Each action keeps its provenance: the conversation, the customer message, the Seller turn (execution_id), the Calendar agent's run (agent_execution_id), the JEV decision, the confidence, what the agent extracted (encrypted at rest), the model, the prompt version and the event. A change reaches Google once: the event's id derives from the action. Events: ai.calendar.connected, ai.calendar.disconnected, ai.calendar.action_created, ai.calendar.action_updated; the metric ai_calendar_actions counts actions by type and status.
/ai/calendarai:readThe calendar page: configured (the platform set up its Google client; false means nothing can connect), enabled (platform policy lets Calendar AI run), redirect_url (register it in the Google client), the connection (ACTIVE, or NEEDS_REAUTH when Google refused the stored access; null when none), tools (the Calendar agent's granted calendar tools and the writes among them), upcoming (the next 50 confirmed meetings Oqim booked, with their titles) and pending (proposals waiting for the customer or a person, and failed changes that can be retried).
Example
/ai/calendar/connectai:manageStart connecting: returns the Google consent screen's url for the signed-in person to open. calendar_id is the calendar to book into: primary (the default) or another calendar's ID from its settings in Google Calendar. The sign-in state is signed, bound to the organization and the person, works once and expires after ten minutes; PKCE protects the code. Connecting again replaces the current connection (and revokes its access).
409 calendar_not_configured until the platform sets GOOGLE_OAUTH_CLIENT_ID and GOOGLE_OAUTH_CLIENT_SECRET. API keys can't connect: a person does, in the console.
Example
/ai/calendar/oauth/callbackSigned inWhere Google sends the person back (a browser navigation, with the session cookie). The state must be valid, unexpired, unused and the signed-in person's, still allowed to manage the AI; then the code is exchanged, the granted scopes checked (both calendar scopes are required), the chosen calendar tried, and the tokens stored encrypted. Answers 302 to the console's /ai/calendar with ?calendar=connected, or ?calendar_error= signed_out, forbidden, invalid_state, state_expired, state_used, wrong_account, access_denied, missing_code, exchange_failed, scopes_missing, no_refresh_token, calendar_unavailable, not_configured or internal.
Query: state, code, error
Example
/ai/calendar/disconnectai:manageDisconnect: Google's access is revoked, the tokens are deleted, and open proposals expire. The connection stays as history (status DISCONNECTED). 409 calendar_not_connected when there's none.
Example
/ai/calendar/actionsai:readThe Calendar agent's actions, newest first, with their provenance and what the agent extracted. Filter by status (AWAITING_CUSTOMER, PENDING, EXECUTING, COMPLETED, REJECTED, FAILED, EXPIRED), type (CREATE, UPDATE, CANCEL, FIND, CHECK_AVAILABILITY), conversation_id and business_id. validation lists the checks and their failure codes (TIME_MISSING, TIME_INVALID, TIME_ZONE_UNKNOWN, TIME_ZONE_MISMATCH, IN_THE_PAST, TOO_SOON, TOO_FAR, HOURS_NOT_SET, OUTSIDE_BUSINESS_HOURS, DURATION_TOO_SHORT, DURATION_TOO_LONG, DOUBLE_BOOKING, EVENT_NOT_FOUND, EVENT_PAST, UNCHANGED); result holds free times (alternatives on a refusal, free for CHECK_AVAILABILITY) or the bookings found. gate is AUTO, CUSTOMER, PERSON, REFUSED or READ.
Query: status, type, conversation_id, business_id, limit, offset
Example
/ai/calendar/actions/{id}/confirmai:operateConfirm a proposed booking, move or cancellation (AWAITING_CUSTOMER or PENDING), or retry a FAILED one: it's validated again against the calendar as it is now, then applied once. A person's confirmation doesn't need the customer's; tell the customer in the conversation.
409 calendar_action_not_pending (already decided, replaced or past), calendar_action_read_only (reads), calendar_not_connected, calendar_needs_reauth; 409 calendar_action_invalid with details.codes when the time can't be booked anymore (the action is REJECTED); 502 calendar_unavailable when Google refused or couldn't be reached (the action is FAILED and can be confirmed again). Audited as AI_CALENDAR_ACTION_CONFIRMED.
Example
/ai/calendar/actions/{id}/rejectai:operateSet a proposed action (or a failed one) aside: REJECTED, with you as decided_by. Nothing reaches the calendar. 409 calendar_action_not_pending otherwise. Audited as AI_CALENDAR_ACTION_REJECTED.
Example
AI Seller: case analysis and insights#
Every conversation in scope (private chats of accounts with Conversation Intelligence on, never ones marked personal, whether or not the AI Seller answered them) is analysed into its business's cases across five dimensions (PURCHASE_OUTCOME, LOST_REASON, DISSATISFACTION, DEMAND, SERVICE_QUALITY), with five scores (satisfaction 1–5, sentiment 1–5, purchase_intent 0–4, urgency 0–3, trust 0–3) and five flags (probabilities; true from 0.6). JEV decides, the LLM writes: your model writes each business's case catalog once (built-in templates without a model); JEV classifies a conversation in one request: LIVE 3 minutes after the customer's last message (at most every 15 minutes), FINAL when it ends (opt-out, lead won or lost, handoff resolved) or has been quiet 12 hours, when your model answers the dimensions JEV left unclear. Without JEV (off, or no key) conversations get FINAL analyses from your model only (analysis_mode LLM); with Conversation Intelligence off for the platform or the organization, nothing is analysed (OFF). JEV gets the masked recent transcript and a short business context, never ids. Your model writes only the per-conversation explanation and the board narratives, lazily per locale, grounded in the conversation or the board's numbers, sealed at rest. Updates arrive on the event stream: ai.analysis.updated, ai.insights.narrative_ready, ai.cases.generated and ai.insights.backfill. A business's catalog also holds two topic dimensions, CONTENT_TOPIC and COMMENT_TOPIC (what its posts and videos and its audience's comments are about), which conversations are never classified on: they are what the social boards group content and comments by (see Social content).
/ai/businesses/{id}/casesai:readA business's case catalog in display order (dimension, then position): every case not deleted. dimension and status filter; dimension accepts the five conversation dimensions and the two topic dimensions CONTENT_TOPIC and COMMENT_TOPIC. conversations_30d counts the conversations classified into a case whose last message is within 30 days; for a topic, posts_30d or comments_30d counts its posts or its audience's comments of the last 30 days instead. The whole catalog comes by default (limit 500).
Query: dimension, status, limit, offset
Example
/ai/businesses/{id}/casesai:manageAdd a case (source MANUAL), last in its dimension (a conversation dimension or a topic: CONTENT_TOPIC or COMMENT_TOPIC). name needs uz, ru and en; description is English and is JEV's criterion for the case: say which conversations belong to it and where it stops. key is UPPER_SNAKE_CASE, derived from the English name when left out, unique in the dimension; NONE_APPLY, UNCLEAR and NOT_APPLICABLE are reserved. At most 30 cases per dimension.
Audited as AI_CASE_CREATED. A key in use is 409 case_key_taken; a full dimension 409 case_limit.
Example
/ai/businesses/{id}/cases/generateai:manageWrite the business's catalog in the background: 6 to 12 cases per dimension (all, or the ones in dimensions), specific to its profile, offers and funnel, with names in Uzbek, Russian and English and English criteria. dimensions names the five conversation dimensions or the two topic dimensions (CONTENT_TOPIC, COMMENT_TOPIC); a topic run also reads the business's recent captions and video titles. Left out, the conversation dimensions are written. A sync whose business has no topics asks for them (and that is how a connected account gets its own topics without an owner asking), and posts or comments judged before the topics existed are judged on them once. replace: false (the default) only adds cases whose key is missing; replace: true rewrites the generated (LLM and TEMPLATE) cases and archives those the new set leaves out; people's cases stay. Each dimension is written separately; one whose answer was unusable (after a second try) gets the built-in templates, as does every dimension without a connected model. A business's first analysis writes its catalog by itself; cases added start the analysis of its past conversations.
Audited as AI_CASES_GENERATION_STARTED and AI_CASES_GENERATED. A job already running is 409 generation_running. At most 10 an hour per business.
Example
/ai/businesses/{id}/cases/generationai:readThe business's latest catalog generation: IDLE, RUNNING, DONE or FAILED (with error).
Example
/ai/cases/{id}ai:manageChange a case's name (the languages given), description, hint (null removes it), status (ACTIVE or ARCHIVED: archived cases leave new analyses) or position. The key never changes.
Audited as AI_CASE_UPDATED.
Example
/ai/cases/{id}ai:manageRemove a case from the catalog and from new analyses. Conversations classified into it keep their history (boards and drill-downs still name it).
Audited as AI_CASE_DELETED.
Example
/ai/conversations/{id}/analysisai:readA conversation's analysis: analysis_mode (JEV, LLM or OFF), phase (LIVE, or FINAL once a FINAL analysis read the customer's last message; LIVE again when they write after it; null before any), scores, flags, the classification per dimension (kind CASE, UNCLEAR or NOT_APPLICABLE; source JEV, LLM or HUMAN), up to 20 past analyses, and the explanation in locale (the ?locale, else Accept-Language, else en). explanation is null until asked for in that locale; its status is READY, PENDING, NOT_CLOSED (live conversations get none) or DISABLED (with error_code: ungrounded when the model wrote numbers the conversation doesn't hold, analysis_off, no_model, generation_failed, or the AI budget's code). Personal conversations are 404.
Query: locale
Example
/ai/conversations/{id}/analysis/refreshai:operateQueue a LIVE analysis of the conversation now. It needs JEV: without it 409 jev_unavailable; with Conversation Intelligence off 409 analysis_off.
At most 10 an hour per conversation and 300 per organization (429 ai_rate_limited).
Example
/ai/conversations/{id}/analysis/explainai:operateWrite the explanation of a closed conversation's current analysis in a locale (uz, ru or en): what happened and why, with key moments that point at the conversation's own messages. It's written from the conversation and its classification, checked (numbers must be ones the messages or the analysis hold; else written once more, then withheld), sealed and kept per conversation, locale and analysis; asking again changes nothing. A live conversation is 409 conversation_not_closed; one never analysed 409 not_analyzed.
At most 100 an hour per organization (429 ai_rate_limited).
Example
/ai/conversations/{id}/cases/{dimension}ai:operateCorrect the conversation's classification on a dimension: case_id (a case of its business in that dimension), or case_id null with kind UNCLEAR or NOT_APPLICABLE. The correction (source HUMAN) wins over later automatic analyses, except that a FINAL analysis replaces a correction made while the conversation was LIVE.
Audited as AI_CASE_ASSIGNMENT_CORRECTED with the previous classification. A conversation without a business can only take a kind (409 no_business for a case).
Example
/ai/insights/boards/{board}ai:readAn insight board: BUSINESS_HEALTH (every dimension, score and flag), SALES_OUTCOMES, LOST_REASONS, HAPPINESS, DEMAND or SERVICE_QUALITY (their dimensions with the scores and flags that explain them). The period is from–to (YYYY-MM-DD in the organization's time zone, both included; the last 30 days by default, 366 at most); the previous period is as long, right before. A conversation belongs to the period of its last message. KPIs are observed (conversations, customers_replied, purchases = leads won, avg_first_reply_seconds, handoffs, opt_outs), derived (conversion) or estimated by the AI (avg_satisfaction), each with the previous period's value and the relative change. Per dimension, total counts the conversations it applied to (a case or UNCLEAR); each case has its count, share of total, previous count, change, average confidence and daily trend; with business_id, every active case of the business is listed, even at zero. Scores come with their average, distribution and daily trend; flags with the share of analysed conversations. channel (TELEGRAM, INSTAGRAM or FACEBOOK) narrows it; without it, by_channel compares the channels (rows add up to conversations). backfill shows the analysis of past conversations while it runs; narrative is null until asked for in the locale (READY, PENDING, STALE when the data changed since, with the old text, or DISABLED).
Query: business_id, channel, from, to, locale
An unknown channel is 400 invalid_channel; an unknown board 404 board_not_found.
Example
/ai/insights/boards/{board}/narrativeai:operateWrite the board's narrative in a locale, for a business and a channel (or all) and a period, from the board's numbers (never from messages): a headline, findings with the cases they're about, actions. Every number in it must be in the data (a share may be written as a percentage); otherwise it's written once more, then withheld (DISABLED, error_code ungrounded). Nothing is written while the stored narrative's data is unchanged (status READY); a narrative is written at most once an hour. ai.insights.narrative_ready announces it.
Within the hour: 429 narrative_rate_limited with Retry-After. At most 60 an hour per organization (429 ai_rate_limited).
Example
/ai/insights/boards/{board}/cases/{case_id}/conversationsai:readThe conversations behind a case's number on the board: the period's conversations classified into it, newest first, with their channel, current phase, the classification's confidence, satisfaction and how they ended for the sale (outcome_case). The case must be in one of the board's dimensions.
Query: business_id, channel, from, to, limit, offset
Example
/ai/insights/backfillai:readThe latest analysis of past conversations for a business (or, without business_id, the organization-wide one): status IDLE, RUNNING, DONE, FAILED or CANCELLED, the conversations it found (total) and has done, failed or skipped, the mode (JEV, or LLM when the language model was allowed without JEV), what it's expected to cost and has cost. IDLE says how many conversations wait and what analysing them would cost.
Query: business_id
Example
/ai/insights/backfillai:operateAnalyse past conversations: every conversation in scope (never personal ones) without an analysis that read its newest message, newest first, for a business and a channel (or all) and active since a date. Quiet ones (12 hours) get FINAL analyses, others LIVE. JEV only unless allow_llm, which also lets the language model analyse without JEV, capped by the AI budget (the job stops when the budget does). It runs at 5 conversations a second per organization, resumes by itself after a restart and never sends anything. It also starts by itself when a business gets its case catalog and when a history import finishes. ai.insights.backfill reports progress (at most every 5 seconds).
Audited as AI_BACKFILL_STARTED. One runs at a time per business (409 backfill_running); without JEV and allow_llm 409 jev_unavailable; an unknown channel 400 invalid_channel. At most 20 an hour.
Example
/ai/insights/backfill/cancelai:operateStop the running analysis of past conversations for a business (business_id in the body), or the organization-wide one. What it analysed stays.
Audited as AI_BACKFILL_CANCELLED. Nothing running is 409 backfill_not_running.
Example
Social content#
What a business publishes on Instagram, Facebook and YouTube, and how people react to it. Oqim reads each account's posts and videos of the last 90 days with their metrics, their comments (newest first, down to the ones it has) and the account's daily metrics: every 6 hours for Instagram and Facebook, every 24 hours for YouTube (its quota is shared by the whole platform), and on request. Instagram and Facebook accounts come from connecting them (Channels); YouTube channels are added by their link and read with the platform's key, and a channel its owner connects (POST /channels/youtube/connect) also gives Oqim its private Analytics: watch time, average view duration, subscribers gained and lost, traffic sources, and the audience's age, gender and country. JEV judges each comment (intent, sentiment, whether it needs a reply, shows a wish to buy, names a competitor or is abusive, and which of the business's comment topics it is about) and each caption (a clear call to action, an offer or price, the strength of its opening, and which of the business's content topics it is about), in one request each, masked and without ids; without JEV the organization's AI model classifies intents and captions, in batches of 50 within a daily cap taken from its AI budget. A choice below 0.5 confidence is UNCLEAR; a flag counts when its probability is at least 0.6. The business's topics are written by the AI model from its profile, offers and recent captions (POST /ai/businesses/{id}/cases/generate with dimensions CONTENT_TOPIC and COMMENT_TOPIC), with a built-in fallback; a sync asks for them the first time it needs them, and posts or comments judged before the topics existed are judged on them once. The numbers are computed from the platforms' own metrics; the narratives are written by the AI model from the board's numbers only, and one that states a number the board doesn't have is withheld. Captions and comments are sealed at rest.
/channels/youtube/accountsaccounts:manageAdd a public YouTube channel by its link (youtube.com/@name, /channel/UC…, /user/…, /c/…, or a video link), its @handle or its channel id. The channel is resolved through the YouTube Data API (1 unit of the platform's quota; 2 for a video link) and stored with status PUBLIC and capability CONTENT; its first sync is queued. A channel the organization disconnected comes back.
422 channel_not_found when there's no such channel or the text isn't a YouTube link, handle or id; 422 validation_failed without url; 409 already_added for a channel already added; 409 provider_not_configured without YOUTUBE_API_KEY; 503 youtube_quota_exhausted (with Retry-After) when the day's quota is used up. Audited as YOUTUBE_CHANNEL_ADDED. Fires channels.account.updated.
Example
/channels/youtube/connectaccounts:manageThe Google consent URL to send the person to, so a YouTube channel's owner lets Oqim read that channel's private Analytics: watch time, average view duration, subscribers gained and lost, where viewers come from, and their age, gender and country. Scopes: youtube.readonly and yt-analytics.readonly. The state is signed, lasts 10 minutes and works once. A person signed in to the console connects accounts (API keys get 403 person_required); 409 provider_not_configured when the platform has no Google OAuth client.
The channel's public numbers still sync with the platform's YouTube API key without this; connecting adds the owner's own private numbers and the INSIGHTS capability. The callback is where Google returns.
Example
/channels/youtube/callbackPublicWhere Google returns the browser (public; the state is verified and used once, and the browser must carry the console session of the person who started the flow, so nobody can be tricked into connecting their channel to another organization). The code is exchanged, the channel is read with the token, and the account goes from PUBLIC to CONNECTED with capabilities CONTENT and INSIGHTS; its access and refresh tokens are sealed on the account and refreshed by themselves, and its first sync is queued. Redirects (302) to PUBLIC_APP_URL/channels?connected=<id>, or ?error= denied, state_invalid, state_expired, connected_elsewhere, not_configured or provider_error.
Query: code, state, error
Example
Audit log and notifications#
The organization's append-only audit trail and its in-console notifications.
/audit-logsaudit:readAudit entries, newest first. from and to take a time or a date; a plain date as to includes that whole day.
Query: action, actor_id, target_id, from, to, limit, offset
Example
/organization/support-sessionsaudit:readWho has looked at this organization as Oqim support, and why: every session, newest first, with the administrator, the reason they gave, when it began and ended, and how many requests it made. An organization can always see this.
Query: limit, offset
Example
/organization/support-sessions/{id}/accessesaudit:readEvery request one support session made against this organization, oldest first, refused ones included. Another organization's session is 404, the same as one that doesn't exist.
Query: limit, offset
Example
/support/enterPublicThe one-time link an administrator opens to start a support session on this hostname (a browser navigation). The ticket works once and expires after two minutes; unknown, used, expired and ended all look the same. On success it sets the session cookie, which expires with the session, and answers 302 to the console's /. Otherwise 302 to /login?support=invalid.
Query: ticket
Example
/notificationsorg:readRecent notifications and the unread count.
Example
/notifications/readorg:readMark notifications read: the given ids, or all of them when ids is omitted.
Example
Public#
Endpoints without authentication: the opt-out page, the console's sign-in screens and the OpenAPI document.
/public/opt-out/{token}PublicDescribe an opt-out link: the organization, the recipient (partly masked) and whether they already opted out. An altered or incomplete token is 404 invalid_link.
Example
/public/opt-out/{token}PublicConfirm the opt-out. Takes effect immediately for every campaign of that organization; repeating it is harmless.
Example
/public/inbox/attachments/{attachment_id}Public (signed link)Meta fetches a reply's file itself, so Oqim hands it out on a signed, short-lived URL (exp and sig query parameters, no session and no credentials in the URL). The signature covers the organization, the file and the expiry, so it opens that one file only. Streams with HTTP Range; an invalid, expired or tampered link answers 403 link_expired.
Query: exp, sig
Example
/public/configPublicDeployment settings the console needs before sign-in, including every webhook event type and whether the platform is in maintenance.
Example
/openapi.jsonPublicRedirect (302) to the OpenAPI 3.1 document for this API, which the docs serve at /docs/openapi.json. It's generated from this reference: point client generators, Postman or an HTTP client at it. Not rate limited.
Example
Administration API#
Platform-wide endpoints under /api/v1/admin, used by the admin console. They need a console session of a platform administrator with two-factor authentication on and verified in that session; API keys are refused. Every change is written to the audit log with the actor type PLATFORM_ADMIN.
/admin/overviewPlatform adminPlatform KPIs, 14 days of delivery outcomes (UTC days), the five newest open abuse reports and worker health counts. kpis.workers counts workers that are online; system_errors counts failed tasks today plus failed send jobs in the last 24 hours; unreachable_proxies counts proxies whose last health check couldn't reach them.
Example
/admin/organizationsPlatform adminAll organizations, newest first, with their size, active campaigns and 30-day volume. q matches the name, slug or ID.
Query: q, status, plan, limit, offset
Example
/admin/organizationsPlatform adminCreate an organization on a plan (FREE by default) with an existing user as its owner. An unknown owner_email is a 422: they sign up first.
Example
/admin/organizations/{id}Platform adminOne organization: usage against its plan limits, members, account and campaign counts by status, and its latest 20 audit entries.
Example
/admin/organizations/{id}Platform adminRename an organization, or change its plan or time zone. A new plan's limits apply at once.
Example
/admin/organizations/{id}/suspendPlatform adminSuspend an organization: its running and scheduled campaigns pause, messaging stops, members get read-only access and a notification with the reason, which is required.
Example
/admin/organizations/{id}/reactivatePlatform adminLift a suspension. Campaigns paused by it stay paused until the organization resumes them.
Example
/admin/usersPlatform adminUsers across all organizations, with how many organizations each belongs to. q matches the email, name or ID.
Query: q, limit, offset
Example
/admin/users/{id}/disablePlatform adminDisable a user: they can't sign in and their sessions end at once. You can't disable yourself (409 cannot_disable_self).
Example
/admin/users/{id}/enablePlatform adminLet a disabled user sign in again.
Example
/admin/accountsPlatform adminTelegram accounts across all organizations, most recently changed first. q matches the name, username, masked phone, organization or ID.
Query: status, q, limit, offset
Example
/admin/accounts/{id}/disablePlatform adminStop an account from sending, for example one that appears compromised. Campaigns using it pause and the organization is notified; it can't resume the account itself (409 disabled_by_admin). The optional note goes to the audit log.
Example
/admin/accounts/{id}/enablePlatform adminRestore an account a platform administrator disabled, so it can send again, and notify the organization. Campaigns paused because of it stay paused until the organization resumes them. Any other account is 409 not_disabled_by_admin.
Example
/admin/proxiesPlatform adminProxies across all organizations, newest first, with the organization, how many accounts use each one, and failed health checks over the last 24 hours. q matches the name, host, organization or ID; organization_id (or org) picks one organization. Credentials are never returned.
Query: status, protocol, organization_id, q, limit, offset
Example
/admin/proxies/{id}Platform adminOne proxy from any organization, with its latest 50 health checks, newest first.
Example
/admin/proxies/{id}/checkPlatform adminQueue a health check, like the organization's bulk check; the result lands on the proxy within seconds. A check already queued in the last minute counts. A disabled proxy isn't checked (409 proxy_disabled).
Example
/admin/proxies/{id}/disablePlatform adminStop Oqim connecting through a proxy, for example an open proxy that's being abused. The reason is required; the organization sees it on the proxy and in a notification. An account using the proxy stops sending, as it would on its next send: it moves to ERROR (PROXY_UNAVAILABLE) and its running and scheduled campaigns pause. It never falls back to a direct connection. The organization can't check, change or reassign the proxy until you enable it.
Example
/admin/proxies/{id}/enablePlatform adminEnable a disabled proxy and queue a health check; it shows as not checked until the result arrives. An account stopped because of the proxy stays stopped until the organization resumes it.
Example
/admin/campaignsPlatform adminCampaigns across all organizations. status takes several values separated by commas.
Query: q, status, organization_id, limit, offset
Example
/admin/campaigns/{id}Platform adminOne campaign from any organization.
Example
/admin/campaigns/{id}/pausePlatform adminPause any organization's running or scheduled campaign. The organization is notified and can't resume it (409 paused_by_admin).
Example
/admin/campaigns/{id}/resumePlatform adminResume a campaign a platform administrator paused: back to RUNNING if it had started, otherwise SCHEDULED. The organization is notified. A campaign paused for any other reason is 409 not_paused_by_admin; its organization resumes it.
Example
/admin/campaigns/{id}/cancelPlatform adminCancel any organization's unfinished campaign. The organization is notified; a cancelled campaign can't be restarted.
Example
/admin/workersPlatform adminWorker and scheduler processes. health is ONLINE (heartbeat under 20 s old), DEGRADED (under 90 s, or failing more than 20% of 50+ tasks) or OFFLINE.
Example
/admin/queuesPlatform adminEvery queue: counts, today's processed and failed tasks (UTC), latency_ms (age of the oldest pending task) and seven days of history. exists is false until a queue receives its first task.
Example
/admin/queues/{name}/pausePlatform adminStop workers taking tasks from a queue; tasks wait and nothing is dropped. Returns the queue. A queue that hasn't received a task yet can't be paused (409 queue_not_started).
Example
/admin/queues/{name}/resumePlatform adminResume a paused queue. Returns the queue.
Example
/admin/queues/{name}/tasksPlatform adminThe tasks in one state of a queue, to find the ones that are stuck. state is pending, active, scheduled, retry, archived or completed (default archived: the ones that failed every retry). Each task is described by its metadata only (type, attempts, last error, when it runs next, payload size); the payload itself is never returned, because it carries customers' message text. Pages are whole pages: offset must be a multiple of limit.
Query: state, limit, offset
Example
/admin/queues/{name}/tasks/{task}/retryPlatform adminPut one archived, retrying or scheduled task back in line to run now. Retrying loses nothing, so it needs no reason, but it is audited (QUEUE_TASK_RETRIED). A task in any other state is 409 task_not_retryable; one that finished or was removed since the list loaded is 404 task_not_found.
Example
/admin/queues/{name}/tasks/{task}/dropPlatform adminDelete one task. This cannot be undone, so a reason of at least 10 characters is required and is kept in the audit log beside what was dropped (QUEUE_TASK_DROPPED). A task that is running right now is 409 task_active: let the worker finish it, or pause the queue first. 204 on success.
Example
/admin/queues/{name}/retry-archivedPlatform adminRe-run every archived task in a queue. Returns how many were put back in line. Audited (QUEUE_ARCHIVED_RETRIED).
Example
/admin/queues/{name}/drop-archivedPlatform adminDelete every archived task in a queue, clearing its backlog of failures. Only the dead ones: nothing still waiting, scheduled or running is touched, because dropping a queued send would silently lose a customer's message. Returns how many were deleted. A reason of at least 10 characters is required and is audited (QUEUE_ARCHIVED_DROPPED).
Example
/admin/systemPlatform adminWhat this deployment is: the build, the database schema it is on against the one this build expects, and whether Postgres and Redis answer. schema.up_to_date is false when the database is behind the binary, which means a start that did not finish its migrations.
Example
/admin/organizations/{id}/support-sessionsPlatform adminOpen a read-only support session on one organization, to debug a customer's problem. A reason of at least 10 characters is required and the organization can read it; minutes (default 30, between 5 and 240) is a hard limit. The organization's notification feed and audit log both record it (SUPPORT_SESSION_STARTED). The response carries enter_url, which opens the session in the customer console's own hostname: it works once, for two minutes, and is returned only here. The session acts as a read-only role, every request under it is recorded, and it cannot reach this administration API.
Example
/admin/support-sessionsPlatform adminSupport sessions across every organization, newest first. status is ACTIVE, ENDED or EXPIRED.
Query: organization_id, status, limit, offset
Example
/admin/support-sessions/{id}/endPlatform adminEnd a session now. Any platform administrator may end any session: it is a safety valve, not a privilege of whoever opened it. Access stops at once, because the browser session it ran as is deleted with it. The organization is told (SUPPORT_SESSION_ENDED). Ending one that already ended is harmless.
Example
/admin/support-sessions/{id}/accessesPlatform adminEvery request made under one session, oldest first, refused ones included. The path only, never the query string, because a query can hold what someone searched for.
Query: limit, offset
Example
/admin/organizations/{id}/limitsPlatform adminWhat one organization may do, where that comes from, and how much of it is used: the plan's limits, the overrides the platform owner has set on top, the effective result, and current usage. A limit of 0 means unlimited.
Example
/admin/organizations/{id}/limitsPlatform adminSet one organization's limits on top of its plan, for a customer who needs more (or less) than their tier without moving to another. accounts, active_campaigns, recipients, team_members and webhooks take a whole number from 0 (unlimited) to 10,000,000; webhooks_enabled and api_access take a boolean. A limit you leave out follows the plan, and the whole set is replaced, so {"overrides": {}} clears them all. A field this API doesn't know is refused rather than ignored. A reason of at least 10 characters is required and is kept in the organization's audit log (ORGANIZATION_LIMITS_UPDATED) with the previous overrides. Lowering a limit below current usage deletes nothing: it only stops the organization adding more. Changing the plan leaves overrides in place.
Example
/admin/organizations/{id}/ai/budgetsPlatform adminOne organization's AI budgets (daily and monthly, each null when unset) with this period's spending, and their combined state. The same shape the organization sees at GET /ai/budgets.
Example
/admin/organizations/{id}/ai/budgetsPlatform adminSet one organization's AI budgets, with the same body as the organization's own PUT /ai/budgets (daily and monthly each take {limit_usd, alert_thresholds, hard_stop}, null removes that budget, a period you leave out stays as it is) plus a reason of at least 10 characters. The change is audited against the organization as a platform administrator's, with the reason and the previous budgets (AI_BUDGET_UPDATED).
Example
/admin/organizations/{id}/ai/costsPlatform adminWhat one organization has spent on AI for period today, week (the last 7 UTC days) or month (the calendar month so far, the default): in total and by business, agent (the capability), provider, model and mode, plus a day-by-day series. The same report the organization sees at GET /ai/costs.
Query: period
Example
/admin/abuse-reportsPlatform adminAbuse reports, newest first. Oqim files HIGH_FAILURE_RATE reports when it pauses a campaign for failures, OPT_OUT_SPIKE when it pauses one because recipients opt out right after receiving it, and ACCOUNT_RESTRICTIONS when an organization has three or more restricted accounts (at most one a day); administrators file MANUAL ones.
Query: status, limit, offset
Example
/admin/abuse-reportsPlatform adminFile a report by hand, optionally tied to one of the organization's campaigns or accounts. The summary is up to 500 characters; details takes any JSON object.
Example
/admin/abuse-reports/{id}Platform adminSet a report's status (OPEN, REVIEWING, RESOLVED or DISMISSED) and resolution note, and optionally suspend its organization in the same call.
Example
/admin/audit-logsPlatform adminAudit entries from every organization plus platform-level ones, filtered like the organization audit log.
Query: organization_id, action, actor_id, target_id, from, to, limit, offset
Example
/admin/settingsPlatform adminPlatform settings.
Example
/admin/settingsPlatform adminUpdate platform settings. Keys you leave out keep their value. failure_rate_pause_threshold and opt_out_spike_threshold are fractions between 0 and 1; a campaign pauses when at least opt_out_spike_min_count recipients opt out after receiving it and they make up opt_out_spike_threshold of its deliveries. Services pick changes up within about five seconds.
Example
/admin/billingPlatform adminPlan limits, organizations per plan, and a page of organizations with their usage over 30 days.
Query: q, plan, limit, offset
Example
/admin/ai/overviewPlatform adminAI across the platform: the AI switches, usage and cost today and this month, the tenants with the most spending over 30 days (with their failed and blocked runs of the last 24 hours), runs by agent over 24 hours, models used without a price, and provider health: attempts, failures and latency per provider over 24 hours, and the provider connections cooling off now after repeated failures.
Example
/admin/ai/modelsPlatform adminThe whole model registry: platform entries first, then organization entries with their organization's name. organization_id filters (platform for platform entries only); q matches the model ID or name.
Query: organization_id, provider_kind, q, limit, offset
Example
/admin/ai/modelsPlatform adminRegister a model: provider_kind and model_id as the provider lists it, capabilities (CHAT, STRUCTURED, EMBEDDINGS; default CHAT and STRUCTURED), context_window, and prices in USD per million tokens for input, output and embedding tokens (null when not known: calls are then unpriced). With organization_id the entry applies to that organization only, over the platform's. Nothing is pre-filled: enter the provider's current prices.
Example
/admin/ai/models/{id}Platform adminChange a registry entry's name, capabilities, context window, prices or enabled (a disabled model is hidden from organizations but still prices past and running usage). Its scope, provider and model ID don't change. New prices apply to calls from then on, within a minute everywhere.