Subcore AI Docs
Agents API

Overview

Register agents, write their prompt files, and let a running deployment read them back on every call.

One deployment can answer calls for many agents from a single codebase. This API is how a partner registers each of those agents with Subcore and stores the prompt files it should answer with. The deployment reads that configuration back on every call, so a prompt edit is live on the next one.

There are two surfaces:

SurfaceWho calls itWhat it does
/agentsYou, from your own toolingCreate or update an agent, and list, write or remove its prompt files
/agent-config/{routingKey}Your deployment, on every callReturns one agent's settings and files. Authenticated with the deployment's own key

Base URL: https://api.subcore.ai

Authentication

Every request carries a Subcore API key as a bearer token, the same way the telemetry read API does:

Authorization: Bearer sk_live_...
Content-Type: application/json

The key resolves to one organization, and every query is scoped to it. An agent that belongs to another organization gets the same answer as one that does not exist.

The manage-agents permission

The /agents routes need a key whose permissions.manageAgents is exactly true. You grant it yourself: an organization admin ticks Manage agents on the key in the Subcore console, under Settings → API keys. No key has it by default, and a key issued before the permission existed does not gain it, so an existing key needs the box ticked rather than being replaced. A key without it authenticates and is then refused:

{
  "error": "This API key cannot manage agents: it lacks the manageAgents scope",
  "code": "FORBIDDEN",
  "timestamp": 1789569967913
}

The key your deployment runs with is a different key. It is issued from the deployment's own page in the Subcore console, it reads configuration and sends telemetry, and it never carries the manage-agents permission. See connecting your deployment.

Conventions

Every answer is Cache-Control: no-store. A response body here can be a prompt, and an edit is live on the next call, so nothing between you and Subcore may keep a copy.

Every refusal has the same shape, the envelope used across api.subcore.ai. Switch on code; the sentence in error may change.

{ "error": "No agent with this slug", "code": "NOT_FOUND", "timestamp": 1789569967913 }

The full list of codes is under errors.

An agent is a core. The slug here is the same slug the telemetry API reports as core_slug and filters by as coreSlug, and the same one a key's permissions.cores names. It is the name you choose, and it appears in every /agents URL. The routing key is minted by Subcore when the agent is created and never changes. It is the agent's address on your deployment.

What makes a deployment answer for an agent

Nothing on an agent names a deployment, and no route here has a field for one. An agent is bound to a deployment by two things and no third: the organization it was created in, and the address a call arrives on.

Your deployment answers {tenantPath}/… for any routing key it is given, reads that agent's configuration with its own deployment key, and serves the call with what comes back. So it answers for every agent in its own organization whose configuration that key can read. Creating the agent with the right key is the whole binding, and there is nothing to switch on afterwards.

A deployment key issued in the console reads all of them. Its cores permission decides whether it may read agents at all, and reading is on unless someone turns it off; there is no setting that leaves it some agents and not others. Turn it off and the config read answers 403 FORBIDDEN for every agent, which is why a key that reads one agent reads the rest.

An agent still on repo is answered with the deployment's own files, so it works from the moment it exists. Once you move one to database, one more thing has to line up, and it is not a binding either: the files have to sit under a directory the deployment serves, or the call fails at the deployment rather than falling back.

Rate limits

The /agents routes allow 120 requests per minute per key. The count is per key once a request has authenticated, and per client address before that. Every answer carries the count:

HeaderMeaning
X-RateLimit-LimitRequests allowed in the window
X-RateLimit-RemainingRequests left in this window
Retry-AfterSeconds until the window resets. Sent on a 429 only

Over budget, you get a 429 with code: "RATE_LIMITED". The config read your deployment makes is not rate limited, because a refusal there would fail a live call.

Setting up an agent

The order matters. Two guards stop an agent from reading its prompts from the database while it has none stored, so the files go in before the switch.

Create the agent

PUT /agents/{slug} with its name, timezone and phone numbers. A new agent starts on instructionSource: "repo" and answers with the shared files in your deployment. The answer carries its routingKey and tenantPath. See agents.

Write its files

PUT /agents/{slug}/instructions/{path} for each prompt file. See instructions.

Switch it to the database

PUT /agents/{slug} with { "instructionSource": "database" }. From the next call on, the files you stored are laid over the deployment's own by path: one you also store replaces it, one it has and you do not is inherited, and one it has never had is added. Switching before any file is written is refused with 409 NO_INSTRUCTIONS.

Test it

Run a text turn through the agent with the files you stored. Nothing on this API compiles what it stores, so this is where a [[partial]] your deployment does not have shows up.

POST {deployment}{tenantPath}/chat/message
Authorization: Bearer <your deployment's chat credential>
Content-Type: application/json

{ "message": "Hi, are you open on Saturday?", "sessionId": null }

{deployment} is wherever you host the deployment, since Subcore does not know that address. The credential is the deployment's own, not the key you called /agents with: either the CHAT_API_SECRET in its environment, or the key it minted for itself, which the console shows on its page and which starts sk_dep_. A deployment with neither answers without a credential.

sessionId: null asks for a new conversation, and the reply names it so you can send the next turn into the same one. A turn that runs streams back as one JSON object per line: a session line, then token lines, then finalize.

A turn refused before that stream opens answers with a status and a plain { "error": …, "code": … } instead, and this is where a file that does not compile shows up. 401 is the wrong credential. 404 means no agent answers on that routing key. 500 carries a code naming what failed, such as compile for a file the deployment could not build, merge for one it refused, or agent_missing for files stored under a directory it does not serve. 503 means it could not reach Subcore to read the configuration at all.

The body above is what the kit accepts unless your deployment defines its own shape, in which case send that one.

Point the number at it

Route the agent's phone number to {deployment}{tenantPath}/incoming-call.

What is not here

This site documents api.subcore.ai. Your deployment's own doors, such as {tenantPath}/chat/message and {tenantPath}/incoming-call, belong to the Subcore kit and are documented with it. The one above is set out here only because testing an agent you just stored files for needs it.

The same goes for the deployment's own code: which resolver keys a {{variable}} may name, and how to add one, are decisions made there rather than here. What a stored file may contain is set out on prompt files.

On this page