Quickstart

Create a personal access token and make your first Management API call.

This walkthrough takes you from nothing to a rotated project API key in four calls: read your organization, create a project, create a key, and rotate it. Every write carries an Idempotency-Key, as the API requires.

Before you start

  • You are an Admin of your Token Factory organization.
  • You have curl or Python 3 with requests (pip install requests). The Management API is plain HTTPS and JSON, so any HTTP client works.

1. Mint a token in the portal

Open Settings → API Tokens and click Create token to open the create-token dialog. Enter a note such as quickstart, keep the 90-day expiration, and under Select scopes tick org:read, the projects parent row (grants projects:read and projects:write — sensitive, the dialog shows a warning), the keys parent row (grants keys:read and keys:write), and catalog:read. Click Generate token, re-authenticate if the portal asks, and copy the secret from the "Token created" step — it is shown once. Full walkthrough: Personal access tokens.

export TF_PAT="tf-pat_…"
export TF_MGMT="https://portal.<domain>/api/v1/mgmt"

The cURL steps below use jq to carry ids from one step to the next.

2. Read your organization

The first call proves the token works. It needs org:read.

curl -i "$TF_MGMT/org" \
  -H "Authorization: Bearer $TF_PAT"
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Request-Id: req_6f1c2d9e8a4b4c3d9e2f1a2b3c4d5e6f
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 99
X-RateLimit-Reset: 1790236810
{
  "id": "3f9a7c21-5d4e-4b8a-9c0f-1e2d3a4b5c6d",
  "name": "Acme Corp",
  "slug": "acme",
  "is_active": true,
  "billing_mode": "prepaid",
  "region": "default",
  "created_at": "2026-03-02T09:41:17Z"
}

Every response carries X-Request-Id; once the token is verified, it also carries the X-RateLimit-* headers (Rate limits). A 401 here means the token is wrong, expired or revoked — the error.code says which (Authentication).

3. Create a project

Every POST, PATCH, PUT and DELETE needs an Idempotency-Key.

PROJECT_ID=$(curl -fsS -X POST "$TF_MGMT/projects" \
  -H "Authorization: Bearer $TF_PAT" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: quickstart-create-project-1" \
  -d '{"name": "quickstart", "description": "Created from the Management API quickstart"}' \
  | jq -r .id)
echo "$PROJECT_ID"
{
  "id": "5b3e9a41-0c7d-4e2f-8a16-9d4b7c2e1f08",
  "name": "quickstart",
  "slug": "quickstart",
  "description": "Created from the Management API quickstart",
  "status": "active",
  "is_default": false,
  "created_at": "2026-09-24T10:02:11Z",
  "updated_at": "2026-09-24T10:02:11Z",
  "monthly_spend_usd": 0,
  "guardrails": { "model_allowlist": [], "rpm_limit": null, "spend_limit_usd": null, "ip_allowlist": [] }
}

Run the same command twice with the same Idempotency-Key: the second response is the stored 201 with the same id, carries Idempotent-Replayed: true, and no second project is created. That is idempotency.

4. Create an API key

Create a project API key in the project. The response is the only time the secret is returned — store it now.

KEY_JSON=$(curl -fsS -X POST "$TF_MGMT/projects/$PROJECT_ID/keys" \
  -H "Authorization: Bearer $TF_PAT" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: quickstart-create-key-1" \
  -d '{"name": "quickstart-app"}')
KEY_ID=$(echo "$KEY_JSON" | jq -r .id)
export TF_API_KEY=$(echo "$KEY_JSON" | jq -r .secret)   # store it in your secret manager
{
  "id": "c41d8e2a-7b90-4f3c-a6e5-2d1f0b9c8a74",
  "project_id": "5b3e9a41-0c7d-4e2f-8a16-9d4b7c2e1f08",
  "name": "quickstart-app",
  "description": null,
  "key_prefix": "tf-4b8a9c-7d21e0-Qm8...k2Zw",
  "status": "active",
  "created_at": "2026-09-24T10:02:40Z",
  "last_used_at": null,
  "expires_at": null,
  "revoked_at": null,
  "requests_7d": 0,
  "allowed_models": [],
  "rpm_limit": null,
  "spend_cap": null,
  "ip_allowlist": [],
  "rotation": null,
  "created_by": { "id": "8e7d6c5b-4a39-4281-9f0e-1d2c3b4a5968", "email": "[email protected]" },
  "secret": "tf-4b8a9c-7d21e0-Qm8xR2v…(full value, shown once)"
}

The key works on the Inference API at https://api.<domain>/v1 straight away — it cannot call the Management API, and your personal access token cannot call the Inference API.

5. Rotate the key

Rotation issues a new secret while the old one keeps working for a grace window you choose (1–168 hours, default 24), so you can deploy without downtime.

ROTATED=$(curl -fsS -X POST "$TF_MGMT/keys/$KEY_ID/rotate" \
  -H "Authorization: Bearer $TF_PAT" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: quickstart-rotate-key-1" \
  -d '{"grace_hours": 1}')
export TF_API_KEY=$(echo "$ROTATED" | jq -r .secret)
echo "old secret stops at $(echo "$ROTATED" | jq -r .rotation.grace_expires_at)"

Deploy the new secret before rotation.grace_expires_at. If you change your mind, POST /keys/{id}/rotate/cancel discards the new secret and keeps the original.

Clean up

Delete the project — this is permanent, and its keys stop working:

curl -fsS -X DELETE "$TF_MGMT/projects/$PROJECT_ID" \
  -H "Authorization: Bearer $TF_PAT" \
  -H "Idempotency-Key: quickstart-delete-project-1" \
  -o /dev/null -w '%{http_code}\n'   # 204

Then revoke the quickstart token in Settings → API Tokens, or keep it for the next pages.

Next steps

To explore every other endpoint, load the OpenAPI specification into Swagger Editor, Postman or Insomnia.

GoalPage
Understand what a token can reachScopes
Automate key rotation in CICI pipeline
Contain a leaked keyIncident response
Pull spend into FinOps toolingUsage