Skip to content

API quickstart

This page takes you from zero to a first request, then to a real async mutation — triggering an on-demand backup and waiting for the job to finish. Pick your language with the tabs; the choice persists across the page.

Mint a personal API key in the dashboard under Settings → API keys, with the scopes this quickstart needs: sites:read to list sites, backups:write to trigger a backup, and jobs:read to watch the job that tracks it. The full secret — prefixed mfk_live_… — is shown once at creation, so copy it then. See creating keys for the walkthrough and scopes for the catalog.

Keep the key out of your shell history
export FORGE_TOKEN="mfk_live_…"

List your sites. Every authenticated call carries the token as a bearer credential and a pinned Forge-Version:

GET /v1/sites
curl https://api.managed.dev/v1/sites \
-H "Authorization: Bearer $FORGE_TOKEN" \
-H "Forge-Version: 2026-06-23"

The response is the standard envelopedata, a pagination block for collections, and a request_id you can quote when asking for help:

200 OK
{
"data": [
{
"id": "site_01J7ZC3W9Q8F2K7M3N0XB5R4D2",
"slug": "acme-store",
"domain": "acme-store.com",
"profile": "bedrock",
"php_version": "8.3",
"runtime": "wordpress",
"region": "us-east",
"status": "running",
"created_at": "2026-05-01T12:00:00Z"
}
],
"pagination": { "next_cursor": null, "has_more": false },
"request_id": "req_01J9F2K7M3N0XB5R4D2W9Q8F2K"
}

If you get a 401, the token is missing or wrong; a 403 with code scope.insufficient means the key is valid but lacks sites:read. See errors for the full taxonomy.

Now do something that takes real work: trigger an on-demand backup. Mutations that aren’t instantaneous return 202 Accepted with a job and a Location header, rather than blocking the connection.

Pass an Idempotency-Key so a retried request reuses the same job instead of starting a second backup (the Go and TypeScript SDKs generate one for you on every POST):

POST /v1/sites/{id}/backup
curl -X POST \
https://api.managed.dev/v1/sites/site_01J7ZC3W9Q8F2K7M3N0XB5R4D2/backup \
-H "Authorization: Bearer $FORGE_TOKEN" \
-H "Forge-Version: 2026-06-23" \
-H "Idempotency-Key: 6f1c8b2a-1d3e-4f5a-9b0c-2e7d8a1f3c4b"

The 202 returns the job in the envelope. status is queued; the Location header points at the job you’ll poll:

202 Accepted
HTTP/1.1 202 Accepted
Location: /v1/jobs/job_01J9XK4M2N7P0QR5S6T7U8V9W0
Job body
{
"data": {
"id": "job_01J9XK4M2N7P0QR5S6T7U8V9W0",
"type": "backup.create",
"status": "queued",
"progress": 0,
"created_at": "2026-06-23T18:04:11.412Z",
"resource": {
"type": "backup",
"id": "site_01J7ZC3W9Q8F2K7M3N0XB5R4D2",
"site_id": "site_01J7ZC3W9Q8F2K7M3N0XB5R4D2"
},
"result": null,
"error": null,
"links": {
"self": "/v1/jobs/job_01J9XK4M2N7P0QR5S6T7U8V9W0",
"stream": "/v1/jobs/job_01J9XK4M2N7P0QR5S6T7U8V9W0/stream"
}
},
"request_id": "req_01J9F3A4B5C6D7E8F9G0H1J2K3"
}

Poll the job until status is succeeded (or failed). The async jobs page covers all three consumption paths — the 202 body, server-sent events, and ETag long-poll for CI use.

Poll the job
curl https://api.managed.dev/v1/jobs/job_01J9XK4M2N7P0QR5S6T7U8V9W0 \
-H "Authorization: Bearer $FORGE_TOKEN"
200 OK — terminal state
{
"data": {
"id": "job_01J9XK4M2N7P0QR5S6T7U8V9W0",
"type": "backup.create",
"status": "succeeded",
"progress": 1,
"created_at": "2026-06-23T18:04:11.412Z",
"resource": {
"type": "backup",
"id": "site_01J7ZC3W9Q8F2K7M3N0XB5R4D2",
"site_id": "site_01J7ZC3W9Q8F2K7M3N0XB5R4D2"
},
"result": { "snapshot_id": "snap_01J9YV5N3P8Q1R6S7T8U9V0W1X" },
"error": null,
"links": {
"self": "/v1/jobs/job_01J9XK4M2N7P0QR5S6T7U8V9W0",
"stream": "/v1/jobs/job_01J9XK4M2N7P0QR5S6T7U8V9W0/stream"
}
},
"request_id": "req_01J9F4B5C6D7E8F9G0H1J2K3L4"
}

That’s the whole shape of the API: an authenticated request, a consistent envelope, and — for anything that takes real work — a job you can watch to completion.