Skip to content

API quickstart

Preview This page takes you from zero to a first request, then to a real async mutation — installing a plugin 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 and wp.plugins:write to install a plugin. 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",
"name": "acme-store",
"runtime": "wordpress",
"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 insufficient scope means the key is valid but lacks sites:read. See errors for the full taxonomy.

Now do something that takes real work: install a plugin on an environment. 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 install:

POST a component (plugin) to an environment
curl -X POST \
https://api.managed.dev/v1/sites/site_01J7ZC3W9Q8F2K7M3N0XB5R4D2/environments/env_01J8AA1B2C3D4E5F6G7H8J9K0L/components \
-H "Authorization: Bearer $FORGE_TOKEN" \
-H "Forge-Version: 2026-06-23" \
-H "Idempotency-Key: 6f1c8b2a-1d3e-4f5a-9b0c-2e7d8a1f3c4b" \
-H "Content-Type: application/json" \
-d '{ "kind": "plugin", "slug": "wordpress-seo", "source": "registry", "version": "latest", "activate": true }'

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": "component.install",
"status": "queued",
"progress": 0,
"created_at": "2026-06-23T18:04:11.412Z",
"resource": {
"type": "component",
"kind": "plugin",
"slug": "wordpress-seo",
"site_id": "site_01J7ZC3W9Q8F2K7M3N0XB5R4D2",
"env_id": "env_01J8AA1B2C3D4E5F6G7H8J9K0L"
},
"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 and Terraform.

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": "component.install",
"status": "succeeded",
"progress": 100,
"created_at": "2026-06-23T18:04:11.412Z",
"result": { "kind": "plugin", "slug": "wordpress-seo", "version": "24.1", "active": true }
},
"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.