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
curlor Python 3 withrequests(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' # 204Then 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.
| Goal | Page |
|---|---|
| Understand what a token can reach | Scopes |
| Automate key rotation in CI | CI pipeline |
| Contain a leaked key | Incident response |
| Pull spend into FinOps tooling | Usage |