API reference
Base URL: https://api.configure.dev. Every server-side request carries two headers:
| Header | Value |
|---|---|
X-API-Key | Your secret key, sk_ prefix. Server only. |
X-Agent | Your 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.
Mint a connect link
text
POST /v1/profile/connectThe 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:
| Field | What it does |
|---|---|
app | Ask for one connector, for example gmail. The flow opens on that app. |
capability | Ask for a capability, for example gmail:send. |
purpose | One sentence shown to the user about why you are asking. |
client_name | The name the hosted page shows, if it differs from your agent name. |
return_url | Where the user lands after finishing. |
user_id | The 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/profilePass 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| Field | Bounds |
|---|---|
messages | Up to 20 messages, up to 16,000 characters each. |
response | Optional assistant reply, appended as the trailing message. |
memories | Up to 20 explicit memories, up to 1,000 characters each. |
sync | Default 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-sessionReturns 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:
| Method | Input | Returns |
|---|---|---|
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/provisionUnauthenticated 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/tokenThe 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 script | https://api.configure.dev/mcp-apps/configure-mcp-host.js |
| Current resource | ui://configure/connect/v8.html |
| Status of a link | GET <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:
| Code | What it means | What to do |
|---|---|---|
API_KEY_MISSING | No key reached the call | Check the server's environment |
AUTH_REQUIRED | 401 or 403, see below | Branch on statusCode, not this code |
ACCESS_DENIED | The tool is outside the set you declared | Widen tools() or do not call it |
TOOL_NOT_CONNECTED | The user has not connected that app, or its access expired | Mint a link for that app |
COMMIT_REQUIRED | A read left an obligation this turn has not cleared | Commit the turn, then retry |
PAYMENT_REQUIRED | Your account is over a quota | Your limit, not the user's connection |
RATE_LIMITED | 429 | Back off using retryAfter |
INVALID_INPUT | The SDK rejected the arguments before sending | Fix the call |
NOT_FOUND | 404 | Check the id |
TOOL_ERROR | The connector itself failed | Surface it, do not offer a link |
TIMEOUT | The request exceeded your client timeout | Answer without it |
NETWORK_ERROR | The request never completed | Answer without it |
SERVER_ERROR | 500 and above | Retry, 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.