Projects

Create, list, update, disable, and delete projects and their guardrails.

A project groups API keys and carries the guardrails every key in it inherits: a model allowlist, a requests-per-minute limit, a spend limit and an IP allowlist. Guardrails are project-level — a key is never wider than its project.

EndpointScopeToken owner's role
GET /projectsprojects:readAny member (members see their own projects)
POST /projectsprojects:writeAdmin
GET /projects/{id}projects:readAny member with access to the project
PATCH /projects/{id}projects:writeAdmin
POST /projects/{id}/disableprojects:writeAdmin
POST /projects/{id}/enableprojects:writeAdmin
DELETE /projects/{id}projects:writeAdmin

projects:write is a sensitive scope. Every write needs an owner who is currently an organization Admin; a Member's token gets 403 insufficient_scope.

The Python examples on this page share this setup:

import os
import uuid
import requests

BASE = "https://portal.<domain>/api/v1/mgmt"
s = requests.Session()
s.headers["Authorization"] = f"Bearer {os.environ['TF_PAT']}"

def idem():
    return {"Idempotency-Key": str(uuid.uuid4())}

The project object

FieldTypeNotes
idstringOpaque id.
namestringDisplay name.
slugstringURL-safe name, derived from the name at creation unless you pass one.
descriptionstring | null
statusstringactive or disabled.
is_defaultbooleantrue for the project created with the organization.
created_at, updated_atstringISO-8601 UTC.
monthly_spend_usdnumberSpend over the trailing 30 days, in USD.
active_key_countintegerOn list rows.
guardrailsobjectThe project's guardrails (below). Present on single-project responses (get, create, update, disable, enable); list rows omit it.
guardrails fieldTypeNotes
model_allowliststring[]Models keys in this project may call (provider/model). Empty = every model your plan allows. Ids come from GET /catalog/models.
rpm_limitinteger | nullRequests per minute for the project; null = no project limit.
spend_limit_usdnumber | nullSpend cap for the project in USD; null = no project cap. Cannot exceed the organization's spend_ceiling.
ip_allowliststring[]CIDR ranges keys may be used from. Empty = any address.
{
  "id": "5b3e9a41-0c7d-4e2f-8a16-9d4b7c2e1f08",
  "name": "payments",
  "slug": "payments",
  "description": "Payments service inference",
  "status": "active",
  "is_default": false,
  "created_at": "2026-09-20T08:14:03Z",
  "updated_at": "2026-09-22T11:30:55Z",
  "monthly_spend_usd": 412.37,
  "guardrails": {
    "model_allowlist": ["openai/gpt-4o", "openai/gpt-4o-mini"],
    "rpm_limit": 1200,
    "spend_limit_usd": 2000,
    "ip_allowlist": ["203.0.113.0/24"]
  }
}

List projects

GET /api/v1/mgmt/projects

Returns the projects the token owner can see, newest first: every project for an Admin, the projects they belong to for a Member. Paginated with limit and cursor.

Query parameterNotes
statusactive or disabled. Any other value → 422 validation_failed.
curl -G https://portal.<domain>/api/v1/mgmt/projects \
  -H "Authorization: Bearer $TF_PAT" \
  -d limit=50
{
  "data": [
    {
      "id": "5b3e9a41-0c7d-4e2f-8a16-9d4b7c2e1f08",
      "name": "payments",
      "slug": "payments",
      "description": "Payments service inference",
      "status": "active",
      "is_default": false,
      "created_at": "2026-09-20T08:14:03Z",
      "updated_at": "2026-09-22T11:30:55Z",
      "monthly_spend_usd": 412.37,
      "active_key_count": 2
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Create a project

POST /api/v1/mgmt/projects
Body fieldTypeRequiredNotes
namestringyesDisplay name.
slugstringnoDerived from name when omitted.
descriptionstringno
region_idstringnoAn inactive or unknown region → 400, param: "region_id".
guardrailsobjectnoAny of model_allowlist, rpm_limit, spend_limit_usd. ip_allowlist cannot be set at creation — set it with PATCH afterwards.

Requires Idempotency-Key. Returns 201 with the project object, including its guardrails.

curl -X POST https://portal.<domain>/api/v1/mgmt/projects \
  -H "Authorization: Bearer $TF_PAT" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1e2d3c-4b5a-4968-8776-5a4b3c2d1e0f" \
  -d '{
    "name": "payments",
    "description": "Payments service inference",
    "guardrails": {
      "model_allowlist": ["openai/gpt-4o", "openai/gpt-4o-mini"],
      "spend_limit_usd": 2000
    }
  }'

Tip

Validate model ids against GET /catalog/models before you write an allowlist. An allowlist naming a model the platform does not know is rejected with 400 invalid_request, param: "guardrails.model_allowlist".

Retrieve a project

GET /api/v1/mgmt/projects/{id}

Returns the project with its guardrails. A project in another organization — or, for a Member, a project they do not belong to — returns 404, exactly like one that does not exist.

curl https://portal.<domain>/api/v1/mgmt/projects/5b3e9a41-0c7d-4e2f-8a16-9d4b7c2e1f08 \
  -H "Authorization: Bearer $TF_PAT"

Update a project

PATCH /api/v1/mgmt/projects/{id}

Changes the name, description and guardrails. Send at least one field. Only the fields you send are changed; inside guardrails, each field you send replaces the current value (send the full allowlist, not a delta) and null clears it.

Body fieldTypeNotes
namestringCannot be empty. The default project cannot be renamed (409 default_project_protected).
descriptionstring | null
guardrails.model_allowliststring[] | null[] or null clears the allowlist.
guardrails.rpm_limitinteger | nullNot negative. null removes the project limit.
guardrails.spend_limit_usdnumber | nullNot negative. null removes the project cap. Must not exceed the org spend_ceiling.
guardrails.ip_allowliststring[] | nullCIDR notation. [] or null clears it.

State is not part of PATCH: use the disable / enable verbs. One request, one intent. Returns 200 with the project object. Guardrail changes take effect within 60 seconds.

curl -X PATCH https://portal.<domain>/api/v1/mgmt/projects/5b3e9a41-0c7d-4e2f-8a16-9d4b7c2e1f08 \
  -H "Authorization: Bearer $TF_PAT" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 0a9b8c7d-6e5f-4a3b-9c2d-1e0f9a8b7c6d" \
  -d '{"guardrails": {"spend_limit_usd": 2500, "ip_allowlist": ["203.0.113.0/24"]}}'

Disable a project

POST /api/v1/mgmt/projects/{id}/disable

The reversible kill switch. Every key in the project stops working for inference (their calls fail with 403 project_disabled); nothing else is touched, and no key is deleted. This is the fastest way to stop a whole project — see Incident response. No body. Returns 200 with the project object (status: "disabled"). The default project cannot be disabled (409 default_project_protected).

curl -X POST https://portal.<domain>/api/v1/mgmt/projects/5b3e9a41-0c7d-4e2f-8a16-9d4b7c2e1f08/disable \
  -H "Authorization: Bearer $TF_PAT" \
  -H "Idempotency-Key: incident-4711-disable-payments"

Enable a project

POST /api/v1/mgmt/projects/{id}/enable

The inverse of disable. Keys that were active before are active again. No body. Returns 200 with the project object (status: "active").

curl -X POST https://portal.<domain>/api/v1/mgmt/projects/5b3e9a41-0c7d-4e2f-8a16-9d4b7c2e1f08/enable \
  -H "Authorization: Bearer $TF_PAT" \
  -H "Idempotency-Key: incident-4711-enable-payments"

Delete a project

DELETE /api/v1/mgmt/projects/{id}

Permanent and irreversible: the project, its settings and its memberships are deleted, and its keys stop working. The one guard is the default project, which cannot be deleted (409 default_project_protected); any other project can be deleted whatever its state. No body. Returns 204 No Content.

curl -X DELETE https://portal.<domain>/api/v1/mgmt/projects/5b3e9a41-0c7d-4e2f-8a16-9d4b7c2e1f08 \
  -H "Authorization: Bearer $TF_PAT" \
  -H "Idempotency-Key: 1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e"

Warning

If you only need the keys to stop, disable the project instead — it is reversible. Delete is for projects you are sure you will never need again.

Errors specific to projects

StatusCodeWhen
400invalid_request (param set)Missing or empty name; unknown field; negative rpm_limit/spend_limit_usd; a spend limit above the org spend_ceiling; an unknown model in model_allowlist; a bad CIDR; ip_allowlist on create; an invalid region_id.
403insufficient_scopeToken lacks projects:read/projects:write, or (for writes) the owner is not currently an Admin.
404not_foundProject absent, in another organization, or not visible to a Member owner.
409default_project_protectedRenaming, disabling or deleting the default project.
422unsupported_filter · validation_failedAn unknown query parameter, or a status filter value other than active/disabled.