pfcloud / Developers

Getting started

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/json and, 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.