Skip to content

Guides

Account management for AI agents

A curated set of Unless REST API endpoints accepts API key authentication, which makes them usable by an AI agent acting on behalf of an account user - no interactive login required. This guide explains how to authenticate, which endpoints are available, and how to chain them to complete common account management tasks.

To use Unless directly from an AI agent like ChatGPT or Claude, give it this URL: https://docs.unless.com/guides/account-management-for-ai-agents.md. That is this page plus the full specification of every endpoint in a single Markdown file, so one fetch is all the agent needs. Then ask it to do the job, for example “publish our help center” or “list the open tasks and tell me what needs attention”.

If you are an AI agent working on someone’s Unless account, follow these rules.

Ask the user for the credentials. Before the first call, ask for all four values in one message:

Ask forWhere the user finds it
Personal API keyDashboard, Account > Settings, the “Personal API key” row. Keys are issued with a personal_ prefix.
Their email addressThe address they sign in with. The key is tied to that user, so the two have to match.
Account IDDashboard, Account > Settings, the “Account ID” row at the top of the page.
Workspace IDDashboard, Workspaces, the actions menu on the workspace row, “Copy workspace ID”.

Ask for the personal key, never the account-wide one. Both authenticate, but the personal key carries only that one user’s permissions and can be replaced without affecting anyone else. A key without the personal_ prefix is probably the account-wide key, so ask again.

The account and workspace pages need the administrator role, so a user without it has to get these values from an administrator.

Do not go looking for the credentials yourself. Do not search files, .env files, environment variables, git history, shell history or a password manager, and do not reuse an account or workspace ID you happened to see somewhere. Acting on the wrong account is far worse than asking. No endpoint lists the accounts or workspaces a key can reach, so the IDs have to come from the user.

Make the calls yourself. Issue the HTTP requests with whatever tool you have. Do not hand the user a curl command and ask them to paste the output back. If you have no way to reach https://api.unless.com, say so plainly instead of delegating the work.

Keep the key in the request headers. Do not repeat it back to the user, write it to a file, commit it, or include it in a summary.

Stay in the workspace the user named. Every call takes x-website-id, and most reads also want the same value as a websiteId query parameter. If the account has several workspaces and the user did not say which one, ask.

Confirm before anything a visitor will see. Publishing the help center, setting a personalization or an audience live, and accepting content library changes all reach real visitors. Describe what you are about to do and wait for a yes. Reads need no confirmation.

Report what the API actually returned. On failure, give the status code and the response body rather than guessing at the cause, and do not report a change as done until a response confirmed it.

Every request needs four headers:

HeaderValue
x-api-keyThe user’s personal API key from Account > Settings. The account-wide key also authenticates, but prefer the personal one.
x-user-emailThe email address of the user the agent acts on behalf of.
x-account-idThe account ID, found in the account settings.
x-website-idThe ID of the workspace (website) to operate on.

All endpoints are served from https://api.unless.com and prefixed with /api/v1. Requests and responses are JSON.

Example:

Terminal window
curl https://api.unless.com/api/v1/tasks?websiteId=<website-id> \
-H "x-api-key: <api-key>" \
-H "x-user-email: <user-email>" \
-H "x-account-id: <account-id>" \
-H "x-website-id: <website-id>"

Notes:

  • Send either an API key or a JWT, never both. Requests carrying both an Authorization header and an x-api-key header are rejected.
  • The permissions of the resolved user apply. A key only reaches the websites its user has access to.

This catalog tells an agent which endpoint fits which job. Every endpoint links to its entry in the REST API reference, where the headers, parameters, request body and responses are documented in full.

EndpointUse it to
GET /api/v1/tasksList the account’s work items: knowledge suggestions, detected gaps, flagged conversations, sales signals.
GET /api/v1/ai/configurationInspect how the AI is configured: main language, product name, custom rules.
GET /api/v1/ai/trainingSee which knowledge sources the AI is trained on and how ingestions went.
GET /api/v1/audiencesList the visitor segments defined for the account.
GET /api/v1/personalizationsList the personalizations (experiences) running on a website.
GET /api/v1/help-center/publish-statusCheck whether the help center is published, publishing or failed.
GET /api/v1/ai/quality/controlList the control questions the AI is scored against.
GET /api/v1/ai/quality/control/reportList quality control reports, or fetch one report’s per-question results with ?reportId=.
GET /api/v1/ai/actionsList the procedures the AI can run: what each one does, whether it is enabled, and which training documents trigger it.
GET /api/v1/components/custom-componentsList the custom components available to the account.
GET /api/v1/accounts/logsRead the audit trail of who changed what. Last 90 days, 100 entries.

Insights are read-only over the API. Every endpoint takes startTime and endTime as inclusive YYYY-MM-DD days, except the ROI and maturity endpoints, which report over the account’s whole history. Results come from a query cache, so check status before trusting the numbers, and reach for forceRefresh=true only when you know the cache is stale.

EndpointUse it to
GET /api/v1/insights/pageviewsPageview totals, split by new versus returning and personalized versus not.
GET /api/v1/insights/pageviews/per-dayThe same split, one entry per day.
GET /api/v1/insights/outcomesHow many outcomes (conversions) were recorded.
GET /api/v1/insights/outcomes/per-dayOutcomes per day, optionally for one personalization.
GET /api/v1/insights/audiences/per-dayHow many sessions one audience matched per day. Needs audienceId and domainName.
GET /api/v1/insights/components/eventsEngagement per variation: views, CTA clicks, and the AI chat counters.
GET /api/v1/insights/components/events/per-dayThe same counters, one entry per variation per day.
GET /api/v1/insights/wiki/eventsContent library updates the AI made, and the tokens it spent.
GET /api/v1/insights/wiki/events/per-dayThe same, one entry per day.
GET /api/v1/insights/roiWhat the platform handled, per surface, against the account’s business figures.
GET /api/v1/insights/ai-maturityThe maturity assessment: score, stage and level summaries, and the features behind each cell.
EndpointUse it to
GET /api/v1/visitors/conversationsList conversations, or fetch one transcript with ?conversationId=.
GET /api/v1/ai/conversations/detailsRead one conversation’s analysis: sentiment, rating, outcome, tags.

The content library (wiki) is the AI’s own editable knowledge base. Changes an agent makes are staged as a suggestion task and only reach the library once accepted.

EndpointUse it to
GET /api/v1/wiki/treeBrowse the entries under a path. Walk it repeatedly to see the whole tree.
GET /api/v1/wiki/pages/{path}Read one page.
GET /api/v1/wiki/change-logSee what changed, with the before and after content of each edit.
POST /api/v1/wiki/chatInstruct the content library agent in natural language. Streams, and stages its changes as a task.
POST /api/v1/wiki/force-importRe-import a source URL, ignoring what the importer already knows.
PATCH /api/v1/wiki/tasks/{taskId}/changes/{changeId}Accept, deny or retry a single staged change.
POST /api/v1/wiki/tasks/{taskId}/accept-allAccept every staged change in a task.
POST /api/v1/wiki/tasks/{taskId}/deny-allDiscard every staged change in a task.
POST /api/v1/wiki/tasks/bulk-acceptAccept several tasks at once.
POST /api/v1/wiki/tasks/bulk-denyDiscard several tasks at once.
EndpointUse it to
PUT /api/v1/tasksChange a task’s state (open, done, deleted), assign it, or edit it.
POST /api/v1/tasksCreate a new task for the account team.
POST /api/v1/ai/training/dataIngest a website (or another source) so the AI learns its content.
POST /api/v1/help-center/publishPublish the help center so content changes go live.
POST /api/v1/audiencesCreate a new audience, or update an existing one.
POST /api/v1/ai/quality/controlAdd or update a control question and its expected answer.
POST /api/v1/ai/quality/control/reportStart a quality control run that scores the AI against every control question. Returns the reportId to poll.
POST /api/v1/ai/actionsCreate a new procedure, or update an existing one.
POST /api/v1/ai/configurationChange how the AI behaves: language, product name, custom rules, PII filtering.
POST /api/v1/personalizationsCreate a personalization, or update an existing one.
POST /api/v1/help-center/categoriesCreate or update a help center category.
POST /api/v1/help-center/faqsCreate or update a help center FAQ. You supply the question; the AI writes the answer.
POST /api/v1/help-center/faqs/retryRegenerate an FAQ’s answer, for instance after the training data changed.
POST /api/v1/help-center/categories/reorderSet the sort position of several categories.
POST /api/v1/help-center/faqs/reorderSet the sort position of several FAQs.
POST /api/v1/wiki/change-log/{id}/restoreRoll a content library page back to one side of a logged change.

Deleting is deliberately not part of the API key surface. Removing a procedure, an audience, a control question, a training source, an FAQ, a category, a personalization, a custom component or a content library page is dashboard-only. So is anything that changes what the insights measure: the ROI business figures, granting a maturity certificate and skipping a maturity task.

EndpointUse it to
POST /api/v1/ai/queryAsk the account’s trained AI a question and get an answer in realtime.

Most real tasks combine a read to establish context with one or more writes, followed by a read to verify the result.

  1. GET /api/v1/tasks?websiteId=...&state=open to list what needs attention.
  2. Investigate a task using the read endpoints, for example GET /api/v1/ai/training for a knowledge gap task.
  3. PUT /api/v1/tasks with { "taskId": ..., "websiteId": ..., "state": "done" } once handled.
  1. GET /api/v1/ai/training to check whether the site is already a source.
  2. POST /api/v1/ai/training/data with { "trainingData": { "accountId": ..., "websiteId": ..., "type": "url", "method": "all", "url": "https://www.example.com", "isPublic": true } }.
  3. Poll GET /api/v1/ai/training and watch indexedUrlsCount and lastScanTimestamp to follow progress.
  1. GET /api/v1/help-center/publish-status to confirm no publish is running (state.runningJobId is null).
  2. POST /api/v1/help-center/publish for a full publish, or with a partial scope for specific FAQs.
  3. Poll GET /api/v1/help-center/publish-status until the job’s status is terminal.
  1. GET /api/v1/audiences to inspect existing audiences and their rule format.
  2. POST /api/v1/audiences with a name, a rule (JSON string) and a state.
  3. GET /api/v1/audiences?ruleId=... to verify the result.

A procedure is something the AI can do beyond answering: call an API, collect information, or escalate to a human. POST /api/v1/ai/actions takes the whole procedure rather than a patch, so an update starts by reading the current one.

  1. GET /api/v1/ai/actions to list the procedures and copy the actionId of the one to change.
  2. Decide on the actionId. To update, use the existing one. To create, generate a UUID yourself rather than letting the API generate one, because arguments has to carry the same value and you cannot know an API-generated ID until the write has already happened.
  3. POST /api/v1/ai/actions with the full procedure, including actionId and arguments. accountId and websiteId come from the auth headers and overwrite whatever the body says.
  4. GET /api/v1/ai/actions again to read back what was stored. The write answers { "actionId": ..., "message": "AI action upserted" }.

The write endpoint stores whatever you send without validating it, so a procedure that is missing a field is created successfully and then fails the first time the AI tries to run it. Check the readback rather than trusting the 200.

Things that are easy to get wrong:

  • arguments is required and easy to miss. It holds the command’s input, and for command: "api" it must be the procedure’s own actionId - the AI dispatches the procedure as /api <arguments>, so an empty or wrong value means nothing gets called. For mcp it is the MCP tool ID, and for direct-answer the answer text.
  • Variable type accepts only string, multiline, email, regex and select. number, boolean, date and phone look plausible but are not implemented, and a stored number makes GET /api/v1/ai/actions answer 502 for every procedure in the workspace until you correct it.
  • Send every field you want to keep. Anything left out of the body is not preserved.
  • Set smartAction explicitly: true lets the AI fill the variables from the conversation, false asks the visitor for each one.
  • body is a string containing JSON, not a nested object.
  • Credentials go in headers as a {{placeholder}} resolved from the visitor’s secure profile. A value declared in variables ends up in the chat data, which is stored with the conversation and sent to the model.
  • Deleting a procedure is not available with an API key, only from the dashboard.

A minimal api procedure, with the same UUID in both places:

{
"actionId": "3f6c1b52-9a4e-4c8d-9f1a-2b7d5e0c4a13",
"arguments": "3f6c1b52-9a4e-4c8d-9f1a-2b7d5e0c4a13",
"name": "Look up order status",
"description": "Retrieves the status of an order from the shop backend.",
"command": "api",
"enabled": true,
"smartAction": true,
"method": "GET",
"endpoint": "https://api.example.com/orders/{{orderId}}",
"body": "{}",
"bodyType": "json",
"variables": [{ "name": "orderId", "description": "The order number the visitor is asking about", "type": "string" }]
}
  1. POST /api/v1/help-center/categories to create the category, or read an existing categoryId from the published help center.
  2. POST /api/v1/help-center/faqs with { "faq": { "question": ..., "categoryId": ..., "websiteId": ... } }. You supply the question only - the FAQ is stored with answerStatus: "pending" and the AI writes the answer from the training data.
  3. Poll the FAQ until answerStatus leaves pending. Use POST /api/v1/help-center/faqs/retry if the answer needs rewriting after a training change.
  4. POST /api/v1/help-center/publish and poll GET /api/v1/help-center/publish-status. Nothing reaches visitors until that publish completes.

Note that faq.websiteId and category.websiteId must be set in the body as well as in the x-website-id header, and that the URL slug is derived from the question or name and then kept stable across later edits.

Edit the content library through the agent

Section titled “Edit the content library through the agent”
  1. GET /api/v1/wiki/tree and GET /api/v1/wiki/pages/{path} to see what is already there.
  2. POST /api/v1/wiki/chat with { "instruction": ... }. The response is a stream of plain text, not JSON: the agent’s narration arrives incrementally, and the last line is the marker __WIKI_CHAT_RESULT__ followed by a JSON object with taskId, changesStaged and agentSummary. Split on the marker.
  3. Review the staged changes on the task, then PATCH /api/v1/wiki/tasks/{taskId}/changes/{changeId} with { "action": "accept" } per change, or POST /api/v1/wiki/tasks/{taskId}/accept-all for the lot.
  4. GET /api/v1/wiki/change-log to confirm what was written. If a change was wrong, POST /api/v1/wiki/change-log/{id}/restore with { "target": "before" } undoes it.

The agent never writes directly. A changesStaged of zero means it decided nothing needed changing, not that it failed.

Score the AI with a quality control report

Section titled “Score the AI with a quality control report”

Generating a report costs one AI call per control question, so it is only available on the Enterprise, Flex, Fixed and Plus plans. On any other plan POST /api/v1/ai/quality/control/report answers 403.

  1. GET /api/v1/ai/quality/control to see the control questions. A report scores every one of them, so add what is missing with POST /api/v1/ai/quality/control first.
  2. POST /api/v1/ai/quality/control/report to start the run. It answers 400 when there are no control questions yet, and otherwise returns the reportId.
  3. Poll GET /api/v1/ai/quality/control/report. The new report appears in the list once its first question has been scored, with processed counting up to totalQuestionsInReport.
  4. GET /api/v1/ai/quality/control/report?reportId=... for the per-question results once processed equals totalQuestionsInReport.