REST API and keys
Create API keys and use the PepoChat REST API to read conversations, reply as your team, sync contacts, add snippets and talk to the agent.
The PepoChat REST API lets your own systems read and act on a workspace: list and update conversations, reply to visitors as your team, sync contacts, add knowledge snippets and send messages to the agent. Every request carries an API key created on the Developers page, and the API is available on every plan, including Free.
Create an API key
Open Configure → Developers
The API keys card is at the top of the page. Only workspace owners and admins can create and revoke keys.
Name the key and click Create key
Type a New key name of up to 60 characters, usually the system that will use it, and click Create key.
Copy the key
The Your new API key dialog shows the full key once; copy it and click Done. Only a hash is stored, so a lost key cannot be shown again: revoke it and create another.
Each key in the list shows its name, its first characters, when it was created and when it was last used (refreshed at most every five minutes). Revoke warns "Anything using this key stops working at once."; click Revoke key to confirm.
Base URL and authentication
Copy the base URL from the API keys card, where it appears in the Try it sample and the reference link:
https://flippant-cheetah-84.convex.site/api/v1
Send the key as a bearer token in the Authorization header of every request:
curl "https://flippant-cheetah-84.convex.site/api/v1/conversations?limit=5" \
-H "Authorization: Bearer pc_live_..."
A key is pc_live_ followed by 32 letters and digits, and every call reads and changes only the workspace it belongs to. The OpenAPI 3.1 description is served at /api/v1/openapi.json without a key. Responses carry no CORS headers, so call the API from your server and keep keys out of browser code.
Endpoints
Paths are relative to the base URL. Bodies are JSON, and every time is a Unix timestamp in milliseconds.
| Method and path | What it does |
|---|---|
GET /me | The workspace the key belongs to: workspaceId and plan. |
GET /conversations | Conversations, newest first. Optional status: unresolved, escalated or resolved. |
GET /conversations/{id} | One conversation with its transcript. |
PATCH /conversations/{id} | Change status, priority or tags. |
POST /conversations/{id}/messages | Reply to the visitor as your team. |
POST /chat | Send a visitor message to the agent and wait for the answer. |
GET /contacts | Contacts, most recently seen first. |
POST /contacts | Create or update up to 500 contacts by email. |
GET /contacts/{id} | One contact. |
PATCH /contacts/{id} | Update a contact. |
GET /knowledge/snippets | Text snippets and Q&A pairs, most recently updated first. |
POST /knowledge/snippets | Add a text snippet or a Q&A pair. |
GET /openapi.json | The OpenAPI reference. No key needed. |
Lists and pagination
The four list endpoints take limit (1 to 100, default 25; other values are clamped) and cursor, and return { "items": [...], "cursor": "..." }. Pass cursor back for the next page and stop when it is null. Conversation lists leave out chats from Test the agent, so a page can hold fewer items than limit while more follow.
Conversations
| Field | Contents |
|---|---|
id, createdAt | The conversation id and when it started. |
status | unresolved, escalated or resolved. |
channel | widget, hosted, api, whatsapp, messenger, instagram, voice or phone. |
priority | low, normal, high or urgent. |
tags, topic, assignedTo | Inbox tags, the topic label and the user id of the assigned teammate. |
rating | The visitor's feedback: up, down or null. |
escalatedAt, resolvedAt | The first time each happened, or null. |
messageCount, lastMessage | The message count and the latest message (text, role, createdAt). |
visitor | sessionId, name, email, emailVerified, country, pageUrl. |
messages, messagesTruncated | Only on GET /conversations/{id}: up to 200 messages with role (visitor, assistant or operator), author (the teammate's name), text and createdAt, without tool calls. messagesTruncated is true when there are more. |
PATCH /conversations/{id} takes any of three fields:
{ "status": "resolved", "priority": "high", "tags": ["billing", "refund"] }
- A status change records
escalatedAtorresolvedAtthe first time and sends the matching webhook event. Settingunresolvedreopens a conversation. tagsreplaces the whole list. Tags are stored in lowercase and cut to 30 characters; a conversation carries at most 10, and a workspace at most 200 different tags.
POST /conversations/{id}/messages takes text (1 to 4,000 characters) and an optional author, the name shown on the reply (up to 80 characters, API when left out). The visitor sees a reply from your team. An unresolved conversation becomes escalated and the agent stops answering it. A resolved conversation has to be reopened first. These replies reach the web chat and the inbox, not WhatsApp, Messenger or Instagram.
Talk to the agent
POST /chat sends a message as a visitor and waits up to 60 seconds for the agent's answer.
curl -X POST "https://flippant-cheetah-84.convex.site/api/v1/chat" \
-H "Authorization: Bearer pc_live_..." \
-H "Content-Type: application/json" \
-d '{"message": "Do you ship to Canada?", "visitor": {"name": "Ada", "email": "[email protected]", "externalId": "user_123", "attributes": {"plan": "pro"}}}'
{ "conversationId": "k17...", "status": "unresolved", "reply": "Yes, we ship to Canada...", "pending": false }
messageis 1 to 4,000 characters. Leave outconversationIdto start a conversation (channelapi, opening with your greeting); send it back to continue one.visitor(optional, read only when a conversation starts) is trusted because the caller holds your key: anemailcounts as verified and files the contact without a new-lead notification, and anexternalIdmarks the visitor as identified, as an identity token would. Unnamed visitors show as "API visitor".- If the agent has not answered within the wait, the response is
202with"reply": nulland"pending": true. Read the answer later withGET /conversations/{id}. - Once a conversation is escalated to your team the agent stops answering it, so the call ends with
202unless a teammate replies within the wait. - Replies count toward the monthly AI replies like widget replies (Plans and billing). On Free, once they are used up, the conversation goes to your team and
replyholds the hand-off notice. The hourly ceiling on anonymous web chat does not apply. - The visitor session behind an API conversation lasts 24 hours and is extended while messages arrive. After it expires, that conversation answers
403"Invalid session"; start a new one.
Contacts
POST /contacts creates or updates up to 500 contacts by email in one call:
{
"contacts": [
{
"email": "[email protected]",
"name": "Ada Lovelace",
"company": "Analytical Engines",
"externalId": "user_123",
"attributes": { "plan": "pro", "seats": 5 }
}
]
}
The response gives created, updated, skipped (emails that are not valid addresses) and ids in request order. Like a CSV import, these writes never trigger a new-lead notification or the lead.created webhook, and new contacts show the source API on the Contacts page.
Fields you send replace the stored values; attributes merge key by key. PATCH /contacts/{id} takes the same fields except email. Longer text is cut: name to 100 characters, phone to 50, company to 100, externalId to 200. Attributes follow the rules in Visitor identity, and entries that break them are dropped without an error.
Knowledge snippets
POST /knowledge/snippets adds a text snippet ("kind": "text" with title and body) or a Q&A pair ("kind": "qa" with question and answer). It follows the rules and plan limits of Text snippets and Q&A pairs and returns 201 with the new id; the snippet reaches the knowledge base in the background.
Limits
- 600 requests a minute per key, in bursts of up to 100. Past that the API answers
429with aRetry-Afterheader in seconds. POST /chatalso has its own limit of 60 calls a minute per key, in bursts of up to 20.- 10 active keys per workspace, and up to 20 new keys an hour.
Common errors
Every error has the same shape:
{ "error": { "code": "bad_request", "message": "status must be unresolved, escalated or resolved" } }
| Status | Message | Meaning |
|---|---|---|
| 401 | Send an API key as Authorization: Bearer pc_live_… | The header is missing, or the key is not in the pc_live_ format. |
| 401 | This API key is not valid | The key was revoked or never existed. |
| 403 | Invalid session | The visitor session of an API conversation expired. Start a new conversation. |
| 404 | Conversation not found | The id is wrong or belongs to another workspace. Contact not found is the same for contacts. |
| 404 | No such route | The path is not part of the API. |
| 400 | text must be 1 to 4000 characters | The reply is empty or too long. /chat says message must be 1 to 4000 characters. |
| 400 | The conversation is resolved; reopen it first | Set status to unresolved before replying. |
| 400 | The conversation is resolved; start a new one | Call /chat without conversationId. |
| 400 | A conversation can carry at most 10 tags | Send fewer tags. |
| 400 | contacts must hold 1 to 500 entries | Split the import into batches. |
| 400 | cursor is not valid: pass the cursor returned with the previous page of the same list, or leave it out for the first page | Use the cursor from the previous page of the same list. |
| 429 | Too many requests | Wait the number of seconds in Retry-After. The /chat limit answers Too many chat requests, without that header. |
| 500 | Something went wrong on our side | Retry later. |
Something missing or wrong on this page? Tell us and we will fix it.
