pfcloud / Developers

Reference

OpenAPI and clients

The OpenAPI file, generating clients, and examples in curl, JavaScript, PHP and Python.

The OpenAPI file

GET https://YOUR-MYFORGE/api/v1/openapi.json

It is public (no token) and describes every endpoint with its scope (x-scope), request body, and response codes. In the dashboard it is also shown under API, then API reference. The pages in the API reference group of these docs are generated from it.

Generate a client with any OpenAPI 3.1 tool, for example openapi-generator or openapi-typescript. Set the base URL to https://YOUR-MYFORGE/api/v1 and the security scheme to a bearer token. Add the Idempotency-Key header to every change.

For AI agents: /llms.txt and /llms-full.txt. See AI with the REST API.

curl

export MYFORGE=https://YOUR-MYFORGE/api/v1
export TOKEN=mf_YOUR_TOKEN

curl -s -H "Authorization: Bearer $TOKEN" $MYFORGE/sites

curl -s -X POST $MYFORGE/sites/SITE_ID/deploy \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: deploy-shop-20261009-1"

JavaScript (Node 18+ or browser)

Never put a token in browser code that other people can open. Use this on a server or in CI.

const base = 'https://YOUR-MYFORGE/api/v1';
const headers = { Authorization: `Bearer ${process.env.MYFORGE_TOKEN}`, Accept: 'application/json' };

const sites = await (await fetch(`${base}/sites`, { headers })).json();
console.log(sites.data.map((s) => s.domain));

const res = await fetch(`${base}/sites/${sites.data[0].id}/deploy`, {
  method: 'POST',
  headers: { ...headers, 'Idempotency-Key': crypto.randomUUID() },
});
if (res.status !== 202) throw new Error(`Deploy refused: ${res.status} ${(await res.json()).message}`);
const deployment = (await res.json()).data;

PHP

$http = Http::withToken(env('MYFORGE_TOKEN'))
    ->acceptJson()
    ->baseUrl('https://YOUR-MYFORGE/api/v1');

$sites = $http->get('/sites')->throw()->json('data');

$response = $http
    ->withHeaders(['Idempotency-Key' => (string) Str::uuid()])
    ->post("/sites/{$sites[0]['id']}/deploy");

if ($response->status() !== 202) {
    throw new RuntimeException($response->json('message'));
}

Python

import os, uuid, requests

base = "https://YOUR-MYFORGE/api/v1"
headers = {"Authorization": f"Bearer {os.environ['MYFORGE_TOKEN']}", "Accept": "application/json"}

sites = requests.get(f"{base}/sites", headers=headers, timeout=30).json()["data"]

r = requests.post(
    f"{base}/sites/{sites[0]['id']}/deploy",
    headers={**headers, "Idempotency-Key": str(uuid.uuid4())},
    timeout=30,
)
r.raise_for_status()
deployment = r.json()["data"]

Waiting for the deployment (Python)

import time

while True:
    d = requests.get(f"{base}/sites/{site_id}/deployments/{deployment['id']}", headers=headers, timeout=30).json()["data"]
    if d["status"] in ("succeeded", "failed"):
        break
    time.sleep(10)
print(d["status"], d.get("code_findings"))

Tips

  • If you retry after a timeout, reuse the same Idempotency-Key. For a new action, make a new key.
  • Follow links.next in lists.
  • Handle 429 by waiting, and 409 by reading the state, not by retrying blindly.
  • Set timeouts on every call.