Your scripts and systems call the EPAV Desk REST API at your own support address, for example https://support.example.com/api/v1, using an API key and secret that belong to an agent. Every call acts as that agent and can do only what the agent's role allows.
Create an API key
- Open Admin > Teammates > Agents and click the agent the key is for. The API keys block appears only on a saved agent, so create the agent first if it does not exist yet.
- In API keys, click Generate API key.
- The API key generated window shows the API key and the Secret. Copy both now: the secret is shown only once and cannot be looked up later.
- Click Close.
To generate keys, an agent needs the Manage users permission. Each agent has at most one key at a time.
Regenerate or revoke a key
On the agent's page, the API keys block shows the current key, when it was Last used, and two buttons:
- Regenerate issues a new key and secret. The old pair stops working immediately, so update your scripts right away.
- Revoke deletes the key. Calls made with it are refused from then on.
Both buttons act at once, without asking for confirmation. A key also stops working when its agent is disabled or deleted.
Use a separate agent for integrations
An API key can do everything its agent can. For an integration, create a separate agent with a role that grants only the permissions the integration needs, and do not add this agent to any team, so that no conversations are assigned to it. To cut the integration off, disable that agent: the people on your team are not affected. Roles are explained in Roles and permissions.
While it is enabled, a separate agent counts toward the agents included in your plan, see Plans, limits and billing. Keys made with Create key on Admin > AI > Connect your AI are the exception: they are meant for the knowledge base and do not count.
Authenticate requests
Send the key and the secret in the Authorization header in one of two forms:
Authorization: token KEY:SECRETAuthorization: Basic BASE64, whereBASE64is the textKEY:SECRETencoded in Base64.
curl https://support.example.com/api/v1/agents/me \
-H "Authorization: token $SUPPORT_API_KEY:$SUPPORT_API_SECRET"
With -u, curl builds the Basic form for you:
curl -u "$SUPPORT_API_KEY:$SUPPORT_API_SECRET" https://support.example.com/api/v1/agents/me
GET /api/v1/agents/me returns the agent the key belongs to, which makes it a quick test for a new key.
Write token in lower case and Basic with a capital B. Bearer does not work with API keys. Requests made with an API key need no session cookie and no CSRF token.
AI apps such as Claude, ChatGPT or Cursor do not need an API key: they connect on Admin > AI > Connect your AI, see Connect your AI (MCP).
Base URL: your support address
All REST API paths start with https://<your support address>/api/v1. Your support address is the address where you open the panel: the one we gave you when you signed up, or the one you switched to in the cabinet, such as your own domain, see Your account and your site. After you switch to a new address the old one keeps working, but use the current address in new integrations.
Responses and errors
Responses are JSON. A successful call returns status and data, shortened here:
{ "status": "success", "data": { "id": 42, "first_name": "Orders", "last_name": "Integration" } }
A failed call returns status, message and error_type:
{ "status": "error", "message": "Permission denied", "data": null, "error_type": "PermissionException" }
| HTTP status | error_type |
Meaning |
|---|---|---|
| 400 | InputException |
a field is missing or has an invalid value |
| 401 | GeneralException |
the key is not valid |
| 403 | PermissionException |
the agent's role does not allow this |
| 404 | NotFoundException |
the object does not exist |
| 409 | ConflictException |
the object already exists or is in use |
| 429 | none | too many requests, see the rate limits below |
| 500 | GeneralException |
something failed on the server |
The message is written in the language of your panel, so let your code depend on the HTTP status and error_type, not on the text. File downloads, such as a conversation transcript, return the file itself instead of JSON.
When a request is refused
The messages below are those of a panel in English:
- 401 "Invalid credential": the key or the secret is wrong, the key was revoked or regenerated, or its agent is disabled.
- 401 "Invalid or expired session" or 403 "CSRF token mismatch": the
Authorizationheader was not recognized, so the request was handled as if it came from a browser. Check the spelling oftokenorBasicand the colon between key and secret. - 403 "Permission denied": the key is valid, but the agent's role does not allow this endpoint.
Paginated lists
Endpoints that return long lists, such as conversations, messages or contacts, take the query parameters page and page_size. page_size is 30 by default and at most 500. Inside data, such responses hold results, total, per_page, total_pages and page.
curl "https://support.example.com/api/v1/conversations/all?page=2&page_size=100" \
-H "Authorization: token $SUPPORT_API_KEY:$SUPPORT_API_SECRET"
Rate limits
EPAV Desk limits how many requests one IP address can send. A request over the limit gets HTTP 429 with a Retry-After header: wait that many seconds, then send the request again. Handle 429 this way on every endpoint your integration calls.
Endpoints not described here
This article covers authentication, the common rules of all responses and the knowledge base endpoints. The panel itself works through the same REST API, with the same permissions, so an action you can do in the panel can also be done with an API key. To see which request an action sends, open your browser's developer tools on the Network tab and do the action in the panel. For help with an endpoint, write to support@epavdesk.com.
Knowledge base endpoints
These endpoints fill the knowledge base the AI assistant answers from. They need the Manage AI features permission.
| Endpoint | What it does |
|---|---|
POST /api/v1/ai/snippets/upsert |
creates or updates one article from text you send, matched by your own key |
POST /api/v1/ai/snippets/import-file |
reads one file and stores it as an article, within the same request |
POST /api/v1/ai/snippets/import-url |
reads one web page and stores it as an article |
POST /api/v1/ai/snippets/sitemap |
lists the pages of a sitemap without importing them |
POST /api/v1/ai/snippets/import |
imports several files and links, sitemaps included, in the background |
GET /api/v1/ai/snippets/import/status |
shows the progress and the log of the background import |
POST /api/v1/ai/snippets/{id}/reindex |
indexes an article again, for example after indexing failed |
For plain reading and editing there are also GET /api/v1/ai/snippets for the list, POST /api/v1/ai/snippets for a new article with title, content and enabled, and GET, PUT and DELETE on /api/v1/ai/snippets/{id}. Doing the same by hand in the panel is described in Importing files, links and sitemaps.
Upsert an article from your own text
POST /api/v1/ai/snippets/upsert takes JSON with these fields:
key: your own ID for the article, 1 to 1000 bytes of text, for examplecrm:faq/42. Sending the same key again updates the same article instead of adding a copy.titleandcontent: the article itself.enabled(optional):trueorfalse. A new article is enabled by default; without this field an existing article keeps its state.on_local_edits(optional):overwrite, the default, replaces an article someone edited in the panel;keepleaves such an edit in place.adopt_id(optional): the ID of an existing article without a key, which then takes over this key instead of a new article being created.dry_run(optional):trueshows the outcome without writing anything.
curl https://support.example.com/api/v1/ai/snippets/upsert \
-H "Authorization: token $SUPPORT_API_KEY:$SUPPORT_API_SECRET" \
-H "Content-Type: application/json" \
-d '{"key": "crm:faq/42", "title": "Delivery times", "content": "Orders ship within 1-2 working days.", "dry_run": true}'
The answer holds item (the article), outcome, previous (the article as it was before the write, useful for an undo) and local_edits (whether the article was edited in the panel). outcome is added, updated, unchanged, adopted or conflict; on a dry run it is would_add, would_update, would_adopt, unchanged or conflict. A conflict happens with keep when the article was edited in the panel and your text changed too: nothing is written.
An invalid key returns 400. With adopt_id, a key that already belongs to another article, or an adopt_id article that already has a key, returns 409. An article that comes from a Confluence connection also returns 409: Confluence articles are updated only from Confluence.
Import files, pages and sitemaps
POST /api/v1/ai/snippets/import-file: a multipart form with onefile(PDF, HTML, Markdown or plain text) and, as text fields, the samekey,title,enabled,on_local_edits,adopt_idanddry_runas the upsert. Without akey, the key isfile://plus the file name, so uploading the same file again updates the same article. The answer also hasextractedwith the title and the number of characters read, and the text itself on a dry run.POST /api/v1/ai/snippets/import-url: JSON withurland the same optional fields; the page address becomes the key.POST /api/v1/ai/snippets/sitemap: JSON withurl; it returnsurls(at most 300),foundandtruncated, and imports nothing.POST /api/v1/ai/snippets/import: a multipart form with any number offilesand aurlsfield with one link per line, where sitemaps are expanded. The work runs in the background;GET /api/v1/ai/snippets/import/statusreturnsrunning,total,success,errorsandlogs. Only one background import runs at a time, a second one gets 409.
curl https://support.example.com/api/v1/ai/snippets/import-file \
-H "Authorization: token $SUPPORT_API_KEY:$SUPPORT_API_SECRET" \
-F "file=@price-list.pdf" -F "key=docs:price-list" -F "dry_run=true"
Limits: a file may be up to 25 MiB, a sitemap gives at most 300 pages per import, a file or page with less than 40 characters of text is refused, and links to private or internal network addresses are refused. Reading a PDF in import-file stops with an error after 45 seconds; the background import allows two minutes per PDF.