You can use the Evo Researcher public API to create and manage projects, templates, apps and chats from your own systems and integrations.
The Evo Researcher public API lets other Evo products and integrations work with Researcher from code. You can create projects, hand over conversations from other AI tools, and manage templates, apps, skills, collaborators and chats. This article explains how to get started and where to find each task.
Before you start
Make sure you have the following before you make your first request.
A button token for your integration. You send it with every request.
The role for the task you need. Creating or updating templates and organisation skills needs the org_author role. Listing users and managing other people's roles needs the admin role.
Write access to any project you want to change. Only the project owner can delete a project or manage its collaborators.
A request from an inactive user or organisation returns 403. A missing or invalid token returns 401.
Authenticate and choose your environment
Choose the base URL for your environment, then add your token to each request.
Environment | Base URL |
Preprod UK | |
Integration EU | |
Integration US | |
Production UK | |
Production EU | |
Production US | |
Production AU |
Choose the base URL for your environment from the table.
Add an Authorization header to each request. Set its value to Bearer followed by your button token.
For requests with a body, set Content-Type to application/json.
All endpoint paths in this article are relative to your base URL. For example, POST /projects in Production UK is sent to the Production UK base URL followed by /projects.
Keep your button token secret. Store it in your integration's secure configuration, not in code that others can read.
Create a project
You can create a project with only a name, or set up its template, instructions and chat history in one request.
Required conditions: Supply a name, a template_id, or both. If you leave out the name, you must supply a template_id, and Researcher names the project after the template with a timestamp.
Send a POST request to /projects.
Set name to the display name. The limit is 200 characters.
Add any optional fields you need from the table below.
Save the id and chat_id from the response. Use the chat_id to take the user straight into the conversation.
Optional field | Limit or format | What it does |
description | 2,000 characters | Describes the project. |
system_prompt | 10,000 characters | Sets the instructions the project follows. |
template_id | Template UUID | Applies the template's instructions, uploads, connectors and settings. |
folder_id | Folder UUID | Places the project in a folder. |
tag_id | Tag UUID | Attaches a tag when the project is created. |
actions | Up to 50 action names | Adds actions to the project. |
output_schema | JSON Schema | Turns each chat turn into a validated JSON artifact. |
messages | Up to 500 items | Pre-fills the chat history. Each item needs a role and content. |
Hand over a conversation from another AI product
If an AI chatbot passes a user to Researcher, send the conversation so far in the messages field. Each message needs a role of user or assistant, and content of up to 100,000 characters. Researcher sets the chat title from the first user message, so the user can carry on where they left off.
Work with templates
Templates provide the starting setup for a project, including its instructions, uploads, connectors and settings. A project takes a copy of its template when it is created, so editing a template does not change projects that already exist.
Task | Method and path | Who can use it |
List organisation templates | GET /templates | Any signed-in user |
Get one template | GET /templates/{templateId} | Any signed-in user |
Create a template | POST /templates | Users with the org_author role |
Update a template | PATCH /templates/{templateId} | Users with the org_author role |
List your personal templates | GET /templates/personal | You |
Get one personal template | GET /templates/personal/{id} | You |
Create or update a template
To create a template, send a POST request to /templates with a name. You can also add a description, system_prompt, on_launch_message, on_launch_prompt, icon and colour.
To update a template, send a PATCH request to /templates/{templateId} with only the fields you want to change. Any field you leave out stays the same.
You cannot set a template's connectors, agents, knowledge spaces, actions or collaborators through the API. Configure these in Evo Researcher. Because updates never clear them, an update cannot remove this setup.
You can read your own personal templates through the API, but you cannot create, update or delete them there. Manage them in Evo Researcher.
Manage apps
An app is a template published to Navigator. It can appear as a Quick Panel, a Quick Action or a Quick Section, and it opens a focused Researcher experience. Apps start as drafts and go live only when you publish them.
Required conditions: Build each app on an organisation template. Use List Templates to find its id. A personal template id is rejected, because a published app is visible across the organisation.
Send a POST request to /apps with a title, description, colour, one surface and params. The app is saved as a draft.
Save the id from the response.
Send a PUT request to /apps/{id}/publish to make the app live in Navigator.
Choose where the app appears
Choose exactly one surface:
quickPanel: set to true to appear as a Quick Panel.
quickAction: set to an object with a width of small, medium or large.
quickSection: set to an object with a size of small, medium or large.
Set mobile to true if you also want the app in the Evo mobile app. It appears on desktop web only by default.
Choose one of these colours: teal, purple, nileblue, ceruleanblue, bluelagoon, eaglegreen, sapgreen, burntorange, brickred, accessred, electricviolet or blueviolet.
Set the app parameters
The params object controls how the app behaves:
version must be 2.
templateId is the organisation template the app is built on.
style is standard for the normal product look, or chatbot for a stripped-back chat style.
launch sets the surface the app opens on. It is chat by default.
chat, conversations and features each take a show value of true or false.
A chatbot-style app usually turns off most features and leads with chat. A full-workspace app turns them on.
Control who can see the app
By default, a published app is visible to the whole organisation. To narrow access, set security to one of these:
Specific users, using up to 100 user ids.
Specific groups, where each group has a type of basic or smart.
Set one target only. You can set access on a draft, and it takes effect when you publish.
Other app tasks
Task | Method and path |
List apps, including drafts | GET /apps |
Update an app | PUT /apps/{id} |
Unpublish an app | PUT /apps/{id}/unpublish |
Delete an app | DELETE /apps/{id} |
App fields use camelCase, such as quickAction and templateId. Projects and chats use snake_case, such as template_id.
⚠️Warning: Updating a published app changes it live in Navigator straight away. The update replaces the whole app definition, so send every field you want to keep.
Organise and update projects
Folders belong to the user who creates them, and you can nest them.
Task | Method and path | Notes |
List your folders | GET /folders | Each folder includes a parent_id, so you can rebuild the hierarchy. |
Create a folder | POST /folders | Send a name and an optional parent_id. Folder names must be unique among sibling folders. |
List projects | GET /projects | Search with q, and filter with folderId or tag. Use page and limit to paginate. |
Get project details | GET /projects/{projectId} | Shows connectors, artifacts, uploads and the system prompt. |
Update a project | PATCH /projects/{projectId} | Changes the name, description or system_prompt. Send an empty system_prompt to clear it. |
Set the output schema | PUT /projects/{projectId}/output-schema | Send null to turn structured output off. |
Delete a project | DELETE /projects/{projectId} | Only the owner can delete a project. |
When you update a project, include only the fields you want to change.
⚠️Warning: Deleting a project is permanent. Its chats, uploads, artifacts, attached skills and connectors are deleted with it. There is no way to recover it.
Share projects with collaborators
Collaborators are existing Researcher users in your organisation. You choose whether each one can view the project or also change it.
Required conditions: You must own the project. To find a user's id, use GET /users, which needs the admin role.
Send a POST request to /projects/{projectId}/collaborators with the user_id and an access_level of read or read_write.
Check the list with GET /projects/{projectId}/collaborators.
To change someone's access, send a PATCH request to /projects/{projectId}/collaborators/{userId}.
To stop sharing, send a DELETE request to the same path. The project itself is not affected.
A collaborator can remove themselves from a shared project. Anyone else needs the owner to remove them.
Your organisation's administrator sets a collaboration policy that limits sharing.
Full: you can share at either access level.
Read only: you can share at read level only. Granting read_write returns 403.
Off: sharing is switched off, so adding or changing a collaborator returns 403.
Existing collaborators keep their access if the policy changes, and you can always remove them.
Add skills and connectors
A skill is a reusable set of instructions. Organisation skills belong to your whole organisation, and personal skills belong to you. Only skills with a binding type of project can be attached to a project.
Task | Method and path |
List a project's skills | GET /projects/{projectId}/skills |
Attach a skill | POST /projects/{projectId}/skills/{skillId} |
Detach a skill | DELETE /projects/{projectId}/skills/{skillId} |
List organisation skills | GET /skills/organisation |
Create, update or delete an organisation skill | POST, PATCH or DELETE on /skills/organisation or /skills/organisation/{id} |
Personal skills use the same paths under /skills/personal.
Attaching a skill that is already attached returns the existing attachment, so it is safe to repeat. Detaching a skill does not delete it from the library.
Attach a connector
You can attach two types of connector through the API: Evo product connectors and third-party connectors. Agent and custom (MCP) connectors cannot be changed through the API.
Find the connector's route in the connectors catalogue.
Send a POST request to the matching path:
Evo product connector: /projects/{projectId}/evo-product-connectors, with a route. You can also add an instance_id and instance_name.
Third-party connector: /projects/{projectId}/third-party-connectors, with a route.
To remove a connector, get its record id from GET /projects/{projectId}. Then send a DELETE request to the matching path with that id.
Attaching a connector that is already attached on the same route returns 409.
Tag projects
Tags help you filter and organise projects. The API cannot list or create tags, so get the tag id from your own system.
Send a POST request to /projects/{projectId}/tags with the tag_id.
To remove the tag, send a DELETE request to /projects/{projectId}/tags/{tagId}.
To find tagged projects, send GET /projects with the tag filter.
Attaching a tag that is already attached, or removing one that is not, has no effect.
Upload files and query datasets
You can add files to a project and query spreadsheet data directly. Each file must be 30 MB or smaller.
Supported file types are .txt, .json, .csv, .md, .pdf, .docx, .xlsx, .xls, .pptx, .html, .xml, .rtf, .yaml, .yml and .log.
Convert the file to base64.
Send a POST request to /projects/{projectId}/uploads with the filename, mimeType, size in bytes and content.
Check GET /projects/{projectId}/uploads/{uploadId} until the status changes from processing to ready. If it shows error, the response explains why.
Task | Method and path |
List uploads | GET /projects/{projectId}/uploads |
Download the original file | GET /projects/{projectId}/uploads/{uploadId}/download |
Get a temporary download link | GET /projects/{projectId}/uploads/{uploadId}/blob-url |
Delete an upload | DELETE /projects/{projectId}/uploads/{uploadId} |
Use the download endpoint when your code needs the file itself. Use the blob-url endpoint for an iframe, an image or a link. Temporary links expire after about 5 minutes.
Query a dataset
Spreadsheets become structured datasets. A CSV file produces one dataset, and an Excel file produces one dataset per sheet.
Send GET /projects/{projectId}/datasets to find the dataset id.
Send a POST request to /projects/{projectId}/datasets/{datasetId}/query with the filters, groupBy, aggregations and orderBy you need.
Each query works on a single dataset, and you cannot join datasets. A query returns 100 rows by default, up to 5,000 rows per response. If more rows exist, use the cursor from the response to get the next page.
Read and create artifacts
Artifacts are the files and documents a project generates. Each artifact has one of these types: HTML, PDF, EXCEL, MARKDOWN, MFE, CHART or POWERPOINT.
Task | Method and path |
List artifacts | GET /projects/{projectId}/artifacts |
Get metadata and text content | GET /projects/{projectId}/artifacts/{artifactId} |
Get full content | GET /projects/{projectId}/artifacts/{artifactId}/content |
Download the file | GET /projects/{projectId}/artifacts/{artifactId}/download |
Get a temporary link | GET /projects/{projectId}/artifacts/{artifactId}/blob-url |
Upload an artifact | POST /projects/{projectId}/artifacts |
To upload an artifact, send its name, its type and its content as base64. The limit is 30 MB.
The API stores the bytes you upload exactly as sent. It does not check that the file matches the type you declare.
PowerPoint artifacts do not support the blob-url endpoint. Use the download endpoint instead.
Run chats in a project
Use the start, poll and fetch pattern for background jobs, such as scheduled reports. Use streaming when a person is waiting for the reply.
Run a chat in the background
Send a POST request to /projects/{projectId}/chats/start with a message. You receive a chat_id and a status of generating.
Check GET /projects/{projectId}/chats/{chatId} until the status is no longer generating.
Send GET /projects/{projectId}/chats/{chatId}/messages to read the full transcript.
To stop a chat that is still running, send a POST request to /projects/{projectId}/chats/{chatId}/cancel. Then check the status until it shows cancelled or idle.
While you check the status, watch message_count. When it goes up, a new reply has arrived.
Stream a reply
Send a POST request to /projects/{projectId}/chats/stream with an id for the message and a message object that contains the role user and the text parts. The reply arrives as Server-Sent Events. If the connection drops, reconnect with GET /projects/{projectId}/chats/{chatId}/stream. This returns 204 when no stream is active.
To list the chats in a project, send GET /projects/{projectId}/chats.
Use standalone chats
Standalone chats sit outside a project, so they suit short, one-off conversations.
Task | Method and path |
Create a chat | POST /chats |
List your chats | GET /chats |
Get one chat | GET /chats/{id} |
Stream a message | POST /chats/{id}/stream |
Work with uploads | /chats/{id}/uploads and /chats/{id}/uploads/{uploadId} |
Work with artifacts | /chats/{id}/artifacts and /chats/{id}/artifacts/{artifactId} |
When you create a chat, every field is optional. You can add a name, a template_id, actions and messages. A chat can use one template only, and you cannot change it later.
Standalone chats have no transcript endpoint. You also cannot rename, delete or favourite them through the API.
Choose a response preset
A preset is a named model with the settings Researcher runs it with. Presets stay the same even when the model behind them changes, so your integration does not need to change.
Send GET /llm/presets to see the presets you can use and your preferred preset.
Add the preset id to your request.
A chat keeps the preset from its first turn, so you only need to set it once. If you ask for a preset you cannot use, the request returns 400 with the code PRESET_NOT_AVAILABLE. Researcher never swaps in a different preset. The older model field still works, but preset takes priority if you send both.
Approve tool calls
By default, chats started through the API approve every tool call they make. To pause write-type tool calls for approval, set enable_tool_consent to true when you start or stream a chat. Read-only calls are never paused. The setting is remembered for that chat.
Start the chat with enable_tool_consent set to true.
Check GET /projects/{projectId}/chats/{chatId}. The pending_tool_consent field shows the tool name and a request_id.
Send a POST request to /projects/{projectId}/chats/{chatId}/tool-consent/{requestId}/decision with a decision of allow, deny or always_allow.
Choosing always_allow approves future calls of the same tool and operation in that project, so Researcher stops asking.
If nobody answers a pending request within a few minutes, Researcher denies it automatically and the turn continues.
Ask the AI without a project or chat
Use the LLM endpoints when you need one answer inside a wider workflow, such as classifying a support ticket or summarising a paragraph. Researcher stores nothing from these calls.
Task | Method and path | Response |
Get a finished answer | POST /llm/message | JSON, once generation completes |
Stream an answer | POST /llm/stream | Server-Sent Events, while the model writes |
Both endpoints take the same body. Include a message, up to 100,000 characters. You can also add messages (earlier turns, up to 500), a system_prompt, a preset and tools.
⚠️Important: Researcher keeps no memory between calls. Send the earlier turns in messages every time.
To let the model search the web, set tools to an object with web_search set to true. Web search works only on presets whose model has built-in search. On any other preset, the request returns 400 with the code TOOL_NOT_AVAILABLE. Retry without web search, or choose a different preset.
Manage user roles
Only admins can read other users' roles or grant and revoke them. Anyone can read their own roles by using me as the user id.
Role | What it allows | Can you grant it through the API? |
org_author | Create and manage organisation templates and skills | Yes |
org_settings | Change organisation settings | Yes |
app_author | Create, edit and delete apps | Yes |
app_publisher | Publish and unpublish apps | Yes |
admin | Organisation admin | No. Access manages this role. |
user | Standard access | No. Applied to all users. |
Find the user's id with GET /users. You can search by name or email with q.
To grant a role, send a POST request to /users/{userId}/roles/{role}.
To remove a role, send a DELETE request to the same path.
Check the result with GET /users/{userId}/roles.
Granting a role the user already has, or revoking one they do not have, has no effect.
Send messages to an embedded Researcher
If your product embeds Researcher, your host can send messages into the open chat.
Call the executeMfeFunction method from the Evo host SDK.
Pass the instance id you used when you embedded Researcher, the function name sendMessage, and a text value.
Researcher treats the message as if the user typed it. If a reply is still streaming, new messages queue and send in the order they arrived. Blank text is ignored. The call resolves once Researcher accepts the message, not when the reply finishes.
Fix common errors
Status | What it means | What to do |
400 | The request is invalid, such as a missing field or a value over its limit. | Check the message in the response and correct the request. |
401 | The token is missing, invalid or lacks the required scope. | Check your button token and its scopes. |
403 | The account or organisation is inactive, or you lack the role or access needed. | Check your permissions with your administrator. |
404 | The item does not exist or is not visible to you. | Check the id. Other users' private items also return 404. |
409 | The request clashes with the current state, such as a duplicate connector or a chat waiting for a consent decision. | Check the current state, then try again. |
503 | Generation failed upstream, for example because of rate limiting. | Wait a moment, then retry. |
