MCP endpoint and tools
The per-site MCP connector URL, OAuth 2.1 sign-in (discovery, registration, PKCE, resource indicators, scopes and token lifetimes), personal keys, every tool with its inputs and who can call it, the field formats, SEO limits, errors and rate limit.
This is the technical reference for the MCP endpoints. Editors who want to connect an AI app should start with Connect AI. Agency admins who work across many clients can use the agency connector, which is one address for every site. This page is for developers, agency admins and anyone who writes their own MCP client or checks what an AI app is allowed to do.
Each site has its own connector URL. AI apps sign in with OAuth 2.1 in the browser, so no secret is pasted into the app. Personal keys still work for scripts and for clients that cannot sign in through a browser. The endpoint belongs to the CMS, not to a site project, so it never uses the site token or the schema token. See tokens.
Endpoint
| Item | Value |
|---|---|
| Connector URL | https://<your CMS>/api/mcp/<site-slug>. One per site. The Connector URL on the Connect AI page shows the exact value, built from APP_URL. |
| Key endpoint | https://<your CMS>/api/mcp. Accepts personal keys only. The key's site is the one it serves. |
| Agency connector | https://<your CMS>/api/agency/mcp. For agency admins only, for every client and site. Each call names its site with the site argument. See Agency connector. |
| Methods | POST, GET and DELETE (Streamable HTTP) |
| Transport | MCP Streamable HTTP in stateless mode. Each request stands alone and no session ID is needed. Responses are JSON, not event streams. |
| Protocol | MCP 2026-07-28. Clients that use the 2025 protocol revisions are served too, statelessly. |
| Authentication | Authorization: Bearer <token>: an OAuth access token issued for that site (areca_at_…), or a personal key (areca_mcp_…). See Authentication. |
| Server name | areca-<site slug>, for example areca-acme-site. The install buttons use this name. |
| Request size | Up to 1 MB per request |
| Rate limit | 120 requests per minute per key or access token, and 600 per minute per client address when the address is known. See rate limits. |
Cookies are not accepted. A request without a bearer credential, or with one that is not an access token or a key, is refused.
Authentication
There are two credentials. Apps that use the connector URL sign in with OAuth. Scripts and clients without a browser use a personal key.
OAuth sign-in
The CMS is an OAuth 2.1 authorization server. It follows the MCP authorization specification: the connector URL is a protected resource, the authorization code flow uses PKCE with S256, and clients name the resource they want with RFC 8707 resource indicators. The server is built on @better-auth/oauth-provider.
Discovery. The steps an app takes, in order:
-
It calls the connector URL with no token. The server answers
401with a challenge that names the protected resource metadata:401 challenge WWW-Authenticate: Bearer resource_metadata="https://cms.example.com/.well-known/oauth-protected-resource/api/mcp/acme-site", scope="mcp offline_access" -
It reads the protected resource metadata (RFC 9728) at
/.well-known/oauth-protected-resource/api/mcp/<site-slug>. The document names the resource, the authorization server and the scopes:Protected resource metadata { "resource": "https://cms.example.com/api/mcp/acme-site", "authorization_servers": ["https://cms.example.com/api/auth"], "scopes_supported": ["mcp", "openid", "profile", "email", "offline_access"], "bearer_methods_supported": ["header"], "resource_name": "Acme Site (Areca CMS)" }resourceis the connector URL exactly, and it is what clients send back as theresourceparameter. An unknown site slug answers404. -
It reads the authorization server metadata (RFC 8414) at
/.well-known/oauth-authorization-server/api/auth, or the OpenID configuration at/.well-known/openid-configuration/api/auth. The issuer ishttps://<your CMS>/api/auth. The same documents are also served under/api/auth/.well-known/…. Documents at the origin root without the/api/authpath are not served, because their issuer would not match.
Registration. Apps register themselves with Dynamic Client Registration (RFC 7591) at POST /api/auth/oauth2/register. No account is needed, so AI apps can register anonymously. The server accepts https redirect URIs, and http only for loopback addresses (localhost, 127.0.0.1, [::1]), as native apps use. Client ID Metadata Documents are not supported yet, so an app must register through DCR. Registration and token requests are rate limited; see rate limits.
Authorization. The app sends the user's browser to GET /api/auth/oauth2/authorize with these parameters:
| Parameter | Value |
|---|---|
response_type | code |
client_id | From registration |
redirect_uri | One of the URIs the app registered |
scope | Space-separated, from the list below. mcp is required. |
resource | The connector URL, for example https://cms.example.com/api/mcp/acme-site (RFC 8707) |
code_challenge, code_challenge_method | PKCE with S256. The server advertises no other method. |
state | Chosen by the app |
The user signs in (with a password, a social or Microsoft sign-in, and a two-factor code if they have one) and then sees the consent screen. See Connect AI. The authorization response carries the iss parameter (RFC 9207), set to the issuer above, so clients can check which server answered.
Token. The app exchanges the code at POST /api/auth/oauth2/token with grant_type=authorization_code, the code_verifier, the same redirect_uri and the resource. The endpoint returns an access token and a refresh token. Refresh with grant_type=refresh_token. Both grants are supported, and no others.
Other endpoints. Revocation is at POST /api/auth/oauth2/revoke (RFC 7009), introspection at POST /api/auth/oauth2/introspect, and user info at /api/auth/oauth2/userinfo. Disconnecting an app on the Connect AI page revokes the same tokens, and it takes effect on the next request.
Scopes. mcp is the only scope the MCP endpoint checks, and it is required. offline_access is needed for a refresh token, so clients usually request it. openid, profile and email are accepted and advertised because common clients request them by default. They identify nothing that the endpoint uses for access.
Lifetimes.
| Token | Format | Lifetime |
|---|---|---|
| Access token | Opaque, prefix areca_at_ | 1 hour |
| Refresh token | Opaque, prefix areca_rt_ | 90 days, counted from its last use. It rotates: every refresh returns a new refresh token. |
| Refresh reuse window | 30 seconds. A client that lost a refresh response can repeat the same request within this window and receive the same tokens. Reusing a rotated token after the window is treated as replay. The server then deletes every token that app holds for that user, on every site, and the app must sign in again. | |
| Connection | Until disconnected, or 90 days without use |
Tokens are opaque, so every request checks them in the database, and revocation takes effect at once. See tokens.
Audience. An access token is valid only for the resource it was issued for. A token issued for acme-site is refused at other-site with 401, and the challenge says the token was issued for a different site.
Per request checks. On every request, the server checks that the token exists, is not expired or revoked, names this site as its resource, and carries the mcp scope. It then checks that the user has an active connection to this site for this app (created by approving the consent screen, not revoked, and used within 90 days). Last, it reads the user's live role on the site. A removed member, a role change or a disconnect takes effect on the next request.
Errors. Failures on the connector URL use the RFC 6750 and RFC 9728 forms:
| Status | When | WWW-Authenticate |
|---|---|---|
401 | No bearer credential | Bearer resource_metadata="…", scope="mcp offline_access" |
401 | Token invalid, expired or revoked, issued for another site, or the app is no longer connected | Bearer error="invalid_token", error_description="…", resource_metadata="…", scope="…" |
401 | The user no longer has access to the site | As above, error_description "You no longer have access to this site" |
403 | Token without the mcp scope | Bearer error="insufficient_scope", … |
403 | Agency admin without two-factor sign-in, on an installation that requires it (the default). Body has "code": "mfa_required". | none |
404 | Unknown site slug | none |
429 | Rate limit exceeded | none (see rate limits) |
A client that receives 401 with invalid_token should refresh its token, and start sign-in again if the refresh fails. invalid_grant on refresh means the refresh token is dead, and the app must sign in again.
Personal keys
A key is areca_mcp_ followed by 43 random characters. Areca stores a SHA-256 hash of it, shows it once, and checks it on every request. A key works on the connector URL of its own site, and on /api/mcp. On a connector URL for another site it is refused.
A request is refused with HTTP 401 when the key:
- does not exist, was revoked, or has expired,
- belongs to a user who is no longer a member of the site's client,
- belongs to an agency admin who has no two-factor sign-in, on an installation that requires it (see below).
The /api/mcp endpoint answers with this body and WWW-Authenticate: Bearer realm="areca-mcp". An OAuth access token is not accepted there; use the connector URL for OAuth.
{ "error": "Invalid or expired Areca MCP key" }An agency admin whose account has no two-factor sign-in is refused with HTTP 403 instead, when the installation requires two-factor for admins (the default):
{ "error": "Your account needs two-factor authentication before AI keys work. Turn it on in Account settings.", "code": "mfa_required" }The role is read from the database on each request, so a change of role applies to the next request. The Last used time on a key is updated at most once a minute.
Agency connector
The agency connector at https://<your CMS>/api/agency/mcp gives one AI app access to every client and site an agency admin can manage, production and development. The tools are the same as the per-site tools below, with one difference: every site tool takes a required site argument, the site's slug, and every result includes a site object (slug, name, client, environment).
Agency admins sign in with an app connection (OAuth) or with a personal agency key created on Agency › Connect AI. Keys last 30 days, 90 days, 1 year or never. Both need two-factor sign-in on the account. Anyone who is not an agency admin gets the same 404 {"error":"Site not found"} that an unknown address gets. A site key sent to this address is refused, and an agency key sent to a site's connector is refused with 401.
Navigation tools
These tools exist only on the agency connector. They are read-only and find the site value for the other tools.
| Tool | Inputs | What it returns |
|---|---|---|
list_clients | none | The clients you can reach, with slug, name and sites (how many sites each has). |
list_sites | client? (a client slug or name, from list_clients) | One row per site, production and development: slug, name, client, environment, linked (the production site of a development environment, or its development environment), publishingMode, live and pendingReadyChanges. |
find_site | query* (text) | The same rows as list_sites for sites whose name, slug, client name or production URL contains the text, up to 50 (truncated is set when there are more). |
Environment rules
Every agency tool is listed, whatever kind of site it is for. A call is checked against the site it names:
- Tools for production only (go live, rollback, cancel build, release and build status, schedule and cancel schedule, and
create_dev_environment) are refused on a development environment. For release tools the refusal says to push to production first. For scheduling it says scheduling is not available on development environments. - Tools for development only (
refresh_from_production,push_to_production,copy_entry_to_production) are refused on a production site. The refusal says to use the site's development environment slug fromlist_sites.
A refusal is a normal tool result with isError: true, so the model can explain it and choose the other site.
Going live
Per-site connectors need the Allow going live tick. The agency connector does not: an agency admin's connection can use go_live, rollback, cancel_build, schedule_entry and cancel_schedule on production sites without it. The OAuth consent screen for the agency connector therefore has no tick box, and says the app can "read and change every client and site, including production, and go live". Per-site connections and keys are unchanged.
Production writes over the agency connector are not blocked. The server instructions ask the model to name the client and the site, and to get the user's confirmation before a production change.
Rules and audit
- Rate limit. The same budget as the per-site connectors, 120 requests a minute for each key or connection, shared across all sites. The per-IP budget applies too.
- Revocation. A key or connection stops working as soon as its owner is no longer an agency admin, is blocked, or loses the second factor. Demoting an agency admin also revokes their agency keys and connections at once.
- Audit. Each change is written to the audit log for the site it changed, with the agency connector as its source and the name of the key or connection that made the call.
Access levels
Tools are registered per request, based on the caller's current role. A tool that the caller cannot use is not listed by tools/list, and calling it by name returns an error.
| Access | Who can use it | Tools |
|---|---|---|
| Everyone | Any member of the site, including editors | All read tools, drafts, uploads, alt text, preview |
| Owner | Owners, and agency admins | Workflow, delete, Library delete |
| Go live | A connected app or key allowed to publish, held by an owner or agency admin. On the agency connector no flag is needed. | go_live, rollback, cancel_build, schedule_entry, cancel_schedule (on production sites) |
| Agency | Agency admins only | upsert_collection, delete_collection, set_publishing_mode, and the development environment tools |
The publishing permission is ignored for editors. Both the app's flag and the user's live role are checked on each request.
Tools
Every tool returns text or JSON. Ids come from earlier tool results. Inputs marked as required must be sent.
Site and schema
| Tool | Access | Inputs | What it does |
|---|---|---|---|
get_site_info | Everyone | None | Returns the site (including its publishingMode and whether a deploymentConfigured target is set), your role and capabilities, cannotDo (what this key may not do, in plain sentences), every collection, every site content section (globals: key, label, enabled, field count, seo, previewPath), and a short writing guide. Call it first. |
get_collection_schema | Everyone | collection (required) | Returns each field's key, label, type, required flag, format, options or referenced collection, and an example fields and seo payload. On a site with languages, each field also says whether it is translatable, and an example update_entry call with a locale shows how to write one language. |
Entries
| Tool | Access | Inputs | What it does |
|---|---|---|---|
list_entries | Everyone | collection (required), status (draft, ready, scheduled, archived), search, limit (1 to 100, default 20), offset, missingLanguage (languages sites) | Lists entry summaries with id, slug, status, live, pendingRelease, title and version, plus total and nextOffset. missingLanguage keeps only entries whose draft is not complete in that language, such as "Missing translation: English". |
get_entry | Everyone | id (required), includePublished, locale (languages sites) | Returns one entry with fields, seo, status, live, pendingRelease, approvedAt and version. With locale, the values, seo and slug of that language. Without it, the main language plus translations: each other language's complete flag and missing fields. With includePublished, also returns liveFields, the version visitors see now. See reading entries. |
create_entry | Everyone | collection (required), fields, seo, slug, locale (languages sites) | Creates a draft and returns the entry and its previewUrl. Put the slug in fields, under the collection's field of type slug (usually slug), or send it as slug. It must be unique in the collection. With a locale other than the main language, the values go to that language; the entry then has no main-language content yet, so it cannot be marked ready until the main language is filled in. |
update_entry | Everyone | id (required), fields, seo, slug, locale (languages sites), expectedVersion | Changes only the keys you send. Clear a field with "", and an SEO key with "", false or []. See clearing values. Send at least one of fields, seo or slug. With locale, the changes apply to that language, and shared fields change in every language (listed in sharedFieldsUpdated). Editing a ready entry does not change the live site. |
list_revisions | Everyone | id (required), cursor, limit (1–100, default 30) | Lists the history of an entry, newest first: revision id, note, time and author. It returns one page. To get older revisions, pass cursor set to the id of the last revision you received. A page shorter than limit is the end of the history. Use a revision id with restore_revision. |
get_preview_url | Everyone | id (required), locale (languages sites) | Returns a signed preview link for an entry, drafts included, in the given language when one is set. It expires after about one hour. Fails when the site has no production URL. |
Site content
Site content sections (globals) are single sets of values, such as company details or the home page. They are not collections, so create_entry and list_entries refuse their keys. Use the section key with the tools below, and the section's entry id (from get_global) with the workflow tools.
| Tool | Access | Inputs | What it does |
|---|---|---|---|
get_global | Everyone | global (required, the section key), locale (languages sites) | Returns the section's id, key, label, status, live, pendingRelease, version, editedSinceReady, fields, an example, values, seo (when SEO is on) and previewAvailable. With locale, the values of that language; without it, translations for the other languages. Disabled sections are for agency admins only. |
update_global | Everyone | global (required), fields, seo, locale (languages sites), expectedVersion | Changes only the keys you send, like update_entry, and takes locale the same way. seo is refused unless the section has SEO on. |
discard_global_draft | Everyone | global (required) | Sets the draft back to the ready version, or the scheduled version. Refused until the section has been ready once. |
upsert_global | Agency admin | key (required), label, fields (complete list), seo, previewPath, enabled, requiredLanguages | Creates or changes a section. fields may set group, the editor heading for a field; collection fields may not. slug fields are refused. On a site with languages, a field may set translatable, and requiredLanguages lists the languages the section must be complete in. |
delete_global | Agency admin | key (required) | Deletes a section. Its content is kept in the deleted-content archive. |
mark_ready, schedule_entry, cancel_schedule, list_revisions, restore_revision and get_preview_url accept the section's entry id. unpublish_entry, archive_entry and delete_entry refuse it. The preview of a section is its previewPath on the site, so get_preview_url needs that path to be set.
Library
| Tool | Access | Inputs | What it does |
|---|---|---|---|
list_assets | Everyone | search, limit (1 to 100, default 20), offset | Lists images and PDFs in the client's Library, with assetId, filename, alt text and URL. |
get_asset | Everyone | id (required) | Returns one asset with size, alt text, usageCount and a preview image. |
upload_asset_from_url | Everyone | url (required), altText, filename | Imports a public image or PDF and returns its assetId. See uploads from a URL. |
create_asset_upload | Everyone | purpose | Opens an upload link for local files and chat attachments. See upload links. |
get_asset_upload | Everyone | uploadId (required), finish | Returns the files sent to an upload link, with previews. finish: true closes the link. |
update_asset_alt | Everyone | id (required), altText (required; empty clears it), locale (languages sites) | Sets the alt text of an asset, for the main language or for locale. |
delete_asset | Owner | id (required) | Deletes an asset. Refused while an entry, revision or published snapshot uses it; the error says how many. |
Publishing
| Tool | Access | Inputs | What it does |
|---|---|---|---|
get_release_status | Everyone | None | Returns the live and demo publications, any running build, the publishing mode, and pendingChanges: entries that are ready but not live, scheduled entries, and entries withdrawn since the last go live. languageChange lists the site languages added or removed, and a new main language, since the last go live, or is null when nothing changed. |
list_publications | Everyone | limit (1 to 50, default 10) | Lists recent go lives, newest first, with publicationId, status (pending, building, live, failed, superseded or rolled_back), who requested them and their build job. |
get_build_job | Everyone | jobId (required) | Returns one build job's status, attempts, timing and error. |
go_live | Go live | note (up to 500 characters) | Queues a build that publishes every approved change. Follow it with get_build_job. |
rollback | Go live | publicationId (required) | Queues a rollback that makes an earlier publication live again. |
set_publishing_mode | Agency admin | mode (required: demo or production) | Sets how the site publishes. demo: go_live builds a release that areca pull can fetch without a deployment; demo releases never reach visitors. production: go_live deploys to the live site and needs a deployment target (application ID and health check URL), set in Settings › Deployment. Refused when switching to demo after the site has ever gone live, and when switching to production without a deployment target. Setting the mode the site already has changes nothing. A change is refused while a release of the site is queued, building, validating or deploying, or its outcome is unknown. Returns publishingMode and deploymentConfigured. |
cancel_build | Go live | jobId (required) | Cancels a build that is queued. A build that has started cannot be cancelled. |
Workflow (owners)
| Tool | Access | Inputs | What it does |
|---|---|---|---|
mark_ready | Owner | id or ids (up to 200 ids; one result per id), expectedVersion (with id only) | Approves the current draft for the next release. Checks required fields and names any that are missing. On a site with languages it also names the languages that are not complete, and the fields, and says to fill them with update_entry and locale. Pass expectedVersion from get_entry to approve exactly the draft you reviewed: if the entry changed since, the call fails with a conflict and nothing is approved. It returns a message that the entry is approved and goes live with the next release. It does not change the live site. |
unpublish_entry | Owner | id (required) | Withdraws the entry from the release: its status becomes draft and the approved version is dropped. The draft is kept. The live site stops showing it at the next go live. |
archive_entry | Owner | id (required) | Archives the entry. The live site is updated at the next go live. |
schedule_entry | Go-live permission | id (required), publishAt (required; ISO 8601 with an offset, in the future), timezone (required; IANA name such as Europe/Amsterdam), expectedVersion | Sets the status to scheduled. The entry goes live at that time without a go_live call. Pass expectedVersion from get_entry to schedule exactly the draft you reviewed. If the entry changed since, the call fails with a conflict and nothing is scheduled. Date fields in the content do not schedule anything. |
cancel_schedule | Owner | id (required) | Removes the scheduled time. The draft and any live version are kept. |
delete_entry | Owner | id (required) | Deletes the entry. Use archive_entry when unsure. |
restore_revision | Owner | id (required), revisionId (required), expectedVersion | Copies an earlier revision into the draft. It does not mark the entry ready and does not change the live site. |
Languages (agency admins)
| Tool | Access | Inputs | What it does |
|---|---|---|---|
set_site_languages | Agency | main (required), all (required, every language in order, including main) | Sets the site's languages and its main language. Removing a language hides it but keeps its text. Changing the main language deletes nothing, but entries not complete in the new main language cannot be marked ready until they are. The result reports the gaps. |
Content types (agency admins)
| Tool | Access | Inputs | What it does |
|---|---|---|---|
upsert_collection | Agency | key, label, routePattern, fields (the complete list), enabled, requiredLanguages, routes (languages sites) | Creates a collection, or replaces its label, route, fields and enabled flag. A slug field is added if none exists. Fields replace the whole list, so send every field. On a languages site, a field may set translatable, requiredLanguages lists the languages that must be complete before ready, and routes gives a route pattern per language (each contains $slug, without the language prefix). It refuses to remove a field that still holds content, and names the field and its entry count. This tool cannot confirm the removal. Remove the field in the CMS, where you confirm the loss, or push the schema with areca manifest push --confirm-drop <collection.field>. |
delete_collection | Agency | key (required) | Deletes a collection and its entries. |
If the site syncs its content types from code, the next manifest push replaces changes made with these tools. Prefer changing the site's configuration.
Development environments (agency admins)
A production site can have one development environment. Agency admins work in it first and push the reviewed changes to production. Its connector URL is site.devEnvironment.connectorUrl in get_site_info, which the production connector gives. The production connector offers create_dev_environment. The development connector offers the tools below, and does not offer the publishing tools (go_live, rollback, cancel_build, list_publications, get_release_status, get_build_job) or the schedule tools. On the agency connector the same tools are listed for every site, and you pass the development environment's slug as site. See Development environments for the workflow.
| Tool | Connector | Inputs | What it does |
|---|---|---|---|
create_dev_environment | Production | confirm (must be true) | Creates the development environment, with the production schema and ready content. Returns its slug and connector URL. Never returns tokens; they are shown once, in the browser that created the environment. For a missed token, rotate it in Settings › Developer on the development site. |
refresh_from_production | Development | confirm (must be true) | Replaces the development environment's content with production's. Unpushed changes are discarded; the result says how many. |
push_to_production | Development | dryRun (default true), planHash, exclude, apply, goLive | With dryRun true, lists every change with its needs-attention items and a planHash, and writes nothing. With dryRun false and the planHash, pushes the changes that were not excluded. Needs-attention items are pushed only when listed in apply. Items marked scheduled or slug-taken cannot be applied. goLive starts a production build and needs the go-live permission. Refused with "The plan changed" when the plan moved since the dry run. |
copy_entry_to_production | Development | id (required), markReady, overwrite | Copies one development entry into production as its draft. The live version does not change unless markReady is true, which also makes the copied draft the production ready version (ready is not live; a release is still needed). Refused on a schema mismatch, on a production change since the last sync unless overwrite is true, and when a URL is used by another production entry. |
Prompt
The server offers one prompt, write_post, with the arguments collection and topic. It asks the model to read the site and collection schema, draft the content in Markdown, fill in SEO, upload or find images with alt text, create a draft, share the preview URL, and not mark the entry ready or go live unless asked.
Languages
On a site with several languages, every content tool that reads or writes text takes an optional locale (a code from get_site_info). Leave it out for the main language. Write one language per call: fields, seo and slug apply to that language. Fields that are not translatable are shared, so they change everywhere; update_entry and update_global list them in sharedFieldsUpdated. Check the other languages with get_entry. Its translations value gives each language's complete flag and missing fields.
On a site with one language nothing here changes, and a locale is refused with a clear message.
Reading entries
Every entry read returns the same shape. Collection data is under fields, and system values sit at the top level, so a field can never collide with them.
| Key | Meaning |
|---|---|
status | draft (being written), ready (approved for the next release), scheduled or archived. |
live | true when the entry is in the production live publication. Demo previews do not count; inspect get_release_status.demo for the demo publication. |
pendingRelease | true when approved changes, an approval, a schedule or a withdrawal are waiting for the next go live. |
approvedAt, scheduledAt, scheduleTimezone | Approval and schedule times, when set. |
editedSinceReady | true when the draft differs from the approved version. |
previewAvailable | true when the site has a production URL. |
version | Pass it as expectedVersion on the next write. |
liveFields | Only with includePublished: fields from the actual live publication, or null if this entry is not live. Demo previews are not reported as live fields. |
approvedFields | Only with includePublished: the approved version waiting for release, separate from the live publication. |
ready means approved, not live. A ready entry can still show live: false until the next go live. Use live and pendingRelease to answer "what is live" and "what is pending". get_release_status lists the pending changes.
Field values
Get the exact format for each field from get_collection_schema. The formats are below. Date and datetime fields are content you write, such as an event date. They do not publish or schedule an entry. Use schedule_entry for that.
| Field type | Write | Read |
|---|---|---|
text | Plain text, up to 1,000 characters | Same |
textarea | Plain text with line breaks, up to 50,000 characters. No Markdown. | Same |
richtext | A Markdown string. See rich text. | Markdown |
image | An asset id, such as "3f2c…", from list_assets, upload_asset_from_url or get_asset_upload | {assetId, url, alt}, or null |
reference | The id of an entry in the referenced collection, from list_entries | {entryId}, or null |
date | YYYY-MM-DD | Same |
datetime | ISO 8601 with an offset, such as 2026-10-10T09:00:00+02:00 | Same |
select | One of the listed options, spelled exactly | Same |
slug | Lowercase letters, numbers and dashes, such as my-first-post. Unique in the collection, up to 200 characters. | Same |
boolean | true or false | Same |
number | A JSON number | Same |
url | An absolute http or https URL | Same |
Unknown field keys are refused, and the error lists the valid keys.
Clearing values
On update, a value is cleared with a spelling that matches its type. Leaving a key out keeps its stored value.
| Where | Clear with |
|---|---|
| A field | "" (an empty string) |
| SEO text keys | "" |
SEO noindex, nofollow | false |
SEO lists (keyTakeaways, faq, about) | [] |
SEO author | an empty name |
Do not send null. The input schema does not accept it for SEO keys.
Rich text
Write rich text as Markdown, never as HTML. Areca converts it to the stored format.
- Use
##for section headings, and###or####for subsections. Other heading levels are adjusted to these. - Separate paragraphs with a blank line. Use
**bold**,*italic*and[links](https://…). - Use bullet and numbered lists,
>blockquotes and tables. - Put images in the text as
. The asset id must belong to the client's Library, or the write is refused. - Raw HTML is dropped. An image written with a full
httpsURL is copied into the Library when the entry is saved, and the text then points at the copy. If a copy fails, the image keeps its URL and the result has awarningslist that names it. Uploaded assets are still preferred, because you control their alt text.
When an entry is read back, its images appear as asset:<assetId> in the Markdown, so the same text can be sent again.
SEO object
The seo argument has these optional keys. To clear one on update, use the spelling in clearing values. Unknown keys are refused. Stored values follow the same limits as the SEO panel in the CMS. See SEO and AI suggestions.
| Key | Type | Limit | Notes |
|---|---|---|---|
title | Text | 300 characters | Target about 50 to 60 characters. Falls back to the entry title. |
description | Text | 1,000 characters | Target 140 to 160 characters. Falls back to the summary. |
focusKeyphrase | Text | 200 characters | The search phrase the entry targets. Not shown to visitors. |
canonicalUrl | URL | 2,048 characters | Only when the page copies another URL. |
noindex, nofollow | Boolean | true keeps the page out of search results, or stops crawlers following its links. | |
ogTitle, ogDescription | Text | 300 and 1,000 characters | Shown when the page is shared. Fall back to the SEO title and description. |
ogImage | Asset id | A social share image, ideally 1200 × 630. | |
twitterCard | summary_large_image or summary | ||
schemaType | Article, BlogPosting, NewsArticle or WebPage | ||
summary | Text | 2,000 characters | Two or three sentences that answer the main question. |
keyTakeaways | List of text | Up to 12 items, 500 characters each | Aim for 3 to 5 self-contained points. |
faq | List of {question, answer} | Up to 20 items; question 500 characters, answer 3,000 | Only real questions the entry answers. |
about | List of {name, sameAs} | Up to 12 items; name 200 characters; sameAs an optional URL | The entities the entry is about. |
author | {name, url, bio} | Name 200 characters, bio 1,000 characters, url optional | The author of the entry. An empty name removes it. |
jsonLd | Text (JSON) | 20,000 characters | Extra schema.org structured data, one object or an array. Usually leave it unset: the CMS writes the main node from the other keys. |
The agent is asked to fill in title, description, focusKeyphrase, summary, keyTakeaways and faq for every new entry. There is no separate SEO-generation tool. The agent writes these values itself.
Errors
Errors from tools come back as a normal response with isError: true and a message written for the AI. They are not HTTP errors. Typical messages:
| Message starts with | Meaning and next step |
|---|---|
Validation failed. | A field is missing, unknown or in the wrong format. Call get_collection_schema and retry. |
This entry changed since you last read it | The expectedVersion is stale. Call get_entry, reapply the change and retry with the new version. |
| A slug message | The slug is already used in the collection. Choose another. |
This asset is still used by N item(s) | delete_asset is refused. Remove the asset from those items first. |
Could not import the file: | The URL could not be imported. See uploads from a URL. |
Only site owners can…, Going live wasn't allowed… / Going live is not enabled for this key…, Only agency admins… | The caller's role or key does not allow the tool. The message says who can do it. |
No entry with id…, No collection…, No asset… | The id or key is not on this site. Use the list tool to find valid ids. |
Something went wrong on the Areca server (ref …) | An unexpected error. Nothing is confirmed as saved. The reference is in the server log. |
Other failures use HTTP status codes:
401for a missing, invalid, revoked or expired key, as described under authentication.403with"code": "mfa_required"for an agency admin without two-factor sign-in, as described under authentication.429when a rate limit is reached, per key (120 a minute) or per client address (600 a minute). The body is{ "error": "Too many requests" }, and theRetry-Afterheader gives the wait in seconds. See rate limits.
Uploads from a URL
upload_asset_from_url downloads a file from the public internet and stores it in the client's Library. Areca accepts only:
- JPEG, PNG, WebP, GIF or PDF files. The file's bytes decide the type, not the
Content-Typeheader. - Files up to 20 MB.
httporhttpslinks on ports 80, 443, 8080 or 8443, without a username or password.- Addresses on the public internet. Private, local, loopback and link-local addresses are refused, including names that resolve to them.
- Up to three redirects, each checked in the same way.
- Downloads that finish within 15 seconds.
When a download fails, the error begins Could not import the file: and then says why. The main cases are:
| Message | Meaning and next step |
|---|---|
Unsupported file type. Use a JPEG, PNG, WebP, GIF or PDF file. | The bytes are not one of those types. |
The link returned a web page (text/html), not an image. Use a direct image URL. | The link points at a page. Use the file's own address. |
The link returned a file of type …, not an image or PDF. | The host sent another kind of file. |
The file is larger than 20 MB | Use a smaller file, or ask the user to upload it in the Library. |
Port … is not allowed. Use 80, 443, 8080 or 8443 | Use a standard web port. |
Links with a username or password are not supported | Use a public link without credentials. |
That address is private or local and can't be downloaded | Only public addresses are allowed. |
The domain name could not be resolved or Could not reach the server | Check the host name, or try again later. |
The link redirected too many times | Use the final address. More than three redirects are refused. |
The download timed out | The file did not arrive within 15 seconds. |
The link didn't return a downloadable file (HTTP 4xx …) | Check the link opens directly as an image or PDF. |
The server at … failed (HTTP 5xx …) | The host is down. Try later or use another link. |
The import is recorded in the activity log as an upload ("uploaded … to the Library"). Always pass altText, which describes the image in one sentence.
Upload links
Most images an AI works with have no public URL: a file on your computer, or a photo you attached to the chat. An AI app can't pass those bytes through a tool call, so Areca gives it an upload link instead. You never need to host an image somewhere first.
- The AI calls
create_asset_upload. It can pass apurpose, such as "Photos for the spring fundraiser post", which the page shows. - The files arrive one of two ways:
- Apps with a shell (Claude Code, Codex, Cursor) send the files themselves:
curl -sS -T photo.jpg "<uploadUrl>/", once per file, orcurl -sS -T "{a.jpg,b.png}" "<uploadUrl>/". Afterwards they callget_asset_uploadwithfinish: true, which closes the link. - Chat apps (claude.ai, ChatGPT, mobile apps) give you the
pageUrl. Open it, drop or paste the images (or pick them from your camera roll), check them in the tray, and press Upload. The link closes once everything is in. Then tell the AI you're done.
- Apps with a shell (Claude Code, Codex, Cursor) send the files themselves:
- The AI calls
get_asset_upload. It gets each file'sassetIdand a preview of each image, so it can see what you sent, decide where each image goes, and write alt text withupdate_asset_alt.
| Limit | Value |
|---|---|
| Files per link | 20 |
| Size per file | 20 MB |
| Types | JPEG, PNG, WebP, GIF, PDF. HEIC and SVG are refused; iPhones convert photos to JPEG when you pick them in the browser. |
| Lifetime | 20 minutes from when the AI created it, or until it is finished |
The link works without signing in, so treat it like a short-lived password. It stops working once it is used or expires, and also if the person whose AI connection created it loses access to the client. Uploaded files appear in the Library and in list_assets, like any other upload.
Activity and audit
Every change made through a connected app or a personal key is recorded as the user it acts for, with the app's name (or the key's name), so it appears as via AI · name in the activity log. Changes made through the agency connector are recorded with the agency connector as their source and the name of the key or connection. Connecting an app to a site is recorded as mcp_connection.create and disconnecting it as mcp_connection.revoke; personal keys are recorded as mcp_key.create and mcp_key.revoke. The log stores the action and the name, not the text of the content. Changes to a development environment are visible to agency admins only.
Related
- Connect AI for editors and owners.
- Tokens for how connections and keys are stored and revoked.
- Rate limits for the sign-in endpoints.
- Permissions matrix for the roles behind each tool.
- Rate limits for the per-key limit.
Scheduling requires Allow going live because the scheduler deploys the site at the chosen time, including other approved changes. Cancelling a schedule remains an owner action. Rich-text image references accept ordinary Markdown titles and reference-style syntax; image examples inside code are not treated as assets.