Requests and errors
Public ids, pages, error format, Idempotency-Key, rate limits, the request log, and how to wait for slow jobs.
Basics
- Base URL:
https://YOUR-MYFORGE/api/v1. - Send and receive JSON. Send
Accept: application/jsonand, for bodies,Content-Type: application/json. - Times are ISO 8601 (
2026-10-09T10:00:00+00:00).
Public ids
Servers, sites, deployments, backups, commands and similar records are identified by a public id (a UUID). You find it in the list endpoints and in the dashboard address. The internal numeric id is never used in the API. A wrong or out-of-reach id answers 404.
Lists and pages
List endpoints return a page of results:
{
"data": [ { "id": "...", "domain": "app.example.com" } ],
"links": { "first": "...", "last": "...", "prev": null, "next": "https://YOUR-MYFORGE/api/v1/sites?page=2" },
"meta": { "current_page": 1, "per_page": 50, "total": 61 }
}
Follow links.next until it is null. Page sizes: sites and servers 50, deployments 25, backups 25, deletion requests 50. The audit log (/audit-log) returns 50 per page with links.next and links.prev and no total.
Single records and errors
A single record comes as {"data": {...}}. Errors are JSON:
{ "message": "No such site." }
For 422 (invalid input), errors names each field:
{
"message": "The domain field is required.",
"errors": { "domain": ["The domain field is required."] }
}
| Status | Meaning |
|---|---|
401 |
Token missing, wrong or expired. |
403 |
A check failed: scope, plan, owner rule, permission, or IP. |
404 |
Does not exist, belongs to another team, or is outside the token's limits. |
409 |
Not possible right now: a deploy is already running, wrong site state, nothing to promote. |
422 |
Invalid input, plan limit reached, or reused Idempotency-Key with a different request. |
429 |
Too many requests. Wait and retry. |
Idempotency-Key
Add an Idempotency-Key header (1 to 120 characters, unique per intended action) to every change (POST, PUT, PATCH, DELETE):
curl -X POST $MYFORGE/sites/SITE_ID/deploy \
-H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: deploy-shop-2026-10-09-1"
How it behaves:
- The first answer is kept for 24 hours, per token.
- Sending the same key with the same request again returns the stored answer and the header
Idempotent-Replayed: true. Nothing runs twice. - The same key with a different method, path or body answers
422. - A retry while the first request is still running answers
409. Wait a moment and retry. - Answers that carry a secret shown only once (a WordPress admin password, a new database password, a new webhook secret) are never stored. Replaying them is not possible, so save the secret the first time.
Use a new key for each new action. For a retry after a timeout, use the same key.
Rate limits
- 120 requests per minute per token.
- 30 changes per minute per token (changes are POST, PUT, PATCH, DELETE).
When you get 429, wait (the Retry-After header tells how long, when present) and try again. Do not poll faster than every 5 to 10 seconds.
The request log
Every call is logged: method, route, path, status, IP address and duration. Bodies are never logged. The log is kept for 30 days and is shown under API, then Activity (last 100 calls). Changes also appear in the team's audit log as source api with the token's name. See Teams and the audit log.
Slow jobs: 202 and polling
Most changes on servers take time, so the API answers 202 Accepted (or 201 for new records) as soon as the job is queued. The work happens afterwards over SSH. To know when it is done, read the record again:
| You did | Poll | Done when |
|---|---|---|
| Create a site | GET /sites/{site} |
status is active (or failed) |
| Deploy, roll back | GET /sites/{site}/deployments/{id} |
status is succeeded or failed |
| Run a command | GET /sites/{site}/commands/{id} |
status is final; read output and exit_code |
| Back up | GET /sites/{site}/backups/{id} |
status stops changing (an error field explains a failure) |
| Renew certificate | GET /sites/{site}/certificate |
expires_at moved, history shows renewed |
| Add or connect a server | GET /servers/{id} |
status is active, adopted or failed |
| Move a site | GET /sites/{site}/move |
the move reaches its final state |
Polling pattern:
until [ "$(curl -s -H "Authorization: Bearer $TOKEN" $MYFORGE/sites/$SITE | jq -r .data.status)" != "creating" ]; do
sleep 10
done
Stop after a sensible time (for example 15 minutes for a deploy) and report instead of looping forever.