Skip to content

API reference

Base URL: https://api.configure.dev. Every server-side request carries two headers:

HeaderValue
X-API-KeyYour secret key, sk_ prefix. Server only.
X-AgentYour agent handle.

Requests about a specific user also identify the user, one of two ways: Authorization: Bearer with the token from sign-in, or X-User-Id with your own id for a user who has not linked yet.

The SDK wraps every endpoint on this page. Reach for raw HTTP when neither the TypeScript SDK nor MCP fits your stack.

text
POST /v1/profile/connect

The only way a Configure link comes into existence. Links are minted per user, per session, and expire; never build one yourself.

Every field is optional:

FieldWhat it does
appAsk for one connector, for example gmail. The flow opens on that app.
capabilityAsk for a capability, for example gmail:send.
purposeOne sentence shown to the user about why you are asking.
client_nameThe name the hosted page shows, if it differs from your agent name.
return_urlWhere the user lands after finishing.
user_idThe user, when you are not sending a bearer token.
bash
curl -X POST https://api.configure.dev/v1/profile/connect \
  -H "X-API-Key: $CONFIGURE_API_KEY" \
  -H "X-Agent: $CONFIGURE_AGENT" \
  -H "X-User-Id: user_842" \
  -H "Content-Type: application/json" \
  -d '{ "purpose": "Use your preferences in replies" }'
json
{
  "status": "authorization_required",
  "connect_url": "https://mcp.configure.dev/connect/mcc_...",
  "expires_at": "2026-08-31T20:14:00Z",
  "instructions": "Show the link to the user so they can connect."
}

status is one of authorization_required, connect_app, permission_needed, or import_ready. Show connect_url to the user verbatim and treat expires_at as authoritative.

Minting shares the account rate limit. Over it you get 429 with code rate_limited and a retry_after in seconds. Back off and reuse the link you have, which stays valid until its expires_at.

Read a profile

text
GET /v1/profile

Pass sections to scope the read; identity, preferences, and summary is the right scope for prompt injection. An unscoped read returns everything the user has approved for your agent and can run long.

The response carries linked, identity, preferences, summary, connections, integrations, imports, and agents. When a box was too long to return whole, truncated and truncated_boxes appear alongside them and name what was cut.

Commit a turn

text
POST /v1/profile/commit
FieldBounds
messagesUp to 20 messages, up to 16,000 characters each.
responseOptional assistant reply, appended as the trailing message.
memoriesUp to 20 explicit memories, up to 1,000 characters each.
syncDefault false: the commit may be queued.

The response carries status: processing when the commit was queued, completed when it ran inline, with facts_written, user_summary, memories_written, and rejected_memories once it has run. With sync: true it always waits for them.

Committing is also how your server pays the read contract: a -32009 commit_required refusal anywhere on the surface clears when the turn is committed.

Open an MCP session

text
POST /v1/profile/mcp-session

Returns linked plus an mcp_servers block ready to hand to a model provider:

json
{
  "linked": true,
  "mcp_servers": [
    {
      "type": "url",
      "url": "https://api.configure.dev/mcp",
      "name": "configure",
      "authorization_token": "..."
    }
  ]
}

Sessions are short-lived, about 15 minutes; mint one per conversation. A session is minted for any user you name; whether they have connected comes from connected on the connect endpoint or linked on the profile read, not from this response.

Write to the profile

Four SDK methods, each a POST under /v1/profile with the same names:

MethodInputReturns
remember(fact, { box?, kind?, reply_to?, resolves? })One durable fact, filed to a box{ saved, memory, source, livemode }
forget({ id } | { match } | { import_id } | { scope }, { reason? })Exactly one selector. id deletes on the spot, because you already named one memory. match, import_id, and scope preview what they would delete and need confirm: true on a second call to go through{ deleted, deleted_count, matched }
import(text, { box?, kind? })A chunk of context to distill, such as a pasted transcript{ saved, box, distilled, memory, source, livemode }
search({ query?, box?, source?, from?, to?, limit? })A lookup across what this user approved for you{ results, visibleSources, sourceCounts, guidance, livemode }

An agent can forget only what it saved itself. commit is the only write with bounds; these take what you give them.

Provision the sandbox

text
POST /v1/sandbox/provision

Unauthenticated on purpose and per-IP rate limited: an agent evaluating Configure gets working keys in one call, with no signup. Returns sk_test_ and pk_test_ keys and a pre-linked synthetic user, so you can rehearse sign-in, first-turn injection, and commits before a real user touches anything. The test user's verification code is always 424242.

With the sk_test_ key: POST /v1/sandbox/reset restores the synthetic user to her seeded state, and POST /v1/sandbox/simulate with { "scenario": "gmail_token_expired" } makes her Gmail connection fail like a revoked token until the next reset.

Exchange the sign-in code

text
POST /oauth/token

The authorization-code exchange behind /callback. Note the path: no /v1 prefix. The SDK method configure.auth.exchangeSignInCode(code) wraps it and returns { token, userId }; use the SDK here unless you cannot.

The card

The card is an MCP app. Mount it with Configure's host script and the unchanged connect() result; the resource id names the card version, and every version keeps serving, so a mounted card never breaks.

Value
Host scripthttps://api.configure.dev/mcp-apps/configure-mcp-host.js
Current resourceui://configure/connect/v8.html
Status of a linkGET <connect_url>/status; answers { "status": "pending" }, approved, or expired

When a call fails

Every SDK method rejects with ConfigureError. An error built from an API response carries code, statusCode, type, retryable, suggestedAction, docUrl, retryAfter and requestId, so requestId is what to quote when you ask us about one.

code is one of thirteen values, exported as ErrorCode:

CodeWhat it meansWhat to do
API_KEY_MISSINGNo key reached the callCheck the server's environment
AUTH_REQUIRED401 or 403, see belowBranch on statusCode, not this code
ACCESS_DENIEDThe tool is outside the set you declaredWiden tools() or do not call it
TOOL_NOT_CONNECTEDThe user has not connected that app, or its access expiredMint a link for that app
COMMIT_REQUIREDA read left an obligation this turn has not clearedCommit the turn, then retry
PAYMENT_REQUIREDYour account is over a quotaYour limit, not the user's connection
RATE_LIMITED429Back off using retryAfter
INVALID_INPUTThe SDK rejected the arguments before sendingFix the call
NOT_FOUND404Check the id
TOOL_ERRORThe connector itself failedSurface it, do not offer a link
TIMEOUTThe request exceeded your client timeoutAnswer without it
NETWORK_ERRORThe request never completedAnswer without it
SERVER_ERROR500 and aboveRetry, then fail open

WARNING

401 and 403 both arrive as AUTH_REQUIRED, and they are opposite problems. 401 means your key is wrong or revoked, which affects every user at once. 403 means this one user has not approved your agent. Code that branches on code alone and caches the answer will mark every reader unconnected the moment a key rotates. Match statusCode when you need to tell them apart.

Fail open. A read, a commit or a tool call that throws should cost the turn its personalization, never its answer.

Everything else

The rest of the profile surface, search, remember, forget, and import, mirrors the SDK methods of the same names. The TypeScript SDK is the reference implementation; when this page and the SDK disagree, the SDK wins.

Personalization infrastructure for agents