REST API
Programmatic access to your Octocom data and configuration over plain HTTP — list and read conversations, manage workflows, articles and bot rules, search your product catalogue, record purchases, and more. This page explains what the API covers and where to find the full reference.
Octocom exposes a REST API for reading your customer support data and managing your bot's configuration programmatically. It's the right choice when you want to pull data into your own systems, run scripts and scheduled jobs, or integrate Octocom from a backend service — anywhere a stable HTTP contract is easier to work with than MCP.
Looking for the full, interactive reference? Every endpoint — with parameters, request/response schemas, and a "try it" console — lives at api-docs.octocom.ai. This page is the orientation; that's the spec.
Base URL
All endpoints are served from production under the /rest/v1 prefix:
https://api.octocom.ai/rest/v1The machine-readable OpenAPI 3 specification is available at https://api.octocom.ai/openapi.json — point your own codegen, Postman, or client library at it.
Authentication
Every request (except the public Browser Sessions endpoint) authenticates with an API key sent in the X-API-Key header:
X-API-Key: your_api_key_hereGenerate and manage your key from the API Key page under Settings in your Octocom dashboard. The key is scoped to your organization — keep it secret and treat it like a password. You can regenerate it at any time from the same page, which immediately invalidates the old key.
A first request
List the most recent conversations:
curl https://api.octocom.ai/rest/v1/conversations \
-H "X-API-Key: $OCTOCOM_API_KEY"List endpoints are paginated and filterable. For example, fetch the second page of conversations that were closed in a given window:
curl -G https://api.octocom.ai/rest/v1/conversations \
-H "X-API-Key: $OCTOCOM_API_KEY" \
--data-urlencode "status=closed" \
--data-urlencode "startDate=2026-01-01T00:00:00Z" \
--data-urlencode "endDate=2026-01-31T23:59:59Z" \
--data-urlencode "page=2"Conversations can also be filtered by handedOff state and by a metadataKey / metadataValue pair (e.g. to find the conversation tied to a specific Zendesk ticket). See the reference for the full set of parameters on each endpoint.
To find whether a customer has contacted support, filter directly by their
email address. Add businessSlug when the same organization contains multiple
businesses, and use imported=all if migrated help-desk history should count:
curl -G https://api.octocom.ai/rest/v1/conversations \
-H "X-API-Key: $OCTOCOM_API_KEY" \
--data-urlencode "[email protected]" \
--data-urlencode "businessSlug=your-business" \
--data-urlencode "imported=all"Conversation list items include the associated customer, derived status,
closedAt, and rating. GET /rest/v1/conversations/{id} exposes the same
rating as { "score": 5, "comment": "..." }, or null when the customer has
not submitted one.
What you can access
The API is organized into resource groups. Most are read-only; the configuration resources (Workflows, Articles, Bot Rules) also support create / update / delete with version history and restore, mirroring what you can do in the dashboard.
| Resource group | What it covers |
|---|---|
| Conversations | List and read conversations with full message history, AI classifications, and agent notes. Add and remove tags, filter by tag, read and write internal notes, add events, set metadata, send outbound email, and import history from another system. |
| Customers | List customer profiles and contact info; update a customer and set customer metadata. |
| Custom Data | AI-classified data extracted from conversations, for analytics and reporting. |
| Businesses | List the businesses in your organization (use their slugs when calling business-scoped endpoints). |
| Products | Search a business's product catalogue — typed queries, search-as-you-type suggestions, voice, photos, and similar products. See below. |
| Users & Teams | List the team members, agents, and team structure in your organization. |
| Macros | Response templates used by human agents. |
| Workflows | Manage the multi-step playbooks that guide the bot — create, update, delete, list version history, and restore. |
| Articles | Manage knowledge base articles the AI answers from — full CRUD with versioning and soft-delete / restore. |
| Bot Rules | Manage the custom instructions that steer bot behavior — full CRUD with versioning and restore. |
| Bot Conversations | Start a conversation with the bot and send it messages programmatically (useful for automated testing or embedding). |
| Conversation API | Two-way messaging for a channel you own: push customer messages in and receive every bot or agent reply on a webhook. See below. |
| Google Sheets | Append rows to a Google Sheet — to a named tab, or the first one by default (share the sheet with the service account shown in the reference as an editor). |
| Browser Sessions | Record a purchase against a browser session for custom sale attribution. No API key required — rate limited by IP. |
Two-way messaging over HTTP
If you own the channel the customer is on — your own WhatsApp number through your own provider, an in-house app, a system we have no integration with — you can run the whole conversation over HTTP. You push the customer's messages in; every reply comes back on a webhook.
Two endpoints write, and the existing GET /rest/v1/conversations/{id} reads.
POST /rest/v1/conversations starts a thread:
curl -X POST https://api.octocom.ai/rest/v1/conversations \
-H "X-API-Key: $OCTOCOM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"businessSlug": "your-business",
"customer": { "name": "Maria Gonzalez", "phone": "+52 55 1234 5678" },
"messages": [
{ "sender": "bot", "content": "Hi Maria, your order #A-33915 has shipped...", "timestamp": "2026-08-18T08:55:10Z" },
{ "sender": "customer", "content": "When will it arrive?", "timestamp": "2026-08-18T09:12:44Z" }
],
"externalId": "your-msg-8891",
"tags": ["whatsapp"],
"metadata": { "orderNumber": "A-33915" }
}'It answers 201 immediately with conversationId, publicId and messageIds. You never wait for the bot — its reply arrives on your webhook.
POST /rest/v1/conversations/{id}/messages appends every reply after the first, and answers 202 with the stored messageId and whether a human currently owns the conversation.
Three things worth knowing:
Seed the message the customer is replying to. When someone answers a transactional message you sent from your own system, their reply often makes no sense alone — "when will it arrive?" needs the order confirmation for context. Include that original message in messages with "sender": "bot" and the bot can see what is being answered. Seeded messages are context only: they never trigger a bot turn and are never sent anywhere. It is optional, but it noticeably improves answers, and it gives your agents the full thread if the conversation is handed off. The last entry in messages must be from the customer.
Tag conversations to route them. tags and metadata are both accepted at creation and are what webhook filters match on, so one integration's traffic can be routed to its own endpoint without inspecting payloads at your end.
Supply externalId and retries are free. It is your own id for the message you are pushing. Replaying one returns the ids from the first call and stores nothing, so a retry after a timeout can't produce a duplicate.
Handoff is transparent. A handed-off conversation keeps accepting messages exactly as before, and replies keep arriving on the same webhook — only message.sender changes from bot to agent. You do not need to detect it or do anything differently.
The webhook is the intended way to receive replies and you should not need to poll. GET /rest/v1/conversations/{id} exists so you can verify state, recover if a delivery was missed, and debug while building.
Finding conversations by tag
GET /rest/v1/conversations?tag=escalated-web returns only conversations
carrying a tag with that exact title. Use it instead of paging every open
conversation to find the handful you tagged.
curl -H "X-API-Key: $OCTOCOM_API_KEY" \
"https://api.octocom.ai/rest/v1/conversations?tag=escalated-web&status=open"Tags are written with POST /rest/v1/conversations/tags and removed with
DELETE /rest/v1/conversations/tags, both taking { "conversationId": "...", "tag": "..." }. Removing only unlinks the tag from that conversation — the tag
itself stays available to your organization and on any other conversation.
Removing a tag the conversation does not have returns 404, so a removal that
did nothing is never reported as success.
A tag-driven queue should remove its tag as part of processing. Without that, the same conversations come back on every poll.
To be notified as tags change rather than polling for them, use the Tag Modified event handler or a webhook.
Internal notes
Notes are visible to your agents in the Octocom dashboard and are never shown to the customer.
| Endpoint | What it does |
|---|---|
POST /rest/v1/conversations/{id}/notes | Add a note ({ "text": "..." }) |
GET /rest/v1/conversations/{id}/notes | List every note, newest first |
{id} accepts either the conversation UUID or its public ID.
Notes created this way are attributed to api rather than to a user, and appear
in the timeline labelled as such, so an agent can tell them from one a colleague
typed. Each returned note carries authorType (user, api, or automation)
and authorName.
From Python, use
add_conversation_note
and
get_conversation_notes
instead of calling these directly.
Importing conversation history
POST /rest/v1/conversations/import brings conversations that happened somewhere else into Octocom — ticket history you're migrating off another helpdesk, or support handled in a system of your own that you want searchable alongside everything else. See the reference for the request format.
Two things to know before you use it:
Imported conversations are archive records, not live tickets. The bot never answers them, they're never routed into an agent queue, and they're read-only in the dashboard. They're also excluded from bot quality scoring and automation metrics, so migrating years of history won't distort your reporting. If you need a thread your agents can actually keep working, don't import it — resolve it in your old system and let the customer's next reply open a normal Octocom conversation.
They're hidden from GET /rest/v1/conversations by default. Pass imported=yes to list only imported conversations, or imported=all for both. This applies whether or not you use the import endpoint yourself.
Every timestamp you supply is stored as given, so history keeps its real dates. Give each record an externalId from the source system and retries are safe — re-importing the same one returns status: "duplicate" instead of creating a second copy. Batches are applied per item rather than all-or-nothing, so read the per-item results rather than relying on the status code alone.
Product search
If Storefront Search is set up for a business, the same search pipeline is available over REST — for a storefront you render server-side, a mobile app, or anywhere our browser SDK doesn't fit. Six POST endpoints, each taking the business's businessSlug:
| Endpoint | What it does | Rate limit |
|---|---|---|
/products/search | Full search for a typed query. | 60/min |
/products/search/suggest | Search-as-you-type panel: query completions, a few top hits, and category/brand facets. | 600/min |
/products/search/panel | Full search answered in the suggest panel's shape — results and facets, count, refinements. | 60/min |
/products/search/voice | Transcribe a recorded spoken query and search for it, in one request. | 30/min |
/products/search/image | Search by photo. | 30/min |
/products/search/similar | "More like this" for a product page. | 300/min |
/panel is /search and /suggest in one request. It runs the complete pipeline — so its ranking is /search's, not the keyword probe's — and returns the same fields /suggest does: products, categories, brands, totalCount, suggestions, plus relaxed. Use it for a submitted query when you render the surrounding furniture (a facet rail, a result count, "narrow your search" chips) yourself: the alternative is calling /search and /suggest together, which costs a wasted search and produces chips describing a differently-ranked result set. It costs the same as /search; it is not a substitute for /suggest on a keystroke.
Note that totalCount is the number of products matching the query at all, not the length of products — that's capped by topK.
Every ranking endpoint answers with the same results array — { id, score, product }, where product is the untouched product object as ingested from your store feed. You render it with the fields you already have; we don't impose a product schema on you. Rate limits are per organization, and the business must belong to the organization the API key is scoped to.
A typed search:
curl https://api.octocom.ai/rest/v1/products/search \
-H "X-API-Key: $OCTOCOM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"businessSlug":"my-store","query":"black leather office chair","topK":20}'The same query with the panel data, for a results page that shows facets and a count alongside:
curl https://api.octocom.ai/rest/v1/products/search/panel \
-H "X-API-Key: $OCTOCOM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"businessSlug":"my-store","query":"black leather office chair","topK":20}'{
"data": {
"products": [
{
"id": "...",
"score": 0.82,
"product": { "...": "your feed's product" }
}
],
"suggestions": [
{ "text": "black leather office chair swivel", "role": "attribute" }
],
"categories": [
{ "text": "Office Chairs", "url": "https://...", "count": 34 }
],
"brands": [],
"totalCount": 41,
"relaxed": false
}
}Facet chips are only returned for values your feed gives a navigable URL for, so categories and brands come back empty for feeds that carry names but no links. That vocabulary still reaches you through suggestions, which need no URL.
Voice
Send one short recording — a single spoken query. This is not a streaming endpoint: detect end-of-speech on the client, then post the clip. The transcript is fed to the same pipeline as a typed query, so you get the transcript and its results in one round trip.
Audio goes in the audio field, base64-encoded (a data: URL works too), up to 1 MB decoded — about 15 seconds, far more than a search query needs. audio/webm, ogg, mp4, mpeg, wav, aac and flac are accepted.
curl https://api.octocom.ai/rest/v1/products/search/voice \
-H "X-API-Key: $OCTOCOM_API_KEY" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg audio "$(base64 -i query.webm)" \
'{businessSlug: "my-store", audio: $audio, mimeType: "audio/webm"}')"{
"data": {
"transcript": "black leather office chair",
"noSpeech": false,
"language": "en",
"results": [{ "id": "SKU-12345", "score": 0.83, "product": { "…": "…" } }],
"relaxed": false
}
}noSpeech: truemeans the recording carried nothing recognizable — a normal outcome, and answered as200with no results. Tell the shopper "didn't catch that" rather than "no products match".- Language is detected between a small candidate set rather than freely, which is much more accurate on a three-word utterance. Omit
languageto use the business's configured voice languages; pass one code ("el") to state it outright, or a comma-separated list ("lt,en,ru") to detect between them. The language actually heard comes back in the response. - Recognition is biased with your catalogue's own mined vocabulary, so brand and product names survive.
- Pass
"mode": "transcribe"to get only the transcript and run the search yourself later — for a dictate-and-review flow where the shopper edits before submitting. - Unlike the storefront widget's mic, this endpoint doesn't require the dashboard's Voice search switch. That toggle governs what shoppers get on your storefront; this surface is your own authenticated backend.
Image
The photo is read by a vision model whose understanding drives retrieval directly. Base64 in the image field (or a data: URL), up to 6 MB decoded, as image/jpeg, png, webp or gif. Downscale big photos to around 1024px on the long edge first — it costs nothing in quality and is most of the upload latency.
curl https://api.octocom.ai/rest/v1/products/search/image \
-H "X-API-Key: $OCTOCOM_API_KEY" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg image "$(base64 -i chair.jpg)" \
'{businessSlug: "my-store", image: $image, mimeType: "image/jpeg", query: "in blue"}')"The optional query refines the photo ("in blue", "cheaper") rather than replacing it. The response adds saw — what the model read off the photo:
{ "data": { "results": [], "relaxed": false, "saw": ["office chair"] } }Show saw to the shopper ("Showing results for: office chair"). A misread photo then reads as a misread instead of looking like an empty catalogue. This endpoint can't start retrieving until the photo is understood, so it's the slowest of the five.
saw is the item type read off the photo, not the query that was run: retrieval also uses the colour, material, size and setting the model read, which is what distinguishes the photographed product from the rest of its category. The full derived query is shown in the dashboard's Storefront Search preview if you need to debug a result.
Similar products
A "you may also like" row, keyed by the product's id as it appears in your feed — the same id the other endpoints return.
curl https://api.octocom.ai/rest/v1/products/search/similar \
-H "X-API-Key: $OCTOCOM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"businessSlug":"my-store","productId":"SKU-12345","topK":8}'{
"data": {
"results": [{ "id": "SKU-67890", "score": 0.71, "product": { "…": "…" } }],
"source": { "id": "SKU-12345", "name": "Aera Office Chair" }
}
}Results never include the source product, and are reranked using its own indexed type and category — which is what keeps a phone's cases out of its neighbours. source is null when the id isn't in the catalogue index; that's different from a product with no neighbours, and usually worth handling differently. Costing no model or embedding call, this is the cheapest of the five and safe to call on every product-page view.
Pagination, rate limits, and errors
- Pagination — list endpoints accept a
pagequery parameter (1-based). - Rate limiting — requests are rate limited; exceeding the limit returns
429 Too Many Requests. Back off and retry. - Errors — the API uses standard HTTP status codes:
| Status | Meaning |
|---|---|
200 | Success |
400 | Malformed request — e.g. an unreadable, oversized, or unsupported upload |
401 | Invalid or missing API key |
404 | Resource not found |
429 | Rate limit exceeded |
500 | Server error |
REST API vs. MCP
Octocom offers two programmatic surfaces, and they're complementary:
- REST API — a fixed, versioned HTTP contract. Best for backend integrations, data pipelines, scheduled jobs, dashboards, and anything you want to script deterministically.
- MCP — the same capabilities (and more) exposed to AI agents like Claude and ChatGPT, so an agent can configure, debug, and test your bot in natural language. Best for hands-on, exploratory, and "do this for me" work.
If you're writing code, start with the REST API. If you're driving an AI agent, use MCP.
Need access or help?
API keys are available from the dashboard today. If anything in the reference is unclear or you need an endpoint that isn't there yet, reach out — we're expanding the API and use your requests to prioritize.