Create an API key
Go straight to key management in the hosted app. If you’re signed out,
you’ll be asked to sign in and then land right back on the API keys page.
- Discover your resources — list your projects, their servers, eval suites, and chat sessions, so every ID the other routes need is self-serve
- Validate a server — connect, initialize, and capture a capability snapshot
- Run the doctor — the probe → connect → initialize → capabilities workflow
- Check OAuth requirements — does this server need an OAuth grant?
- List tools, prompts, and resources — the server’s MCP primitives
- Call a tool / render a prompt — execute primitives and get the MCP result back verbatim
- Read a resource by URI
- Export a full snapshot — tools, resources, and prompts as one JSON document
- Manage a project’s hosts — list, read, create (from a built-in
template like
claude/chatgpt/cursor, or from a full host config), rename, and delete the named model + capability profiles you run chats and eval suites against - List a project’s chatboxes — name, access mode, attached servers, and share link — and read one chatbox’s settings: model, system prompt, tool-approval policy, and resolved servers
- Run eval suites asynchronously — create a run, get a
202+runIdimmediately, then poll status, per-iteration results (tool calls, token usage, latency), and full traces. Runs appear live in the hosted UI, taggedsource: "api" - Import OAuth tokens — complete OAuth yourself (e.g. the SDK’s
runOAuthLogin) and push the tokens; subsequent calls inject and refresh them server-side
Base URL
Authentication
Every request needs an MCPJam API key in theAuthorization header:
Creating a key
- Open Settings → API keys in the hosted app (the card at the top of this page takes you there directly).
- Click Create API key, name it, and pick the organization it will act in.
- Copy the key (
sk_…) immediately — it is shown exactly once and never stored by MCPJam in retrievable form.
Scope
A key is bound to one MCPJam organization at creation and acts as you, inside that organization. Per-request authorization still applies: a call only succeeds if the key’s owner can access that project and server. A key can never reach projects outside its organization.Two deliberate restrictions while in preview:
- API keys cannot manage API keys. Requests to the key-management surface
with an
sk_…bearer fail with403 FORBIDDEN. Create and revoke keys in the UI. - Guest sessions cannot use the API. Sign in to create keys.
Keep keys secret
Treat an API key like a password:- Load it from an environment variable or secret manager — never commit it to source control or ship it in client-side code.
- Scope keys narrowly: one key per integration, named so you can tell them apart.
- Rotate by creating a replacement key, switching traffic, then revoking the old one.
- If a key leaks, revoke it immediately in Settings → API keys.
401 UNAUTHORIZED with details.reason: "ORPHANED_KEY", the key
is no longer bound to an organization and cannot be used — create a new key
from Settings.
Conventions
Requests. Operations on servers arePOST with a JSON body (most accept
an empty {}). Reads — the catalog listings and eval-run polling — are
GET and take their options (cursor, limit, filters) as query
parameters.
Responses. Three envelope shapes, used consistently:
| Kind | Shape |
|---|---|
| Single resource | The resource object directly |
| Collection | { "items": [...], "nextCursor": "..." } — nextCursor omitted on the last page |
| Error | { "code": "...", "message": "...", "details": {...} } with a matching HTTP status |
nextCursor as cursor in the next request (body field on POST lists,
query parameter on GET lists). Cursors are opaque — don’t parse them.
Authoring vs running suites. POST /eval-suites creates a runnable suite
(the suite plus its test cases) synchronously and responds 201 with the
suiteId, WITHOUT running anything — author once, run later. POST /eval-runs
is the async counterpart: it validates and creates a run synchronously, then
detaches execution and responds 202 with a runId. Poll
GET /eval-runs/{runId} until status is completed, failed, or
cancelled.
IDs. Every identifier the API takes is discoverable through the API
itself: GET /projects lists your projects, GET /projects/{projectId}/servers
lists each project’s servers, and GET /projects/{projectId}/eval-suites
lists its eval suites. Start from GET /me to confirm which account a key
acts as.
Errors
Errors always use the canonical body{ code, message, details? }. The code
is stable and machine-readable; message is human-readable and may change;
details is an optional, unstructured bag.
| Code | HTTP | Meaning |
|---|---|---|
UNAUTHORIZED | 401 | Missing, invalid, revoked, or orphaned key |
OAUTH_REQUIRED | 401 | The target MCP server needs an OAuth grant — distinct from a bad key |
FORBIDDEN | 403 | Key is valid but not allowed to do this |
VALIDATION_ERROR | 400 | Malformed body or parameters |
NOT_FOUND | 404 | Unknown project, server, or resource |
FEATURE_NOT_SUPPORTED | 422 | The target server doesn’t support this MCP capability |
RATE_LIMITED | 429 | Too many requests — see Rate limits |
SERVER_UNREACHABLE | 502 | Could not connect to the target MCP server |
TIMEOUT | 504 | The target MCP server connected but didn’t respond in time |
INTERNAL_ERROR | 500 | Something failed on our side |
Rate limits
Each key gets 60 requests per minute sustained, with bursts up to 10. Exceeding it returns429 RATE_LIMITED with a Retry-After header (in
seconds):
Retry-After, add jittered exponential backoff, and expect these limits
to be tuned during the preview.
Endpoints
Each endpoint is fully documented — request and response schemas, examples, and an interactive playground — under Endpoints in the sidebar. Catalog — discover the IDs everything else takes:| Endpoint | What it does |
|---|---|
GET /me | The account behind the key |
GET /projects | Projects the key can access (optionally ?organizationId=) |
GET /projects/{projectId}/servers | The project’s saved MCP servers |
GET /projects/{projectId}/eval-suites | The project’s eval suites, with latest-run summaries |
GET, POST /projects/{projectId}/hosts | List hosts, or create one from a template or full config |
GET, PATCH, DELETE /projects/{projectId}/hosts/{hostId} | Read, rename/edit, or delete a host |
GET /chat-sessions | Chat sessions (?projectId=&status=&limit=&before=) |
/projects/{projectId}/servers/{serverId}/...):
| Endpoint | What it does |
|---|---|
POST .../validate | Connect, initialize, and capture a capability snapshot |
POST .../doctor | Full health workflow: probe → connect → initialize → capabilities |
POST .../check-oauth | Does this server require an OAuth grant? |
POST .../tools | List the server’s tools |
POST .../tools/call | Execute a tool; returns the MCP CallToolResult verbatim |
POST .../prompts | List the server’s prompts |
POST .../prompts/get | Render a prompt; returns the MCP GetPromptResult verbatim |
POST .../resources | List the server’s resources |
POST .../resources/read | Read one resource by URI |
POST .../export | One JSON snapshot of tools, resources, and prompts |
POST .../oauth/import-tokens | Store OAuth tokens you obtained yourself; later calls inject + refresh them |
/projects/{projectId}/...):
| Endpoint | What it does |
|---|---|
POST .../eval-suites | Author-only: create a suite + its test cases from name + serverIds + a default model + tests, WITHOUT running it; responds 201 + suiteId. Per-test model/provider/runs/expectedToolCalls are optional and fall back to suite defaults |
POST .../eval-runs | Create a run from a suiteId (rerun; serverIds optional — defaults to the suite’s saved servers) or suiteName + inline tests + serverIds (new suite); responds 202 + runId |
GET .../eval-runs/{runId} | Run status, result, and summary — poll until terminal |
GET .../eval-runs/{runId}/iterations | Per-iteration results: tool calls, token usage, latency (paginated) |
GET .../eval-runs/{runId}/iterations/{iterationId}/trace | Full trace: messages + expected-vs-actual analysis |
GET .../eval-suites/{suiteId}/runs | Recent runs for a suite, newest first |
/projects/{projectId}/eval-ingest/...) — how @mcpjam/sdk saves results from eval runs executed outside the platform (local dev, CI). The {projectId} segment accepts the literal default for the key org’s Default project. Most users never call these directly — set MCPJAM_API_KEY and the SDK reporter does:
| Endpoint | What it does |
|---|---|
POST .../eval-ingest/report | One-shot: create a run from finished results and finalize it |
POST .../eval-ingest/runs/start | Chunked flow: create (or idempotently reuse) a run by externalRunId |
POST .../eval-ingest/runs/iterations | Append an iteration batch to a started run |
POST .../eval-ingest/runs/finalize | Finalize a chunked run (idempotent) |
POST .../eval-ingest/artifacts/upload-url | Mint an upload URL for eval artifacts (JUnit XML, Jest/Vitest JSON) |
/projects/{projectId}/chatboxes...) — read-only:
| Endpoint | What it does |
|---|---|
GET .../chatboxes | List the project’s published chatboxes: name, access mode, attached servers, share link |
GET .../chatboxes/{chatboxId} | One chatbox’s settings: model, system prompt, tool-approval policy, resolved servers |
Run evals from the API
The shortest useful loop — create a run with one inline test, then poll:model takes an id from the hosted catalog — provider/name form, e.g.
anthropic/claude-haiku-4.5 — and runs on your organization’s credits.
Provider-native ids (e.g. claude-sonnet-4-5) are bring-your-own-key: pass
the key in modelApiKeys. A model the API can’t execute is rejected at
create time with VALIDATION_ERROR; details.hostedModels lists the valid
hosted ids for that provider.
Reruns are even shorter: { "suiteId": "..." } reruns the suite exactly as
configured, connecting the suite’s saved server selection (the 202 response
lists the resolved servers). Pass serverIds to override the selection; a
suite with no saved selection requires it (VALIDATION_ERROR with
details.reason: "NO_SAVED_SERVER_SELECTION" otherwise). Per-organization
concurrency is capped (default 2 concurrent runs); exceeding it returns
429 with details.reason: "CONCURRENT_RUN_LIMIT" — wait for an active run
to finish.
If a server answers 401 OAUTH_REQUIRED, complete the OAuth flow yourself
(the SDK’s runOAuthLogin handles interactive, headless, and
client-credentials flows) and push the result to
POST .../oauth/import-tokens once. Subsequent calls inject the stored token
and refresh it server-side.
Not in the API yet
To set expectations while in preview, these are not available over the API today (most exist in the hosted inspector UI):- Creating or revoking API keys (UI-only by design — see Authentication)
- Chat and conformance suites
- Browser-based OAuth flows initiated by the API (use
oauth/import-tokensafter completing OAuth yourself)
Versioning & stability
- The API is path-versioned (
/api/v1). When it reaches general availability, breaking changes will require a new version path. - During the preview, breaking changes to v1 may still happen; we’ll note them in the changelog.
- Additive changes — new endpoints, new optional request fields, new response fields, new error codes — are considered non-breaking and can ship at any time. Write clients that ignore unknown fields.
- Error codes are stable identifiers; error messages are not.

