MCPCloud.sh — Product and API reference

API Reference

Auth, runtime, and API-key reference for the first public API milestone.

This page covers the customer-facing machine contract that exists today: API-key lifecycle, runtime token exchange, protected runtime usage, and the adjacent OAuth runtime-grant flow.

Foundations

Start with these credentials

Personal API key

Long-lived control-plane credential. Use it to authenticate the runtime token exchange request or the API-key lifecycle routes.

Runtime token

Short-lived Bearer token issued by MCPCloud for protected `privateOrg` and `unlisted` runtime calls.

Browser session

Useful for interactive flows. Session-based exchange may infer the active organization, but scripts and CI should always send it explicitly.

API key management

Manage personal API keys over the canonical v1 HTTP surface

Use this route to list active personal keys, create a fresh key for a script or CI runner, and revoke keys that should no longer have access. The raw secret is returned only at creation time.

API key lifecycle endpointsbash
curl "$MCPFACTORY_BASE_URL/api/v1/api-keys" \
    -H "Authorization: Bearer $MCPFACTORY_API_KEY"

curl -X POST "$MCPFACTORY_BASE_URL/api/v1/api-keys" \
    -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "name": "CI runner" }'

curl -X DELETE "$MCPFACTORY_BASE_URL/api/v1/api-keys" \
    -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "apiKeyId": "<api-key-id>" }'

Exact request and response shapes

The snippets below match the current contract for listing, creating, and revoking keys on /api/v1/api-keys.

GET/api/v1/api-keys

200 OK response

{
  "apiKeys": [
    {
      "id": "<api-key-id>",
      "name": "CI runner",
      "keyPrefix": "mcf_abc123",
      "lastFour": "WXYZ",
      "createdAt": 1760000000000,
      "lastUsedAt": 1760000001000
    }
  ]
}
POST/api/v1/api-keys

Request body

{
  "name": "CI runner"
}

201 Created response

{
  "apiKeyId": "<api-key-id>",
  "apiKey": "mcf_public_secret",
  "name": "CI runner",
  "keyPrefix": "mcf_abc123",
  "lastFour": "WXYZ"
}
DELETE/api/v1/api-keys

Request body

{
  "apiKeyId": "<api-key-id>"
}

200 OK response

{
  "apiKeyId": "<api-key-id>",
  "revokedAt": 1760000002000
}

API key scopes

Keys may carry a scope allowlist (set at mint via preset or scopes). Effective authority is always the intersection with your live organization role — scopes only narrow, never widen. Keys without scopes are legacy full-authority. A request outside the grant fails with 403 insufficient_scope; org-pinned keys refused for another org fail with 403 api_key_org_mismatch. Widening a key (rotating to more scopes, clearing or changing its org pin) requires a signed-in browser session.

ScopeRoutesagentread-onlyci-deployci-build
account:read3
account:write1
orgs:read3
members:read1
projects:read5
projects:write3
servers:read30
servers:build29
servers:deploy9
servers:secrets2
servers:test7
deployments:read7
skills:read12
skills:write16
skills:invoke1
registry:read4
registry:publish3
audit:read1
billing:read2
domains:manage9
connections:manage9
keys:manage4
runtime:connect

The full preset grants every scope. runtime:connect gates the runtime token exchange; keys:manage is never in any preset except full.

Which pipeline step needs which scope: push-spec --apply, generate --store and regenerate change the server’s code and need servers:build; deploy needs servers:deploy; deploy --wait and deployments get need deployments:read; test needs servers:test. The ci-deploy preset redeploys the stored bundle only; ci-build adds servers:build for pipelines that regenerate code before deploying.

Denied requestjson
{
  "error": {
    "code": "insufficient_scope",
    "details": {
      "grantedScopes": ["skills:read"],
      "requiredScopes": ["servers:deploy"]
    },
    "docsUrl": "/docs/api#api-key-scopes",
    "message": "This API key lacks the required scope: servers:deploy."
  }
}

Connect your agent to the official server

The official MCPCloud MCP server lets any agent operate your workspace — build, deploy, observe, and publish — through the platform’s own control-plane API. The fastest path is plain OAuth: add the endpoint to any MCP client with no credentials at all — claude mcp add --transport http mcpcloud https://mcp.mcpcloud.sh/mcp — and your client opens the browser to authorize; approve once and the connection persists with silent token refresh. The same approval custodies a connection-scoped key server-side (zero-paste), so tool calls execute as you without your agent ever holding a long-lived credential. Members of the operating organization connect on the member plane; every other signed-in MCPCloud user connects as an end-user — same flow, and the credential always executes in your own workspace.

Headless / CIbash
# one command: discovers the official server, enrolls this
# profile's key as your custodied credential, prints the snippet
mcp connect-agent

mcp connect-agent --status   # your plane + connection state
mcp connect-agent --revoke   # disconnect (reconnecting is one command)

Programmatic discovery: GET /api/v1/official-server (scope account:read) returns the live endpoint, the end-user plane state, and your connection status — no access to the operating organization required.

Discovery responsejson
GET /api/v1/official-server
{
  "server": {
    "name": "MCPCloud",
    "mcpUrl": "https://mcp.mcpcloud.sh/mcp",
    "deploymentId": "…",   // POST it to /api/v1/runtime/auth/token
    "endUserAccess": "enabled"
  },
  "callerIsMember": false,
  "connection": null
}

Connection-scoped keys carry exactly these grants (org-pinned to your workspace, 90-day expiry, revocable any time — your agent only ever holds short-lived runtime tokens):

  • account:read
  • connections:manage
  • orgs:read
  • projects:read
  • projects:write
  • servers:read
  • servers:build
  • servers:deploy
  • servers:secrets
  • servers:test
  • deployments:read
  • skills:read
  • skills:write
  • skills:invoke
  • registry:read
  • registry:publish
  • audit:read
  • runtime:connect
  • billing:read

Official server lock

The official mcpcloud server carries a self-management lock. Scoped (non-full-authority) mcf_ API keys are denied every write route that targets it with 403 official_server_locked — an agent holding a scoped key cannot modify, redeploy, or delete the server it is talking through. Signed-in browser sessions and full-authority keys are unaffected, and read routes are never lock-checked. The guarded write routes:

  • PATCH /api/v1/server
  • DELETE /api/v1/server
  • POST /api/v1/server/ingest
  • POST /api/v1/server/generate
  • POST /api/v1/server/regenerate
  • POST /api/v1/server/enrich
  • POST /api/v1/server/dependencies
  • DELETE /api/v1/server/dependencies
  • POST /api/v1/server/dev-bundle
  • POST /api/v1/server/deploy
  • POST /api/v1/server/pause
  • POST /api/v1/server/resume
  • PATCH /api/v1/server/tool
  • POST /api/v1/server/tool
  • DELETE /api/v1/server/tool
  • POST /api/v1/server/tools/rename
  • PATCH /api/v1/server/tool/handler
  • DELETE /api/v1/server/tool/handler
  • PUT /api/v1/server/secret
  • PUT /api/v1/server/resource
  • PUT /api/v1/server/end-user-access
  • DELETE /api/v1/server/secret
  • POST /api/v1/deployment/rollback
Denied requestjson
{
  "error": {
    "code": "official_server_locked",
    "details": null,
    "docsUrl": "/docs/api#official-server-lock",
    "message": "This server is the locked official MCPCloud server. Scoped API keys cannot modify it — operate your own servers instead."
  }
}

Identity

Caller identity

The recommended first call from any CLI or SDK. Returns the user record, default organization, and API key provenance for the current request — never the raw key.

Identity

Caller identity

Use /api/v1/me as the first call from any CLI or SDK. It returns the canonical caller view — user record, default organization, and API key provenance — so you can render a whoami-style status before doing anything else.

Auth

bearer API key or browser session.

Response shape

user object (id, email, name), authMethod, defaultOrganizationId, and an apiKey block describing which key authenticated this request.

Token safety

the raw API key is never returned. The apiKey block is null on browser-session calls; on bearer calls it carries the durable id, the dashboard name, the keyPrefix, and the lastFour for visual confirmation.

defaultOrganizationId

a hint, not a substitute. Machine clients must still send organizationId explicitly on /api/v1/runtime/auth/token.

GET /api/v1/me
bash
curl "$MCPFACTORY_BASE_URL/api/v1/me" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Identity

User settings (GET / PATCH)

Use /api/v1/user-settings to read or update the caller's self-owned preferences — default organization and default enrichment model. The first PATCH mutation in v1, on the simplest possible reuse case (a self-owned record with no org-membership gate).

Auth

bearer API key or browser session. Caller-scoped — there is no userId parameter, and bearer auth always operates on the key owner.

GET response

full settings record including read-only onboarding state, plus createdAt and updatedAt (both null for users who have never had a settings row written).

PATCH semantics

omit a key to leave unchanged, pass null to clear, pass a value to set. An empty PATCH (no settable fields) returns 400 invalid_request.

Settable fields

defaultOrganizationId (validated against actual membership; non-members get 403 organization_access_denied) and defaultEnrichmentModelId (404 model_not_found if missing, 409 model_not_available if deprecated).

Onboarding fields are read-only. They are UI-driven; exposing them as PATCH-able would let an API key fake onboarding completion.

Idempotency

PATCH is naturally idempotent at the application level — replaying the same body produces the same final state. The cross-cutting Idempotency-Key header is silently ignored on this route in v1.

GET / PATCH /api/v1/user-settings
bash
# Read
curl "$MCPFACTORY_BASE_URL/api/v1/user-settings" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

# Patch
curl -X PATCH "$MCPFACTORY_BASE_URL/api/v1/user-settings" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "defaultOrganizationId": "<organization-id>",
    "defaultEnrichmentModelId": null
  }'

Control Plane

Workspace control-plane reads

Use the control-plane discovery slice to resolve organizations and project-scoped metadata before branching into MCP server or skill-specific automation. These routes are the narrow workspace lookup surface for scripts, CI, and internal tooling.

Discovery

Organization discovery

Call /api/v1/organizations first when a script needs to discover the organizations the current caller can access. This is the machine-friendly source of organization identifiers for later requests.

What the response gives you

Each organization entry includes the Better Auth organization id, role, slug, display name, plan, and whether it matches the saved default organization preference.

GET /api/v1/organizations
bash
curl "$MCPFACTORY_BASE_URL/api/v1/organizations" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Exact read

Single organization read

Use /api/v1/organization when you already know the organization id. Returns the canonical record plus memberCount and absolute timestamps from the workspace mirror table.

Required params

send organizationId.

Why this exists

scripts can revalidate a stored org id without paginating /api/v1/organizations, and surface live member count without a second join.

memberCount paginates the Better Auth membership store and reflects the live count at request time. createdAt and updatedAt may be null for organizations that exist in Better Auth but have not yet been mirrored into the workspace organizations table.

GET /api/v1/organization
bash
curl "$MCPFACTORY_BASE_URL/api/v1/organization?organizationId=<organization-id>" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Artifact read

Organization members

Use /api/v1/organization/members to enumerate the membership roster, joined against access-state and self-serve policy plus the Better Auth user record (display name, email, image).

Required params

send organizationId.

Optional params

accessState=active|disabled|all (default active), limit (1..100, default 50), and cursor (the previous response's nextCursor).

accessState and the capability flags fall back to schema defaults (active, canSelfServeDeploy true, canSelfServePublish true) for members that exist in Better Auth but have no local policy row yet — that is the expected state for newly invited users until the first policy mutation.

Pagination is provided by the Better Auth member model. The accessState filter is applied after pagination, so a small page may return fewer rows than the page would otherwise allow.

GET /api/v1/organization/members
bash
curl "$MCPFACTORY_BASE_URL/api/v1/organization/members?organizationId=<organization-id>&accessState=active&limit=50" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Artifact read

Organization usage

Use /api/v1/organization/usage to read the workspace's operational request-count rollup across every deployment in the org. Visibility is plain membership — any role can read this.

Required params

send organizationId. Optional windowDays defaults to 30 (max 90).

Response

a totals object plus a recentDaily array with per-UTC-day rollups of requestCount, allowedCount, rateLimitedCount, unauthorizedCount, and failedCount.

Cost-derivable signals (totalCpuTimeMs, duration samples) live on /api/v1/billing/usage and are intentionally not exposed here. Two endpoints, one underlying table, two visibility levels

viewer-readable telemetry vs. admin-readable cost building blocks.

Why this exists

scripts can answer "did our usage hit the rate limit?" without the admin/owner gate that protects the cost rollup.

GET /api/v1/organization/usage
bash
curl "$MCPFACTORY_BASE_URL/api/v1/organization/usage?organizationId=<organization-id>&windowDays=30" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Discovery

Project discovery

Once you know the organization id, call /api/v1/projects to discover the project ids that anchor downstream server, deployment, source, and skill automation.

Required params

send organizationId.

Optional params

pass limit for a bounded result size up to 100.

Why this exists

scripts can now resolve canonical project ids before branching into project-scoped resources or exact project reads.

GET /api/v1/projects
bash
curl "$MCPFACTORY_BASE_URL/api/v1/projects?organizationId=<organization-id>&limit=25" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Exact read

Single project read

Call /api/v1/project when your automation already knows a project id and needs to verify the canonical project metadata without relying on app-only project screens.

Required params

send both organizationId and projectId.

Response shape

the route returns one project object, not a list, so scripts can branch cleanly on 404 project_not_found.

Why this exists

persisted project ids in CI and internal tooling can be revalidated before requesting project-scoped resources.

GET /api/v1/project
bash
curl "$MCPFACTORY_BASE_URL/api/v1/project?organizationId=<organization-id>&projectId=<project-id>" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Artifact read

Project API sources

Call /api/v1/project/api-sources when you already know the project id and need the imported API specs, ingestion state, and source metadata that generated servers and tools trace back to.

Required params

send both organizationId and projectId.

Optional params

pass limit for a bounded newest-first result size up to 100.

Why this exists

automation can correlate generated assets back to the raw source material without depending on authenticated project views.

GET /api/v1/project/api-sources
bash
curl "$MCPFACTORY_BASE_URL/api/v1/project/api-sources?organizationId=<organization-id>&projectId=<project-id>&limit=25" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Exact read

Single project API source read

Call /api/v1/project/api-source when automation already has an apiSourceId and needs the full source record — drift state, warnings, and download paths for the raw spec and parsed schema artifacts — without paginating /api/v1/project/api-sources.

Required params

send organizationId, projectId, and apiSourceId.

Response shape

returns the resolved apiSource, the parent project summary, and the organization triplet. parsedSchemaDownloadPath and rawSpecDownloadPath point at /api/v1/artifacts/download when the underlying artifact has been generated, and are null otherwise.

Why this exists

list discovery gives you ids; this exact-read lets scripts inspect drift status, last drift check, and the original spec origin URL without re-scanning the list.

GET /api/v1/project/api-source
bash
curl "$MCPFACTORY_BASE_URL/api/v1/project/api-source?organizationId=<organization-id>&projectId=<project-id>&apiSourceId=<api-source-id>" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Artifact read

Project tools

Call /api/v1/project/tools when you need the generated tool catalog for one project, including tool names, labels, schema summaries, and source-backed metadata.

Required params

send both organizationId and projectId.

Optional params

pass limit for a bounded result size up to 100.

Response shape

each tool is the project-scoped draft view used to inspect generated capability before it is attached to a server or skill.

GET /api/v1/project/tools
bash
curl "$MCPFACTORY_BASE_URL/api/v1/project/tools?organizationId=<organization-id>&projectId=<project-id>&limit=25" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

MCP APIs

MCP server reads

Once automation has project context, these MCP server routes cover server lookup, deployment inspection, operational health, and sandbox validation without relying on the authenticated workspace.

Discovery

Server discovery

Call /api/v1/servers once you know the organization id and need the current server ids, project context, status, and latest deployment pointer without traversing authenticated app-only server views.

Required params

send organizationId.

Optional params

pass limit for a bounded result size up to 100.

Why this exists

automation can enumerate stable server ids before branching into exact server reads, deployment inspection, or runtime-token exchange preparation.

GET /api/v1/servers
bash
curl "$MCPFACTORY_BASE_URL/api/v1/servers?organizationId=<organization-id>&limit=25" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Exact read

Single server read

Call /api/v1/server when your automation already knows a server id and needs the canonical server status, version, latest deployment pointer, and project context without traversing app-only views.

Required params

send both organizationId and serverId.

Response shape

the route returns one server object with its latest deployment reference, so scripts can revalidate a stored server id and then jump directly to the related deployment when needed.

Why this exists

server discovery gives you server ids, and follow-up automation often needs to confirm the server’s current status before deciding whether to exchange a runtime token or inspect a deployment.

GET /api/v1/server
bash
curl "$MCPFACTORY_BASE_URL/api/v1/server?organizationId=<organization-id>&serverId=<server-id>" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Artifact read

Server versions

Call /api/v1/server/versions when you already know the server id and need a bounded, newest-first version history without relying on the authenticated workspace.

Required params

send both organizationId and serverId.

Optional params

pass limit for a bounded result size up to 100.

Response shape

each version entry is a summary with id, semver, changelog, and createdAt so exact-version reads can remain a follow-on surface instead of bloating list responses.

GET /api/v1/server/versions
bash
curl "$MCPFACTORY_BASE_URL/api/v1/server/versions?organizationId=<organization-id>&serverId=<server-id>&limit=25" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Exact read

Single server version read

Call /api/v1/server/version when automation already has a server version id and needs the canonical changelog, semver, and snapshot artifact pointer without paginating the version list.

Required params

send both organizationId and serverVersionId.

Response shape

returns the resolved version, its parent server summary, and the organization triplet. The schemaSnapshotDownloadPath is populated when the version has a stored schema snapshot and points at /api/v1/artifacts/download.

Why this exists

the versions list gives you ids; this exact-read lets you re-fetch one historical version (changelog, snapshot pointer) without re-scanning history.

GET /api/v1/server/version
bash
curl "$MCPFACTORY_BASE_URL/api/v1/server/version?organizationId=<organization-id>&serverVersionId=<server-version-id>" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Artifact read

Server OAuth connections

Call /api/v1/server/oauth-connections to list the caller’s OAuth connections for one server. Returns 404 when the server does not exist in the organization, instead of an empty list.

Required params

send both organizationId and serverId.

Optional params

pass status=connected, expired, error, revoked, or all (default connected) plus limit for a bounded result size up to 100.

Response shape

connection rows mirror /api/v1/oauth/connections, plus a resolved server summary so callers can render context without a second lookup. Token material is never returned.

GET /api/v1/server/oauth-connections
bash
curl "$MCPFACTORY_BASE_URL/api/v1/server/oauth-connections?organizationId=<organization-id>&serverId=<server-id>&status=connected&limit=25" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Artifact read

Server deployments

Call /api/v1/server/deployments when you need the deployment history for one server, including active, failed, and undeployed releases, without listing every deployment in the organization.

Required params

send both organizationId and serverId.

Optional params

pass status=all, queued, building, deploying, active, failed, or undeployed plus limit for a bounded result size up to 100.

Why this exists

once automation narrows to one server, release-state inspection should not require a broader organization deployment scan.

GET /api/v1/server/deployments
bash
curl "$MCPFACTORY_BASE_URL/api/v1/server/deployments?organizationId=<organization-id>&serverId=<server-id>&status=all&limit=25" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Discovery

Deployment discovery

Once you know the organization id, call /api/v1/deployments with an explicit organizationId query parameter to discover deployment ids, server names, project names, and runtime URLs.

Default filter

only active deployments are returned.

Optional params

pass status=all to include non-active deployments and limit for a bounded result size up to 100.

Why this exists

scripts no longer need to copy deployment ids out of the dashboard before they can exchange a runtime token.

GET /api/v1/deployments
bash
curl "$MCPFACTORY_BASE_URL/api/v1/deployments?organizationId=<organization-id>&status=active" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Exact read

Single deployment read

Call /api/v1/deployment when your automation already knows a deployment id and needs to verify the current runtime URL, version, or access mode without listing the full organization deployment set again.

Required params

send both organizationId and deploymentId.

Response shape

the route returns one deployment object, not a list, so scripts can branch cleanly on 404 deployment_not_found.

Why this exists

persisted deployment ids in CI and internal tooling can be revalidated without fetching up to 100 deployments first.

GET /api/v1/deployment
bash
curl "$MCPFACTORY_BASE_URL/api/v1/deployment?organizationId=<organization-id>&deploymentId=<deployment-id>" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Operational read

Deployment events

Call /api/v1/deployment/events when you need the deployment event stream for one known deployment, including build, deploy, and health-check messages, without opening the dashboard timeline.

Required params

send both organizationId and deploymentId.

Optional params

pass limit for a bounded newest-first event stream up to 100 entries.

Why this exists

operational debugging should not require copying event messages out of the authenticated UI.

GET /api/v1/deployment/events
bash
curl "$MCPFACTORY_BASE_URL/api/v1/deployment/events?organizationId=<organization-id>&deploymentId=<deployment-id>&limit=50" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Operational read

Deployment health

Call /api/v1/deployment/health when you need the latest health state for one deployment without inferring it from the broader deployment list.

Required params

send both organizationId and deploymentId.

Response shape

the health object isolates the last known check outcome, status code, and errors while the deployment object carries the stable routing metadata.

Why this exists

automation can distinguish routing metadata from current runtime health without scraping timeline events.

GET /api/v1/deployment/health
bash
curl "$MCPFACTORY_BASE_URL/api/v1/deployment/health?organizationId=<organization-id>&deploymentId=<deployment-id>" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Operational read

Deployment runtime-auth

Call /api/v1/deployment/runtime-auth to introspect a deployment's protected-runtime posture before deciding whether to call POST /api/v1/runtime/auth/token. The endpoint never returns token material, signing keys, or the raw edge-policy JSON — only the introspection that lets a client reason about the contract.

Required params

send both organizationId and deploymentId.

Response shape

the runtimeAuth block carries accessMode, requiresRuntimeToken (true when accessMode is not public), tokenAudience, exchangePath, runtimeKind, exposureTarget, and a strict subset of the active edgePolicy row (rate-limit ceiling, allowed audience, allowed org).

edgePolicy is null for deployments that are queued, building, or failed before the edge policy was first written. For active deployments it is always populated.

Why this exists

scripts and CLI commands can decide whether to mint a runtime token without first attempting an unauthenticated request and reading the 401, and without parsing the deployment summary's accessMode in isolation.

GET /api/v1/deployment/runtime-auth
bash
curl "$MCPFACTORY_BASE_URL/api/v1/deployment/runtime-auth?organizationId=<organization-id>&deploymentId=<deployment-id>" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Validation read

Server test runs

Call /api/v1/server/test-runs when you need recent validation activity for one server, including pass or fail state, scenario linkage, and suite context, without opening the sandbox UI.

Required params

send both organizationId and serverId.

Optional params

pass status=all, queued, running, passed, or failed plus limit for a bounded newest-first result size up to 100.

Why this exists

server validation parity with skill validation means automation can inspect recent checks before promoting a release.

GET /api/v1/server/test-runs
bash
curl "$MCPFACTORY_BASE_URL/api/v1/server/test-runs?organizationId=<organization-id>&serverId=<server-id>&status=failed&limit=25" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Exact read

Single server test run

Call /api/v1/server/test-run when your automation already stores a sandbox run id and needs the exact trace, input, output, and baseline comparison for one execution.

Required params

send both organizationId and testRunId.

Response shape

the route returns one detailed run object with baseline comparison metadata instead of forcing scripts to infer diffs from list responses.

Why this exists

failed validation should be diagnosable without scraping the authenticated sandbox detail view.

GET /api/v1/server/test-run
bash
curl "$MCPFACTORY_BASE_URL/api/v1/server/test-run?organizationId=<organization-id>&testRunId=<test-run-id>" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Validation read

Server test scenarios

Call /api/v1/server/test-scenarios when you need the canonical sandbox scenarios for one server, including baseline and last-run summaries.

Required params

send both organizationId and serverId.

Optional params

pass status=active or all plus limit for a bounded newest-first result size up to 100.

Why this exists

release automation can enumerate the scenario set it expects to validate before running or re-running suites.

GET /api/v1/server/test-scenarios
bash
curl "$MCPFACTORY_BASE_URL/api/v1/server/test-scenarios?organizationId=<organization-id>&serverId=<server-id>&status=active" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Exact read

Single server test scenario

Call /api/v1/server/test-scenario when you already know a sandbox scenario id and need its exact definition, baseline run, and latest execution summary.

Required params

send both organizationId and scenarioId.

Response shape

the route returns one scenario object with its baseline and most recent run so scripts can validate stale ids directly.

Why this exists

rollout automation often stores scenario ids out-of-band and needs a canonical refresh step before acting on failures.

GET /api/v1/server/test-scenario
bash
curl "$MCPFACTORY_BASE_URL/api/v1/server/test-scenario?organizationId=<organization-id>&scenarioId=<scenario-id>" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Validation read

Server test suites

Call /api/v1/server/test-suites when you need the aggregate validation suite history for one server, including preview, live-endpoint, and mock-binding profiles.

Required params

send both organizationId and serverId.

Optional params

pass profile=all, liveEndpoint, previewBindings, or mockBindings plus limit for a bounded newest-first result size up to 100.

Why this exists

operators can separate live verification from preview smoke tests without walking every underlying run record.

GET /api/v1/server/test-suites
bash
curl "$MCPFACTORY_BASE_URL/api/v1/server/test-suites?organizationId=<organization-id>&serverId=<server-id>&profile=previewBindings&limit=25" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Exact read

Single server test suite

Call /api/v1/server/test-suite when you already know a suite id and need the aggregate suite record together with its underlying run summaries.

Required params

send both organizationId and suiteId.

Response shape

the exact suite read returns the suite summary plus the runs it contains, so scripts can branch on the aggregate and drill into the individual failures immediately.

Why this exists

suite ids are often the stable handle emitted by validation automation, not the individual run ids.

GET /api/v1/server/test-suite
bash
curl "$MCPFACTORY_BASE_URL/api/v1/server/test-suite?organizationId=<organization-id>&suiteId=<suite-id>" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Spec Studio

No spec? Generate one.

Spec Studio turns a connected GitHub repository into a reviewed OpenAPI spec: routes inventoried by framework adapters, schemas inferred per endpoint with confidence annotations, optionally verified against your live API — then one click to become an MCP server. The spec is yours either way.

1

Scan

routes inventoried · free

2

Select

pick the routes that matter

3

Generate

per-route inference, streamed live

4

Review

triage, verify against the live API

5

Create server

or just keep the spec

The full flow lives in the dashboard at /spec-studio — the API below is the same pipeline, headless.
app.mcpcloud.sh/spec-studio/runs/… · review

Routes

▾ api/v1/

GET servers

POST ingest

PATCH tool

118 generated3 low

PATCH /api/v1/server/tool ⚠ review

⚠ Low confidence — request body inferred from a dynamic merge

Updates mutable metadata on a single tool — display name, risk class, and tags.

⟳ Re-infer (Sonnet)✎ Edit description✓ Mark reviewed
The review screen: routes grouped in a tree with low-confidence roll-ups; every flagged route carries its reason and one-click fixes.

Spec Studio

Start a spec-generation run

Point Spec Studio at a connected GitHub repository. The scan inventories every route (framework auto-detected: Convex, FastAPI, Express, NestJS — or an LLM fallback), then per-route inference produces an OpenAPI 3.0 document with confidence annotations. The call returns immediately; the pipeline runs server-side.

Metered per generated route (~1 credit / route). The same credit-estimate gate as the dashboard applies

a run the balance cannot cover never starts.

Requires admin access to the organization and the MCPCloud GitHub App installed on the repository. Scanning itself is free.

Optional selectedKeys ('METHOD /path' strings) narrows generation to specific routes; absent, the scan's default selection (non-internal routes) generates.

The generated spec is yours — download it, keep it, or hand it straight to server creation.

POST /api/v1/spec-studio/run
bash
curl -X POST "$MCPFACTORY_BASE_URL/api/v1/spec-studio/run" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "organizationId": "org_123",
    "owner": "acme",
    "repo": "billing-api",
    "branch": "main"
  }'

Spec Studio

Poll a run / fetch the spec

Status walks scanning → generating → review_ready. Once review_ready, the response carries the full OpenAPI document plus triage counts — how many routes generated, failed, or want a human look (low confidence).

spec is the OpenAPI 3.0 document as a JSON string; every operation carries x-mcpcloud-confidence (and a reason when the model was unsure).

headCommit records the branch head the spec reflects; the version folds it in as SemVer build metadata (1.0.0+<sha>).

Review in the dashboard for the full triage loop — re-infer with a stronger model, hand-edit descriptions, live-probe verification, and one-click server creation.

GET /api/v1/spec-studio/run
bash
curl "$MCPFACTORY_BASE_URL/api/v1/spec-studio/run?organizationId=org_123&runId=ssr_456" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Agent path: the official mcpcloud MCP server exposes the same capability as tools — searchTools("generate api spec") surfaces generateApiSpec and getApiSpecRun, so an agent can run the whole flow conversationally.

Skill APIs

Skill artifact reads

Once you have a skill id, the public API lets you drill into exact skill metadata, immutable versions, installation footprint, and sandbox execution history without opening Skill Studio.

Skill list

Skill discovery

Use the skills list route when your automation needs the stable skill ids for an organization without opening the authenticated app shell.

Default filter

only published skills are returned.

Optional params

pass status=all to include draft and archived skills, plus limit for a bounded list up to 100.

Why this exists

release pipelines and internal catalog sync jobs often need exact skill ids before they can inspect versions, installs, or sandbox history.

GET /api/v1/skills
bash
curl "$MCPFACTORY_BASE_URL/api/v1/skills?organizationId=<organization-id>&status=published" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Exact read

Single skill read

Use the single skill route when you already know the skill id and want the canonical source metadata, dependencies, guardrails, and authored markdown.

Required params

send both organizationId and skillId.

Response shape

one skill object with current authored content, not a reduced summary row.

Why this exists

automation can revalidate a stored skill id before branching into version, install, or test-run reads.

GET /api/v1/skill
bash
curl "$MCPFACTORY_BASE_URL/api/v1/skill?organizationId=<organization-id>&skillId=<skill-id>" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Version history

Skill versions

Use the versions route to inspect the immutable publish history for a skill without pulling app-only Skill Studio state.

Required params

send organizationId and skillId. The default limit is 50 and the maximum is 100.

Response shape

each entry is a version summary with semver, counts, and publish-time metadata.

Why this exists

release automation and auditing often need the durable version history immediately after a publish flow completes.

GET /api/v1/skill/versions
bash
curl "$MCPFACTORY_BASE_URL/api/v1/skill/versions?organizationId=<organization-id>&skillId=<skill-id>&limit=10" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Exact read

Single skill version

Use the single-version route when you already have a skillVersionId and want the trigger phrases, dependency count, workflow summary, and a download path for the snapshot artifact when one exists.

Required params

send organizationId and skillVersionId.

Snapshot path

snapshotPackageDownloadPath is null when the version was published without a snapshot artifact; otherwise it points at /api/v1/artifacts/download with its own artifactId query parameter.

Why this exists

rollout automation can verify a stored version pointer and pull its bundle through one transport without listing the whole version history first.

GET /api/v1/skill/version
bash
curl "$MCPFACTORY_BASE_URL/api/v1/skill/version?organizationId=<organization-id>&skillVersionId=<skill-version-id>" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Install state

Skill installations

Use the installations route to read organization-scoped install state for a skill, including installs created against either the draft skill id or its published registry artifact alias.

Default filter

only active installations are returned.

Optional params

pass status=all to include disabled or removed installs, plus limit up to 100.

Why this exists

operators can verify rollout footprint before changing dependencies or publishing another version.

GET /api/v1/skill/installations
bash
curl "$MCPFACTORY_BASE_URL/api/v1/skill/installations?organizationId=<organization-id>&skillId=<skill-id>&status=active" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Validation

Skill test runs

Use the test-runs route to list recent sandbox executions for a skill, including queued or running runs when your automation needs to observe in-flight validation.

Default filter

all test-run statuses are included. You can narrow the list with status=queued, running, passed, or failed.

Response shape

list entries are lightweight summaries that indicate whether trace, assertions, and output are available.

Why this exists

CI and review tooling can quickly inspect recent validation outcomes without fetching the full run body every time.

GET /api/v1/skill/test-runs
bash
curl "$MCPFACTORY_BASE_URL/api/v1/skill/test-runs?organizationId=<organization-id>&skillId=<skill-id>&status=passed&limit=20" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Validation

Single skill test run

Use the exact test-run route when you need the detailed sandbox payload, assertions, summary, and execution trace for one known run id.

Required params

send organizationId and testRunId.

Response shape

one detailed testRun object plus the parent skill summary for quick correlation.

Why this exists

debugging, approvals, and regression tooling usually pivot from a stored run id to the full trace after a failure or suspicious output.

GET /api/v1/skill/test-run
bash
curl "$MCPFACTORY_BASE_URL/api/v1/skill/test-run?organizationId=<organization-id>&testRunId=<test-run-id>" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Validation

Skill test scenarios

Use the scenarios list to enumerate the durable sandbox scenarios for a skill, including their fixture mode and whether they were authored manually or generated.

Default filter

only active scenarios are returned. Pass status=all to include archived scenarios.

Response shape

list entries omit the prompt and assertion bodies. Use the exact scenario route below when you need the full payload.

Why this exists

review tooling and regression jobs need a stable scenario index before pivoting into one scenario for execution or comparison.

GET /api/v1/skill/test-scenarios
bash
curl "$MCPFACTORY_BASE_URL/api/v1/skill/test-scenarios?organizationId=<organization-id>&skillId=<skill-id>&status=active" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Exact read

Single skill test scenario

Use the exact scenario route when you have a scenarioId and want the prompt, assertion JSON, and fixture mode for one stored scenario.

Required params

send organizationId and scenarioId.

Response shape

one detailed testScenario object plus the parent skill summary for quick correlation.

Why this exists

rerun and replay tooling typically pivots from a known scenarioId to the full payload before kicking off a fresh sandbox run.

GET /api/v1/skill/test-scenario
bash
curl "$MCPFACTORY_BASE_URL/api/v1/skill/test-scenario?organizationId=<organization-id>&scenarioId=<scenario-id>" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Mutations

Mutations

The full v1 write surface — sandbox runs, server lifecycle (deploy, pause, ingest, generate, enrich), top-level resource create + PATCH, skill lifecycle (archive, install, uninstall), installation lifecycle, and OAuth revocation. Every mutation follows the same cross-cutting contract: bearer-or-session auth with explicit organizationId in the body, structured error envelope, and X-Request-Id response headers.

  • 201 Created for create POSTs (new top-level resource); 200 OK for state-transition POSTs (existing row, state flipped) and PATCH updates. Regen-style mutations like /api/v1/server/generate also return 200.
  • Idempotency: every POST and DELETE honors the Idempotency-Key header — replays with the same key + body return the cached response byte-identically (24-hour TTL); replays with a different body return 409 idempotency_key_conflict. PATCH is naturally idempotent and silently ignores the header.
  • Today's mutations run synchronously. Future variants may run asynchronously and return immediately; clients should always pivot from the returned id into the read endpoint for status.
  • Role floors: read-only discovery requires membership; sandbox runs and skill install/uninstall require member; everything else (creates, PATCH, deploy, pause, ingest, generate, enrich, archive) requires admin or owner. Installation and OAuth-connection state transitions additionally require ownership of the addressed row.

Mutation

Create a server sandbox run

Use POST /api/v1/server/test-runs to execute a stored sandbox scenario — or rerun a prior run's inputs — against the server's latest deployment. The first public write endpoint and the template every later mutation reuses.

Auth

bearer API key or browser session. Required role: member or higher in the target organization.

Body

pass exactly one of scenarioId or rerunFromTestRunId together with organizationId, projectId, and serverId.

Response

201 Created with the new testRun row. The id is the durable handle — pivot into GET /api/v1/server/test-run for assertions, execution trace, and result artifact.

Synchronous in v1

the response carries the final passed/failed status. Code against id + status, not against the assumption that the work is complete on response.

Retries

not idempotency-keyed in v1. On a 502 sandbox_run_failed, call GET /api/v1/server/test-runs and verify whether a row was written before retrying.

Cost

sandbox runs invoke a real tool call. Future versions may rate-limit by (userId, serverId); design for 429 today.

POST /api/v1/server/test-runs
bash
# Scenario mode
curl -X POST "$MCPFACTORY_BASE_URL/api/v1/server/test-runs" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "organizationId": "<organization-id>",
    "projectId": "<project-id>",
    "serverId": "<server-id>",
    "scenarioId": "<scenario-id>"
  }'

# Rerun mode
curl -X POST "$MCPFACTORY_BASE_URL/api/v1/server/test-runs" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "organizationId": "<organization-id>",
    "projectId": "<project-id>",
    "serverId": "<server-id>",
    "rerunFromTestRunId": "<test-run-id>"
  }'

Mutation

Create a skill sandbox run

Use POST /api/v1/skill/test-runs to execute a stored skill scenario — or rerun a prior run's inputs — through the deterministic skill composition sandbox. The second public write and the first reuse of the mutation contract.

Auth

bearer API key or browser session. Required role: member or higher in the target organization.

Body

pass exactly one of scenarioId or rerunFromTestRunId together with organizationId, projectId, and skillId.

Response

201 Created with the new testRun row. Pivot into GET /api/v1/skill/test-run for assertions, execution trace, mock fixtures, and the result artifact.

No deployment dependency

the skill sandbox is a deterministic in-process simulation. There is no endpointUrl on the response and no deployment_unavailable error — those are server-endpoint concepts.

Reruns inherit scenarioId, scenarioName, fixtureMode, and the prompt from the source run, then re-execute against the current skill definition.

Retries

not idempotency-keyed in v1. On a 502 sandbox_run_failed, call GET /api/v1/skill/test-runs and verify whether a row was written before retrying.

POST /api/v1/skill/test-runs
bash
# Scenario mode
curl -X POST "$MCPFACTORY_BASE_URL/api/v1/skill/test-runs" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "organizationId": "<organization-id>",
    "projectId": "<project-id>",
    "skillId": "<skill-id>",
    "scenarioId": "<scenario-id>"
  }'

# Rerun mode
curl -X POST "$MCPFACTORY_BASE_URL/api/v1/skill/test-runs" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "organizationId": "<organization-id>",
    "projectId": "<project-id>",
    "skillId": "<skill-id>",
    "rerunFromTestRunId": "<test-run-id>"
  }'

Mutation

Pause a server

Use POST /api/v1/server/pause to transition a server's latest active deployment to paused and resync the edge policy so traffic stops. The deployed worker is not torn down — pause is reversible by redeploying or via the in-product resume flow. The first incident-response write in v1.

Auth

bearer API key or browser session. Required role: admin or owner. Members and viewers receive 403 insufficient_role.

Body

pass organizationId, projectId, and serverId. The route operates on mcpServers.latestDeploymentId — there is no deploymentId field because only the latest deployment is serving traffic.

Idempotency

honors the Idempotency-Key header per the cross-cutting mutation contract. Safe to retry on 502.

Response shape

returns the resolved deployment object with previousStatus, current status (always "paused"), pausedAt, accessMode, runtimeKind, target, and version, so clients can render the transition without a follow-up GET /api/v1/deployment.

Already-paused

returns 409 deployment_already_paused rather than 200 so retries are observable. Clients with a make-paused semantic should treat the 409 as success.

Token revocation

pause does NOT revoke OAuth runtime grants or invalidate already-issued runtime tokens. Tokens issued before pause continue to verify until natural expiry; the edge-policy resync is what stops new requests at the worker boundary. If your incident requires immediate token revocation, use the in-product undeploy flow.

POST /api/v1/server/pause
bash
curl -X POST "$MCPFACTORY_BASE_URL/api/v1/server/pause" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "organizationId": "<organization-id>",
    "projectId": "<project-id>",
    "serverId": "<server-id>"
  }'

Mutation

Deploy a server

Use POST /api/v1/server/deploy to push the server's generated bundle to Cloudflare Workers. The highest-leverage v1 lifecycle write — pairing this with `mcp dev --spec` closes the spec-watch + deploy loop.

Auth

bearer API key or browser session. Required role: admin (deploying changes production runtime state).

Body

organizationId, projectId, serverId, target (workersDev | managedDomain), accessMode (public | protected, default public), and runtimeConfig (the binding configuration — all seven keys accepted; absent keys default to null).

Idempotency

honors the Idempotency-Key header. A successful response replaces the active worker for that server, so use the header if you cannot tolerate a double-deploy on a network blip.

runtimeConfig is required and authoritative. The control plane does not silently inherit values from the prior deployment; the request body is the full source of truth.

Release gates

409 deployment_failed with error.details.reason set to protectedRuntimeBundleOutdated, runtimeCodePreviewRequired, or generatedCodePolicyViolation. Re-deploying after fixing the gate is safe.

Mid-flight failures

502 deployment_failed. Retry with the same Idempotency-Key once the upstream issue resolves.

POST /api/v1/server/deploy
bash
curl -X POST "$MCPFACTORY_BASE_URL/api/v1/server/deploy" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "organizationId": "<organization-id>",
    "projectId": "<project-id>",
    "serverId": "<server-id>",
    "target": "workersDev",
    "accessMode": "public",
    "runtimeConfig": {
      "upstreamBaseUrl": "https://api.example.com",
      "upstreamApiKey": null,
      "upstreamApiKeyHeader": null,
      "upstreamApiKeyPrefix": null,
      "upstreamCustomHeaders": null,
      "additionalPlainTextBindings": null,
      "additionalSecretBindings": null
    }
  }'

Mutation

Import a spec (create a server)

Use POST /api/v1/server/ingest to import an OpenAPI / GraphQL spec (or fetch from a URL) into a project, parse it, persist the spec snapshot, and create a new MCP server with the extracted tool surface. The public mutation `mcp init --from-spec` composes against. Project-scoped and create-shaped: each call produces a new apiSources row plus a new mcpServers row.

Auth

bearer API key or browser session. Required role: admin or owner.

Body

organizationId, projectId, sourceType (openapi | graphql | url), and exactly one of rawSpec (for openapi/graphql) or sourceUrl (for url). Optional: specOriginUrl, specDriftCheckEnabled.

Server name and description are derived from the spec (info.title, info.description). To rename after import, call PATCH /api/v1/server.

Idempotency

replaying the same key + body returns the cached 201 byte-identically with the same serverId/apiSourceId. A different body with the same key returns 409 idempotency_key_conflict.

Failed imports are durable. On 422 spec_invalid, the apiSource row stays with status

"failed" plus a parsedSchemaArtifactId pointing at the captured error envelope — inspect via GET /api/v1/project/api-source.

No deployment side effect. Importing creates a draft server with no deployment. To bring it online, follow with POST /api/v1/server/deploy.

Entitlement gates

URL imports require the urlIngestion capability; GraphQL imports require graphqlIngestion. OpenAPI text imports have no entitlement gate at v1.

Distinct from POST /api/v1/server/dev-bundle, which is the legacy preview-only path (member-level, in-memory regen) used by `mcp dev` and `mcp servers push-spec`. Ingest persists; dev-bundle does not.

POST /api/v1/server/ingest
bash
curl -X POST "$MCPFACTORY_BASE_URL/api/v1/server/ingest" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "organizationId": "<organization-id>",
    "projectId": "<project-id>",
    "sourceType": "openapi",
    "rawSpec": "openapi: 3.1.0\n..."
  }'

Mutation

Regenerate the server bundle

Use POST /api/v1/server/generate to render the source bundle that codegen would produce against the server's current tool surface. Pure compute — no spec parse, no LLM call, no persistence. Useful for inspecting 'what code would deploy' before invoking POST /api/v1/server/deploy.

Auth

bearer API key or browser session. Required role: admin or owner.

Body

organizationId, projectId, serverId. Unknown top-level keys return 400 invalid_request with error.details.field set to the offending key.

Response

200 OK with the resolved server summary plus the regenerated bundle (schemaVersion, generatedAt, entryFile, server, files[]).

Deterministic

replaying with the same body returns the same bytes. Idempotency caches the response across the 24-hour window — re-key when you want a fresh render.

No persistence. The bundle is recomputed each call from the live tools table; no artifact is stored. This matches the deploy flow which re-runs codegen at deploy time.

No spec re-parse. Reflects current persisted tools, not current spec text. Re-import via POST /api/v1/server/ingest to refresh tools from a spec.

POST /api/v1/server/generate
bash
curl -X POST "$MCPFACTORY_BASE_URL/api/v1/server/generate" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "organizationId": "<organization-id>",
    "projectId": "<project-id>",
    "serverId": "<server-id>"
  }'

Mutation

Compose another server into this one

Use POST /api/v1/server/dependencies to make one deployed server serve another's tools. The parent advertises each child's tools under a prefix in tools/list, and a prefixed tools/call is routed to the child. GET lists a server's dependencies; DELETE removes one.

Auth

bearer API key or browser session. Required role: admin or owner. Scope: servers:build to bind or remove, servers:read to list.

Body

organizationId, serverId (the parent), childServerId, optional toolPrefix, includedTools and enabled.

No regeneration, no redeploy. The merge happens at the edge, so the parent bundle never learns composition exists — binding re-pushes the parent policy and takes effect within seconds. A child whose tools change is picked up on the next tools/list.

The child's own policy still applies. A composed call is an ordinary dispatch call to the child, so its pause state, rate limits, per-tool access rules, credit and spend-cap gates all evaluate exactly as they would for a direct call — and both servers get a metering row.

Bind-time rules, each refused with 409 and a reason in error.details.reason

same organization only, one level deep (a server that composes cannot itself be composed), no prefix that collides with a sibling or shadows one of the parent's own tool names, and the official MCPCloud server at neither end.

A child that is paused or undeployed is reported in the merged tools/list as unavailable with a reason, rather than silently producing a shorter list.

POST /api/v1/server/dependencies
bash
curl -X POST "$MCPFACTORY_BASE_URL/api/v1/server/dependencies" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "organizationId": "<organization-id>",
    "serverId": "<parent-server-id>",
    "childServerId": "<child-server-id>",
    "toolPrefix": "billing_"
  }'

Mutation

Re-enrich a tool, or a whole server

Use POST /api/v1/server/enrich to re-run AI enrichment — generating richer descriptions, semantic tags, related-tool hints, recovery guidance, and usage examples that downstream agents consume. Persists the enrichment payload to the tool's enrichmentData field.

Auth

bearer API key or browser session. Required role: admin or owner.

Body

organizationId, projectId, serverId, optional modelId, and either toolId (one tool) or toolIds (a subset). Sending both is rejected.

Two modes of one verb. With toolId the call is synchronous and returns the result. Without it, every tool on the server is enriched as one background job

the response is 202 with a jobId, and GET /api/v1/server/enrich?jobId= reports progress plus a per-tool result list. A single tool failing does not fail the job.

The job runs tools sequentially. Enrichment passes a per-call tier budget check and draws on the credit ledger, and concurrent calls would each read that state before any of them wrote to it — so a parallel run could walk past a spend cap a sequential one stops at.

Cached payloads are free. When a prior enrichment with the exact same (prompt, input) hash is found in the cross-org cache, it is applied without an LLM call. cached

true in the response signals this; no credits are deducted.

Cost guard fires up-front. Before any LLM call, the shared core asserts the projected cost falls within the org tier per-call budget. Misconfigured model + token combinations are rejected with 502 sandbox_run_failed.

Same shared core powers the dashboard `enrichTool` action, so model preferences, the cross-org payload cache, the tier-driven cost guard, the credit gate, and intelligence-run tracking all behave identically.

No deployment side effect. The deployed server continues to serve the previous enrichment until the next POST /api/v1/server/deploy. Codegen reads the latest enrichmentData at deploy time.

POST /api/v1/server/enrich
bash
curl -X POST "$MCPFACTORY_BASE_URL/api/v1/server/enrich" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "organizationId": "<organization-id>",
    "projectId": "<project-id>",
    "serverId": "<server-id>",
    "toolId": "<tool-id>"
  }'

Mutation

Create a project

Use POST /api/v1/projects to create a new project in an organization. The first cross-cutting Create POST contract example: empty-name validation, visibility defaults to "private", admin/owner role floor.

Auth

bearer API key or browser session. Required role: admin or owner. Members and viewers receive 403 insufficient_role.

Body

organizationId, name (1–100 chars), optional description (defaults null), optional visibility ("private" | "public", defaults "private").

Idempotency

honors the Idempotency-Key header per the cross-cutting POST mutation contract (24-hour replay cache).

Visibility transitions

setting "public" on create fires the project-sharing entitlement check (shareProjects). Tier-restricted plans receive 403 insufficient_capability.

Response shape

201 Created with the full project + organization summary, matching GET /api/v1/project so the create response is interchangeable with the read response.

Audit

emits controlPlane.projectCreated.

POST /api/v1/projects
bash
curl -X POST "$MCPFACTORY_BASE_URL/api/v1/projects" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "organizationId": "<organization-id>",
    "name": "Stripe MCP",
    "description": "PSP charges, refunds, disputes.",
    "visibility": "private"
  }'

Mutation

Update a project

Use PATCH /api/v1/project to update project metadata. The first cross-cutting PATCH-on-owned-by-org-resources contract example: RFC 7396 merge patch, naturally idempotent (Idempotency-Key silently ignored), 200 OK with the updated resource shaped exactly like GET /api/v1/project.

Auth

bearer API key or browser session. Required role: admin or owner.

Body

organizationId + projectId required. Settable fields: name, description, visibility ("private" | "public"). Omit a key to leave unchanged; pass null to clear (where nullable). Empty body returns 400 invalid_request.

Unknown top-level keys return 400 invalid_request with error.details.field set to the offending key (or dotted path for nested merges).

Idempotency

PATCH is naturally idempotent at the application level — the cross-cutting Idempotency-Key header is silently ignored on this route.

Visibility transitions

setting "public" fires the project-sharing entitlement check (shareProjects). Tier-restricted plans receive 403 insufficient_capability.

Audit

emits controlPlane.projectPatched with metadata.patchedFields listing the top-level keys included in the body.

PATCH /api/v1/project
bash
curl -X PATCH "$MCPFACTORY_BASE_URL/api/v1/project" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "organizationId": "<organization-id>",
    "projectId": "<project-id>",
    "name": "Renamed Project"
  }'

Mutation

Create a server skeleton

Use POST /api/v1/servers to create an empty server skeleton in a project. v1 deliberately ships an empty-skeleton create — source-material wiring (OpenAPI/GraphQL ingestion, tool generation) belongs on POST /api/v1/server/ingest. The cheapest 'I need a server slot' call.

Auth

bearer API key or browser session. Required role: admin or owner.

Body

organizationId, projectId, name (1–100 chars), optional description (defaults null).

Empty-skeleton

no apiSource, no generated bundle, no deployment. To populate, call POST /api/v1/server/ingest (creates a server from a spec) or follow with the dashboard import flow.

Including any deferred source-material fields on the create body returns 400 invalid_request with error.details.field — the same behavior as any unknown key.

Initial state

status = "draft", version = "0.0.0". Both move forward through the deploy lifecycle.

Response

201 Created with the new server shaped like GET /api/v1/server. Fires server_limit_reached (409) when the org has hit its plan-bound server count cap.

Audit

emits controlPlane.serverCreated.

POST /api/v1/servers
bash
curl -X POST "$MCPFACTORY_BASE_URL/api/v1/servers" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "organizationId": "<organization-id>",
    "projectId": "<project-id>",
    "name": "Stripe MCP"
  }'

Mutation

Update a server

Use PATCH /api/v1/server to update server metadata. v1 is intentionally restricted to name and description — runtime configuration (oauth, deploymentDefaults, runtime bindings) lives in mcpServers.config and is changed through POST /api/v1/server/deploy, not PATCH.

Auth

bearer API key or browser session. Required role: admin or owner.

Body

organizationId + serverId required. Settable fields in v1: name, description. Empty body returns 400 invalid_request.

Including any unknown top-level key (including oauth, deploymentDefaults, runtimeConfig) returns 400 invalid_request with error.details.field. PATCH is metadata; deploy is the act.

Idempotency

PATCH is naturally idempotent — the Idempotency-Key header is silently ignored.

Response

200 OK with the updated server shaped like GET /api/v1/server.

Audit

emits controlPlane.serverPatched.

PATCH /api/v1/server
bash
curl -X PATCH "$MCPFACTORY_BASE_URL/api/v1/server" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "organizationId": "<organization-id>",
    "serverId": "<server-id>",
    "name": "Renamed Server"
  }'

Mutation

Create a skill draft

Use POST /api/v1/skills to create an empty skill draft in a project. v1 deliberately ships an empty-draft create — structural fields (workflow, schemas, compositionGraph, etc.) are authored through Skill Studio, not the public control-plane create.

Auth

bearer API key or browser session. Required role: admin or owner.

Body

organizationId, projectId, name (1–100 chars), optional description (defaults null), optional visibility ("private" | "public" | "unlisted", defaults "private").

Settable-fields parity with PATCH

POST /api/v1/skills and PATCH /api/v1/skill share the exact same allowed-key set (name, description, visibility), so callers do not need to learn a different field list across the two routes.

Including any deferred structural fields (workflow, schemas, compositionGraph, runDeterministicGuardrail, triggerPhrases, triggerDescription, dependencies, guardrails, examples, skillMd) returns 400 invalid_request with error.details.field.

Visibility transitions

non-private values fire the skill-sharing entitlement check (shareSkills).

Response

201 Created with the new skill draft. status = "draft", version = "0.0.0", empty arrays for triggerPhrases / dependencies / guardrails / examples.

Audit

emits controlPlane.skillCreated.

POST /api/v1/skills
bash
curl -X POST "$MCPFACTORY_BASE_URL/api/v1/skills" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "organizationId": "<organization-id>",
    "projectId": "<project-id>",
    "name": "Charge refund flow"
  }'

Mutation

Update a skill

Use PATCH /api/v1/skill to update skill metadata. v1 is restricted to name, description, visibility — structural fields (workflow, schemas, compositionGraph, etc.) remain authored through Skill Studio.

Auth

bearer API key or browser session. Required role: admin or owner.

Body

organizationId + skillId required. Settable fields: name, description, visibility ("private" | "public" | "unlisted").

Including any structural field returns 400 invalid_request with error.details.field. The skill family is the only one with a frozen-state PATCH gate today (409 skill_immutable on archived skills).

Idempotency

PATCH is naturally idempotent — Idempotency-Key is silently ignored.

Visibility transitions away from "private" fire the skill-sharing entitlement check (shareSkills).

Response

200 OK with the updated skill shaped like GET /api/v1/skill.

Audit

emits controlPlane.skillPatched.

PATCH /api/v1/skill
bash
curl -X PATCH "$MCPFACTORY_BASE_URL/api/v1/skill" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "organizationId": "<organization-id>",
    "skillId": "<skill-id>",
    "visibility": "public"
  }'

Mutation

Archive a skill

Use POST /api/v1/skill/archive to retire a skill. The first state-transition POST in v1 — replays as success: re-archiving an already-archived skill returns 200 OK silently with no skill_already_archived code.

Auth

bearer API key or browser session. Required role: admin or owner. Archiving is a workspace-management action, not a personal-installation flip.

Body

organizationId, projectId, skillId.

Replay-as-success

re-archiving an archived skill returns 200 OK silently. The pattern applies to every state-transition POST in v1 (re-disable, re-revoke, etc.).

Response

200 OK with the resource (status = "archived", archivedAt set), shaped like GET /api/v1/skill.

Audit

emits controlPlane.skillArchived.

POST /api/v1/skill/archive
bash
curl -X POST "$MCPFACTORY_BASE_URL/api/v1/skill/archive" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "organizationId": "<organization-id>",
    "projectId": "<project-id>",
    "skillId": "<skill-id>"
  }'

Mutation

Install a skill

Use POST /api/v1/skill/install to install a skill into the caller's project. Operates on the caller's own installation row — installations are per-user in the data model, so cross-user installs are not in v1.

Auth

bearer API key or browser session. Required role: member or higher (installing is a workspace-use action, not a settings-class change).

Body

organizationId, projectId, skillId, optional version (semver to pin), optional config (per-installation runtime overrides as an opaque string the skill runtime consumes).

Commerce gate

402 purchase_required when the skill is monetized through the registry and the caller has no active purchase or auto-approval. error.details.{ marketplace, listingId } carry the listing context.

Replay-as-success

re-installing returns 200 OK silently if a row already exists with the same (callerUserId, projectId, skillId).

Response

200 OK with the installation row, shaped to match GET /api/v1/skill/installations.

Audit

emits controlPlane.skillInstalled.

POST /api/v1/skill/install
bash
curl -X POST "$MCPFACTORY_BASE_URL/api/v1/skill/install" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "organizationId": "<organization-id>",
    "projectId": "<project-id>",
    "skillId": "<skill-id>",
    "version": "1.0.0"
  }'

Mutation

Uninstall a skill

Use POST /api/v1/skill/uninstall to mark the caller's installation of a skill as removed. Different mental model from POST /api/v1/installation/remove (same target state, different addressing — see below).

Auth

bearer API key or browser session. Required role: member + ownership of the installation row (the caller's userId must match the installation's userId).

Body

organizationId, projectId, skillId. Looked up by (callerUserId, projectId, skillId).

Returns 404 installation_not_found when no installation row exists for that triple — different from skill/archive`s replay-as-success because there is no row to replay against.

Two-route pattern

POST /api/v1/skill/uninstall and POST /api/v1/installation/remove end at the same installations.status === "removed" state. Use uninstall for the "uninstall this skill from my project" mental model; use remove for the "remove this installation row" mental model. Both ship.

Response

200 OK with the installation row updated to status = "removed".

Audit

emits controlPlane.skillUninstalled.

POST /api/v1/skill/uninstall
bash
curl -X POST "$MCPFACTORY_BASE_URL/api/v1/skill/uninstall" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "organizationId": "<organization-id>",
    "projectId": "<project-id>",
    "skillId": "<skill-id>"
  }'

Mutation

Disable an installation

Use POST /api/v1/installation/disable to flip an installation by id rather than by (skillId, projectId). For callers that already have the installation row from a prior GET /api/v1/skill/installations and want to flip its status without re-resolving the skill scope.

Auth

bearer API key or browser session. Required role: member + ownership of the installation row.

Body

organizationId, installationId.

Replay-as-success

re-disabling a disabled installation returns 200 OK silently.

Ownership-leak safety

cross-user "not yours" + "doesn't exist" + "wrong organization" all collapse to 404 installation_not_found so cross-user enumeration is not a side channel.

Response

200 OK with the installation row updated to status = "disabled".

Audit

emits controlPlane.installationDisabled.

POST /api/v1/installation/disable
bash
curl -X POST "$MCPFACTORY_BASE_URL/api/v1/installation/disable" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "organizationId": "<organization-id>",
    "installationId": "<installation-id>"
  }'

Mutation

Remove an installation

Use POST /api/v1/installation/remove to flip an installation by id to "removed". Same target state as POST /api/v1/skill/uninstall but addresses the row by id rather than by (callerUserId, projectId, skillId).

Auth

bearer API key or browser session. Required role: member + ownership of the installation row.

Body

organizationId, installationId.

Replay-as-success on already-removed rows.

Ownership-leak safety

same 404 collapse pattern as installation/disable.

Response

200 OK with the installation row updated to status = "removed".

Audit

emits controlPlane.installationRemoved.

POST /api/v1/installation/remove
bash
curl -X POST "$MCPFACTORY_BASE_URL/api/v1/installation/remove" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "organizationId": "<organization-id>",
    "installationId": "<installation-id>"
  }'

Mutation

Revoke an OAuth connection

Use DELETE /api/v1/oauth/connection to revoke an OAuth provider connection. When the provider has a revocationUrl, MCPCloud first asks it to revoke the refresh and access tokens (on every plan), then marks the row "revoked".

Auth

bearer API key or browser session. Required role: member + ownership of the connection row.

Body

organizationId, connectionId. DELETE with a JSON body is unconventional but Convex's HTTP router accepts it, and keeping the shape aligned with the POST family in this section makes client code simpler.

Best-effort upstream revoke

a provider that refuses or is unreachable never blocks the disconnect. The row is still marked revoked, MCPCloud stops issuing its tokens, and the provider's answer is recorded in the security audit log.

Replay-as-success on already-revoked connections.

Token material (accessToken, refreshToken) is never returned.

Audit

emits controlPlane.oauthConnectionRevoked.

DELETE /api/v1/oauth/connection
bash
curl -X DELETE "$MCPFACTORY_BASE_URL/api/v1/oauth/connection" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "organizationId": "<organization-id>",
    "connectionId": "<connection-id>"
  }'

Billing — admin / owner only

Billing visibility

The first capability-gated reads. Both endpoints require viewBillingDetails — i.e. admin or owner role in the requested organization. Members and viewers receive 403 insufficient_capability.

Billing — admin / owner only

Billing usage

Use /api/v1/billing/usage to read the org's recent edge-request and CPU usage rolled into daily buckets. Capability-gated: the caller must hold viewBillingDetails (admin or owner). Members and viewers receive 403 insufficient_capability.

Required params

send organizationId. Optional windowDays defaults to 30 (max 90).

Response

a totals object plus a recentDaily array with per-UTC-day rollups across every deployment in the org.

CPU and duration are reported in milliseconds. averageCpuTimeMs and averageDurationMs are derived; either may be null when no samples exist.

Why this exists

distinguishing "auth failure" from "plan-limit failure" requires aggregate visibility scripts cannot derive from per-deployment reads.

Capability boundary

aggregate cost data is sensitive cross-organization signal in a way per-deployment counts are not. Admins and owners see usage; everyone else does not.

GET /api/v1/billing/usage
bash
curl "$MCPFACTORY_BASE_URL/api/v1/billing/usage?organizationId=<organization-id>&windowDays=30" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Billing — admin / owner only

Credits ledger

Use /api/v1/credits/ledger to read the org's credit balance and a paginated history of grants, debits, and adjustments. Same capability gate as billing usage — admin or owner only.

Required params

send organizationId. Optional limit defaults to 50 (max 100). Optional cursor pages backwards in time using the nextCursor returned by the previous response.

All credit values are denominated in micros (1/1,000,000 USD-equivalent). Convert with value / 1_000_000 for display.

amountMicros is negative for debits and positive for grants/adjustments. entryKind is grant | debit | adjustment; source identifies which subsystem produced the entry.

Pagination

nextCursor is null when fewer than limit rows were returned. Pass it back as cursor to get the next page.

GET /api/v1/credits/ledger
bash
curl "$MCPFACTORY_BASE_URL/api/v1/credits/ledger?organizationId=<organization-id>&limit=25" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Audit — admin / owner only

Audit visibility

The second capability-gated read. Requires viewAuditLogs — i.e. admin or owner role in the requested organization. The same audit history this endpoint serves also records who tried to read it without permission.

Audit — admin / owner only

Audit events

Use /api/v1/audit/events to page through the workspace's auditLog rows, including the access denials this same v1 surface emits. Capability-gated: the caller must hold viewAuditLogs (admin or owner). Members and viewers receive 403 insufficient_capability.

Required params

send organizationId. Optional limit defaults to 50 (max 100).

Pagination

pass cursor (the previous response's nextCursor) to fetch the next page. nextCursor is null when the page returned fewer rows than limit. The cursor is opaque to clients but is just the previous page's last timestamp encoded as a string; rows are append-only so cursors do not expire.

Optional filter

pass action to narrow to one event type (e.g. controlPlane.accessDenied while reviewing an incident, controlPlane.serverDeployCreated when auditing release activity).

Response shape

each event row carries action, actorId, entityId/Type, organizationId, timestamp, and an opaque metadata JSON string. The metadata schema for any one action is owned by the route family that emitted it; the audit endpoint preserves it as-is so adding new actions does not break this contract.

Capability boundary

workspace audit history is admin-and-owner data because actor history can disclose membership patterns and access attempts that are not part of the read-only discovery surface.

GET /api/v1/audit/events
bash
curl "$MCPFACTORY_BASE_URL/api/v1/audit/events?organizationId=<organization-id>&limit=25&action=controlPlane.accessDenied" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

OAuth state

OAuth connections

Read the state of the caller's OAuth connections — provider, status, expiry, last error — before invoking a protected runtime. Token material is never returned.

Discovery

OAuth connection discovery

Use /api/v1/oauth/connections to read the state of the caller's OAuth connections — provider, status, expiry, last error — before invoking a protected runtime. Token material is never returned.

Required params

send organizationId. The caller must be a member of that organization, and only the caller's own connections are returned. Cross-user enumeration is intentionally blocked.

Optional filters

serverId narrows to one MCP server; status defaults to connected and accepts connected, expired, error, revoked, all; limit defaults to 50 with a max of 100.

Why this exists

automation needs to distinguish "needs a re-consent flow" from "stale provider tokens" before deciding whether to call /oauth/runtime/token or send the user back through the connect flow.

Read-only

calling this route does not refresh tokens. Use POST /oauth/runtime/token when you need a fresh provider grant.

GET /api/v1/oauth/connections
bash
curl "$MCPFACTORY_BASE_URL/api/v1/oauth/connections?organizationId=<organization-id>&status=connected&limit=20" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Exact read

Single OAuth connection

Use /api/v1/oauth/connection when you have a stored connectionId and want to revalidate state for one connection without listing the whole set. The connection must belong to the calling user; otherwise the route returns oauth_connection_not_found.

Required params

send organizationId and connectionId.

Response shape

one connection metadata object plus the parent organization summary. Token material is omitted.

Why this exists

when a runtime call fails, automation typically pivots from the connectionId on the failure into a state read before deciding whether to retry, refresh, or prompt for re-consent.

GET /api/v1/oauth/connection
bash
curl "$MCPFACTORY_BASE_URL/api/v1/oauth/connection?organizationId=<organization-id>&connectionId=<connection-id>" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Registry

Registry artifacts

Public discovery and per-organization private reads for distributable server and skill artifacts. The registry table is shared across both families, so one list endpoint and one exact-read endpoint cover both.

Discovery

Registry artifact discovery

Use /api/v1/registry/artifacts to span the marketplace and any organization-private registry rows the caller is permitted to see. Server and skill artifacts share the registry table, so this single list covers both families.

Default filter

only public artifacts are returned. Pass access=privateOrg or access=all together with organizationId to surface the caller's organization-private rows.

Optional filter

artifactType=server|skill narrows the family. The default is all.

Optional search

q runs a relevance score across name, description, tags, and indexed semantic terms. Results are sorted by score, then by latestPublishedAt desc.

Visibility

unlisted artifacts are never enumerated. Removed (taken-down) artifacts are filtered out. Use the exact-read route to fetch an unlisted artifact by id.

GET /api/v1/registry/artifacts
bash
curl "$MCPFACTORY_BASE_URL/api/v1/registry/artifacts?artifactType=skill&access=public&q=lead+routing&limit=20" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Exact read

Single registry artifact

Use /api/v1/registry/artifact when you already have a registry artifact id. The exact-read route returns the readme, dependency summary, and the most recent versions, including a download path for each version snapshot when available.

Required params

send artifactId.

Visibility

unlisted artifacts can be fetched here when the caller knows the id. privateOrg artifacts require caller membership in the artifact's organization. Removed artifacts return registry_artifact_not_found.

Versions

the response includes up to the 12 most recent published versions, newest first. Use versionCount on the artifact to detect whether more history exists, and call /api/v1/registry/artifact/versions to paginate the full history.

Snapshot path

snapshotPackageDownloadPath is null when the version was published without a snapshot artifact; otherwise it points at /api/v1/artifacts/download with the right artifactId query parameter pre-encoded.

GET /api/v1/registry/artifact
bash
curl "$MCPFACTORY_BASE_URL/api/v1/registry/artifact?artifactId=<registry-artifact-id>" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Artifact read

Registry artifact versions

Use /api/v1/registry/artifact/versions to paginate the full version history of one registry artifact when versionCount on the artifact summary indicates more rows exist than the 12 returned by the exact-read route.

Required params

send artifactId.

Optional params

limit (1..100, default 50) and cursor (the nextCursor returned by the previous response).

Pagination

nextCursor is an opaque forward-only marker bound to the previous page's last createdAt. nextCursor is null when the page returned fewer rows than limit.

Visibility

tracks the parent artifact. public artifacts return their version history to any authenticated caller; privateOrg requires membership; unlisted is reachable when the caller knows the artifact id. "Removed", "private without membership", and "no such artifact" all collapse to one 404 to avoid leaking the existence of privateOrg rows.

GET /api/v1/registry/artifact/versions
bash
curl "$MCPFACTORY_BASE_URL/api/v1/registry/artifact/versions?artifactId=<registry-artifact-id>&limit=25" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Exact read

Single registry artifact version

Use /api/v1/registry/artifact/version to fetch one published version by id once you have already paginated the versions list. Returns the full version row plus the parent artifact summary.

Required params

send both artifactId and versionId.

Visibility

gating is enforced on the parent artifact, so version.access reflects publish-time access and does not by itself imply the artifact is still publicly visible.

Bound check

the route returns registry_artifact_version_not_found when versionId exists but is bound to a different artifact, instead of silently rebinding it.

GET /api/v1/registry/artifact/version
bash
curl "$MCPFACTORY_BASE_URL/api/v1/registry/artifact/version?artifactId=<registry-artifact-id>&versionId=<version-id>" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY"

Artifact transport

Artifact downloads

One authenticated transport for both resolved generated bundles and discrete artifact blobs. The body is raw bytes; the metadata travels in response headers, including a SHA-256 digest when available.

Transport

Artifact download

Use /api/v1/artifacts/download to pull resolved generated bundles or discrete artifact blobs through one authenticated transport. The route returns raw bytes, not JSON, and conveys metadata through response headers.

Selector A — discrete artifact

pass artifactId to download one stored blob (e.g. an imported source-material snapshot).

Selector B — generated source bundle

pass projectId and serverId together, with optional bundle=base|current (defaults to current).

Mutually exclusive

combining artifactId with projectId, serverId, or bundle returns invalid_selector. Pick exactly one selector per request.

Response headers

Content-Type and Content-Disposition convey shape; Content-Length gives byte size when known; X-Artifact-Sha256 carries a SHA-256 digest when the upstream record stored one — treat its absence as "unknown" rather than "verified".

Cache-Control is private, max-age=60. The route is per-user authenticated; never cache it on a shared edge.

Failure model

400 invalid_selector / project_id_required / server_id_required / invalid_bundle_variant; 401 authentication_required / invalid_api_key; 403 organization_access_denied; 404 artifact_not_found / bundle_not_found.

GET /api/v1/artifacts/download
bash
# Discrete artifact selector
curl -L "$MCPFACTORY_BASE_URL/api/v1/artifacts/download?artifactId=<artifact-id>" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  --output ./artifact.bin

# Generated source bundle selector
curl -L "$MCPFACTORY_BASE_URL/api/v1/artifacts/download?projectId=<project-id>&serverId=<server-id>&bundle=current" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  --output ./bundle.zip

Runtime Access

Exchange a runtime token

Use the canonical v1 route, `/api/v1/runtime/auth/token`, to exchange authenticated control-plane credentials for a short-lived runtime token before calling a protected deployment.

1. Generate a personal API key

Create the key in Settings and store the raw value immediately. The secret is shown once.

Open Settings > API Keys

2. Send organization context explicitly

Machine callers should send both `organizationId` and `deploymentId`. The app exposes both as copyable values.

3. Use the runtime token against the deployment URL

Protected runtimes accept the short-lived runtime token, not the long-lived personal API key used to obtain it.

POST /api/v1/runtime/auth/token
bash
curl -X POST "$MCPFACTORY_BASE_URL/api/v1/runtime/auth/token" \
  -H "Authorization: Bearer $MCPFACTORY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "deploymentId": "<deployment-id>",
    "organizationId": "<organization-id>"
  }'
Initialize against the deployed runtime
bash
curl -X POST "$DEPLOYED_RUNTIME_URL" \
  -H "Authorization: Bearer $MCPFACTORY_RUNTIME_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": "list-1",
    "method": "tools/list",
    "params": {}
  }'

The deployed runtime response is server-defined. This example shows a typical MCP initialize result after the auth boundary has already been satisfied.

OAuth runtime grants

`/oauth/runtime/token` sits next to the core v1 auth surface. It is not the primary quickstart path, but it follows the same public error envelope and `X-Request-Id` response contract as the rest of the machine-facing auth routes.

Use this only for provider-backed runtime flows

The request requires `x-mcpcloud-runtime-token` and `connectionId`. MCPCloud refreshes the provider token when needed, then returns the provider access token, expiry, and scopes.

POST /oauth/runtime/token
bash
curl -X POST "$MCPFACTORY_BASE_URL/oauth/runtime/token" \
  -H "x-mcpcloud-runtime-token: <runtime-grant-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "connectionId": "<oauth-connection-id>",
    "forceRefresh": false
  }'

Reliability

Common failures

Every machine-facing auth route returns the same error envelope: `error.code`, `error.message`, `error.requestId`, `error.details`, and `error.docsUrl`.

Structured error responsejson
{
  "error": {
    "code": "organization_id_required",
    "message": "organizationId is required for API-key runtime token exchange.",
    "requestId": "api_123",
    "details": {
      "field": "organizationId"
    },
    "docsUrl": "/docs/api#common-failures"
  }
}
400

Missing deployment identifier

missing_deployment_id

Pass `deploymentId` in the JSON body before retrying.

401

Organization id required for machine flows

organization_id_required

When you authenticate with a personal API key, send `organizationId` explicitly in the JSON body.

403

Deployment runtime access denied

deployment_runtime_access_denied

Verify the deployment belongs to the organization you supplied and that your user is a member of that organization.

429

Too many exchange attempts

rate_limit_exceeded

Honor `Retry-After` and stop looping on invalid credentials or mismatched organization context.