TemprTempr
Docs
Gateway API

Management API

Last updated

Create, change and revoke virtual keys, read your Gateway usage, request logs, plan and provider health, manage an organization's MCP Tool Packs and member groups, and publish the rules and skills its members' agents follow, from your own scripts, CI jobs or infrastructure-as-code, with a management key. It comes with the Gateway Pro, Scale and Enterprise plans.

Management keys

A management key is a separate kind of credential from a virtual key. It can manage virtual keys but can't call models, and a virtual key can't call the management API. Management keys are prefixed tmk_, stored hashed, and shown to you once, when you create them.

Create one in the Portal:

  • For your own account: Gateway → Keys → Management keys. It manages your personal virtual keys.
  • For an organization: Organization → Org keys → Management keys. Only owners and admins can create one, and it manages the organization's shared virtual keys.

Choose what each key can do when you create it:

ScopeAllows
keys:readListing and reading virtual keys.
keys:writeCreating, changing and revoking virtual keys.
usage:readReading usage, cost and latency metrics, the plan, and provider health.
logs:readReading request logs and one request's detail, including the prompt and answer Tempr captured, when you ask for them. Not given by default: tick it when you create the key.
tool_packs:readReading an organization's Tool Packs, member groups, Tool Packs settings, and tool access requests and grants. Organization keys only.
tool_packs:writeChanging which MCP tools each member and key can use: creating, changing and deleting Tool Packs and member groups, giving packs to groups, members and keys, changing the settings, and approving, declining and revoking tool access. Organization keys only.
agent_config:readReading an organization's rules and skills, their content, and who gets them, and its agent settings. Organization keys only.
agent_config:writeChanging the rules and skills every member's agent follows: adding, changing, ordering and deleting rules and skills, and giving them to groups and members; and changing its agent settings. Organization keys only.

A management key can also have an expiry date and an IP allowlist, set the same way as a virtual key's (see what a virtual key scopes). An account can have up to 10 active management keys. Management keys are only made in the Portal; the API can't create another one.

For an organization:

  • Every change a management key makes is recorded in the audit log (Organization → Compliance) under that management key's label.
  • When a member is removed, or an owner or admin is made a member, the management keys they created are revoked. Virtual keys those management keys created keep working.

Making requests

The base URL is https://api.temprhq.io/v1/management. Send the management key as a bearer token:

curl https://api.temprhq.io/v1/management/keys \
  -H "Authorization: Bearer tmk_xxxxxxxxxxxxxxxxxxxxxxxx"

Request and response bodies are JSON with snake_case field names. Response timestamps are Unix seconds. expires_at in a request is an ISO 8601 date and time, for example 2027-01-31T00:00:00Z.

Each management key can make 300 requests a minute. Over that, the API answers 429 until the next minute starts.

List keys

GET /v1/management/keys (scope keys:read) returns the account's virtual keys, newest first. The Portal's Playground key and the keys Tempr creates for team members' IDE sessions aren't included.

Query parameterMeaning
limitKeys per page, 1 to 100. Default 20.
afterA key id from the previous page (its last_id); the page starts after it.
include_revokedtrue to include revoked keys. By default only active and expired keys are listed.
{
  "object": "list",
  "data": [ { "id": "6f1c2b0e-…", "object": "virtual_key", "label": "ci", … } ],
  "first_id": "6f1c2b0e-…",
  "last_id": "6f1c2b0e-…",
  "has_more": false
}

Create a key

POST /v1/management/keys (scope keys:write) creates a virtual key. label is required; every other field is optional. The response is 201 with the key object, plus the raw key in key. It isn't shown again, so store it now.

curl https://api.temprhq.io/v1/management/keys \
  -H "Authorization: Bearer tmk_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "ci",
    "allowed_models": ["anthropic/claude-sonnet-4-5"],
    "max_budget_usd": 25,
    "rpm_limit": 60,
    "expires_at": "2027-01-31T00:00:00Z"
  }'
{
  "id": "6f1c2b0e-…",
  "object": "virtual_key",
  "label": "ci",
  "masked_key": "tvk_...a1b2",
  "status": "active",
  "created_at": 1790000000,
  "expires_at": 1801353600,
  "last_used_at": null,
  "revoked_at": null,
  "allowed_models": ["anthropic/claude-sonnet-4-5"],
  "allowed_mcp_servers": null,
  "fallback_chains": null,
  "max_budget_usd": 25,
  "hold_worst_case": false,
  "rpm_limit": 60,
  "tpm_limit": null,
  "guardrail_mode": null,
  "response_guardrail_mode": null,
  "llm_guardrail_enabled": null,
  "mcp_argument_mode": null,
  "mcp_result_mode": null,
  "mcp_injection_mode": null,
  "prompt_caching_enabled": null,
  "content_capture_enabled": null,
  "caller_credentials_enabled": null,
  "when_used_up_model": null,
  "default_model": null,
  "force_default_model": false,
  "allowed_ips": [],
  "end_user_limits": { "monthly_budget_usd": null, "rpm_limit": null },
  "budget_rules": [{ "period": "day", "meter": "cost", "limit": 20, "action": "block" }],
  "reasoning_policy": null,
  "key": "tvk_xxxxxxxxxxxxxxxxxxxxxxxx"
}

A personal account can have up to 3 active virtual keys; an organization has no such limit.

Get a key

GET /v1/management/keys/{id} (scope keys:read) returns one key. status is active, expired or revoked.

Change a key

PATCH /v1/management/keys/{id} (scope keys:write) changes the fields you send and leaves the rest as they are. Send a field as null to clear it, which removes that limit or goes back to the account default. label can be changed but not cleared. A revoked key can't be changed.

curl -X PATCH https://api.temprhq.io/v1/management/keys/6f1c2b0e-… \
  -H "Authorization: Bearer tmk_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "rpm_limit": 120, "max_budget_usd": null }'

Revoke a key

POST /v1/management/keys/{id}/revoke (scope keys:write) revokes a key and returns it. Requests with the key stop authenticating at once. Revoking a key that's already revoked changes nothing.

Key fields

FieldTypeMeaning
labelstringA name for the key, up to 100 characters.
allowed_modelsarray of stringsThe provider/model values the key may call. Empty or null allows any model.
allowed_mcp_serversarray of stringsThe MCP server slugs the key may reach through the MCP proxy. Empty or null allows any.
fallback_chainsobjectAliases mapped to their chains of 2 to 5 candidates. A chain is either a list, tried in the order written, for example {"smart": ["anthropic/claude-sonnet-4-5", "openai/gpt-4o"]}, or an object with an order of configured, cheapest, fastest or balanced and its models, for example {"fast": {"order": "fastest", "models": ["groq/llama-3.3-70b-versatile", "openai/gpt-4o-mini"]}}. Responses return a configured chain as a list, as before orders existed, and a cheapest, fastest or balanced one as the object. See fallback chains.
max_budget_usdnumberThe most the key may spend, in US dollars.
hold_worst_casebooleanMakes the budget strict: each request's worst case is set aside while it runs, so requests running at once can't spend past it together. Adds about 5 milliseconds per request. False unless set; null sets it back to false. See strict budgets.
rpm_limit, tpm_limitintegerRequests and tokens per minute.
guardrail_modestringThe pre-request guardrail: off, flag_only, redact_secrets (tokens, API keys and private keys in the prompt are replaced with placeholders before the model sees them), redact_personal (secrets, and personal data such as email addresses, phone and card numbers) or block. Null uses the account's setting.
response_guardrail_modestringAnswer scanning: off, flag_only, redact_secrets or redact_personal (never block). Null uses the account's setting.
budget_rulesarrayUp to 5 budget rules, each { "period": "hour|day|week|month", "meter": "cost|tokens|requests", "limit": number, "action": "warn|block" }. At most one warn and one block rule per period and meter; token and request limits are whole numbers. null or [] removes them all.
llm_guardrail_enabledbooleanWhether the LLM guardrail check runs. Null uses the account's setting.
mcp_argument_modestringWhat the MCP guardrails do with secrets and personal data in tool-call arguments: off, flag_only or block. Null uses the organization's or account's MCP setting.
mcp_result_modestringThe same for tool results: off, flag, redact or block, which withholds the whole result (the Portal calls it Withhold, and withhold is accepted too). Null uses the organization's or account's MCP setting.
mcp_injection_modestringWhat they do with planted instructions in tool results and descriptions: off, flag_only or block. Null uses the organization's or account's MCP setting.
prompt_caching_enabledbooleanWhether the Gateway adds prompt-cache breakpoints. Null uses the account's setting.
content_capture_enabledbooleanWhether request and response bodies are kept in the logs. Null uses the account's setting.
caller_credentials_enabledbooleanWhether the key accepts a Claude or ChatGPT plan login, or a provider key, sent with the request beside the virtual key in x-tempr-key (see your own credentials). Null uses the account's setting: on for a personal account, off for an organization until an owner or admin turns it on.
routing_policyobjectThe key's default host preferences for its pools and fallback chains, in a request's provider shape: only, ignore, order, sort, max_price, allow_fallbacks, first_byte_timeout_ms and output_floor_tokens, for example {"only": ["groq", "cerebras"], "sort": "price"}. Provider names come back as Tempr's. A field it doesn't support is refused. A request's own provider object can narrow it but not widen it. Null clears it.
routing_rulesarrayThe key's routing rules, first match wins, each {"when": {...}, "target": "..."}. Conditions in when: model (a trailing * matches a prefix), metadata and headers (objects of names to values), end_user, team, has_tools, has_images, min_prompt_tokens and max_prompt_tokens; the target is a model, an alias or a pool. For example [{"when": {"model": "fast*", "has_tools": true}, "target": "any/openai/gpt-oss-120b"}]. Up to 20. Pro and up: on Free, setting rules answers plan_required, while clearing them, or sending the saved ones unchanged, works. Null clears them.
when_used_up_modelstringWhat the key's requests go to once a plan paying for them is used up, or its dollar budget is: a provider/model id or one of its fallback aliases. A plan-paid request tries the plan first and hands over when it refuses; a request refused for budget goes there instead, which the budget lets through only when a plan pays. Null: the request stops with the plan's or budget's error.
default_modelstringThe model a request on the key uses when it names none: a provider/model id or one of its fallback aliases. Null: a request must name its model.
force_default_modelbooleanWhen true, default_model replaces whatever model a request names. Needs a default_model. Default false.
group_idstring or nullThe member group the key belongs to as a team, for an organization's key: the team's budget covers the key's spend, its model list and budget rules apply, and the settings it locks replace the key's own. Null: no team.
expires_atdate and timeWhen the key stops working, at most five years ahead.
allowed_ipsarray of stringsAddresses or CIDR ranges the key may be used from, up to 50. Empty or null allows any address.
end_user_limitsobjectDefaults for each of the key's end users: monthly_budget_usd (number) and rpm_limit (integer), null or 0 for none, for example {"monthly_budget_usd": 5, "rpm_limit": 20}. On PATCH, a field left out of the object stays as it is, and "end_user_limits": null clears both. Set one end user's own limits with PUT …/end-users/{end_user}/limits.
reasoning_policyobjectThe key's reasoning policy: {"default": "medium", "min": "low", "max": "high"}, each a level (none, minimal, low, medium, high, xhigh, max) or null. default is used when a request names no reasoning; a request below min or above max is moved to the nearest allowed level. The object replaces the whole policy, so a level left out of it is cleared; null clears them all. The default must lie within the range. Responses return null when the key has no policy.

The model and MCP server lists take up to 200 entries each. A field the API doesn't know is an error rather than being ignored, so a typo doesn't pass unnoticed.

End users

An app names its own customers, its end users, with the x-tempr-end-user header or the request's end-user field. These endpoints show each end user of a key and set limits for one of them, overriding the key's end_user_limits. End users aren't created: they appear as requests name them, and an id you set limits for before it's been seen is kept for when it is. Put the end-user id in the path percent-encoded, up to 256 characters.

  • GET /v1/management/keys/{id}/end-users (scope keys:read) lists the end users seen on the key this calendar month (UTC), and any with limits of their own, highest spend first, as { "object": "list", "data": [ … ], "has_more": false }. It pages like the key list, with limit and after (an end-user id).
  • GET /v1/management/keys/{id}/end-users/{end_user}/limits (scope keys:read) returns one end user, seen or not.
  • PUT /v1/management/keys/{id}/end-users/{end_user}/limits (scope keys:write) sets the end user's own limits: monthly_budget_usd, rpm_limit and blocked. It replaces what was set before; a limit left out or null uses the key's default, and 0 means no limit for this end user. Sending none of them, and blocked false, is the same as DELETE.
  • DELETE /v1/management/keys/{id}/end-users/{end_user}/limits (scope keys:write) drops the end user's own limits, so the key's defaults apply again, and returns the end user.
curl -X PUT https://api.temprhq.io/v1/management/keys/6f1c2b0e-…/end-users/customer-4821/limits   -H "Authorization: Bearer tmk_xxxxxxxxxxxxxxxxxxxxxxxx"   -H "Content-Type: application/json"   -d '{ "monthly_budget_usd": 20, "rpm_limit": 60, "blocked": false }'
{
  "id": "customer-4821",
  "object": "end_user",
  "key_id": "6f1c2b0e-…",
  "spend_this_month_usd": 3.4125,
  "requests_this_month": 212,
  "last_seen_at": 1791234567,
  "limits": { "monthly_budget_usd": 20, "rpm_limit": 60, "blocked": false },
  "caps": { "monthly_budget_usd": null, "rpm_limit": null },
  "effective_limits": { "monthly_budget_usd": 20, "rpm_limit": 60, "blocked": false },
  "budget_remaining_usd": 16.5875
}

limits is the end user's own, with null where the key's default applies. caps are limits an AI agent set through the Gateway's MCP server: a cap only ever lowers what applies, and a PUT or DELETE here replaces it. effective_limits is what the Gateway enforces, the lower of the end user's limit (or the key's default) and any cap, null for no limit. budget_remaining_usd is null when no budget applies. Spend counts the requests the Gateway priced this month; requests_this_month counts every request, refused ones too. For an organization's key, each change is in the audit log.

Test guardrail settings

POST /v1/management/guardrails/test (scope keys:read) runs the guardrail tester's checks on a text, so a CI job can check that your guardrail settings catch what they should. Nothing is stored or logged, and it doesn't run the LLM check.

FieldTypeMeaning
textstringRequired. The text to check, up to 100,000 characters.
kindstringRequired. request (a request to a model, checked as the gateway scans the raw body), mcp_arguments (a tool call's arguments; JSON is checked compact, as the relay sees it), mcp_result (the text of a tool's result) or mcp_description (a tool's description).
key_idstringUse that key's effective settings: its overrides over the account's or organization's. Without it, the management key owner's saved settings.
guardrail_modestringoff, flag_only, redact_secrets, redact_personal or block, in place of the saved setting, to try one before you set it.
mcp_argument_mode, mcp_injection_modestringoff, flag_only or block, likewise.
mcp_result_modestringoff, flag, redact or block, likewise.
{
  "object": "guardrail_test",
  "verdict": "blocked",
  "summary": "This request would be blocked: it contains an AWS access key.",
  "matches": [
    { "category": "aws-key", "start": 12, "length": 20, "masked": "A••••••••••E" }
  ],
  "categories": ["aws-key"],
  "redacted_text": "Deploy with [REDACTED-AWS-KEY] now.",
  "settings": {
    "guardrail_mode": "block",
    "mcp_argument_mode": "flag_only",
    "mcp_result_mode": "redact",
    "mcp_injection_mode": "flag_only"
  }
}

verdict is allowed, flagged, redacted (a request or tool result the model gets with secrets masked) or blocked (a request or tool call refused, a result withheld, or a tool left out of the list). matches lists every match with its position in the checked text and only its first and last characters showing, including matches a check that's off would make; categories are the ones the verdict counts. For MCP kinds the answer adds model_text, what the model (or, for arguments, the tool) would get, and hidden_characters; a request under a redact mode adds model_text too, the text with the covered categories masked. A key_id that isn't the owner's answers 404 key_not_found.

Usage

GET /v1/management/usage (scope usage:read) totals the account's Gateway requests over a range of days.

Query parameterMeaning
start, endUTC dates (yyyy-MM-dd), both included. The range can be up to 90 days. By default it's the last 30 days, ending today.
group_byday (the default, oldest first), key or model (both by cost, highest first).
{
  "object": "usage",
  "start": "2026-09-01",
  "end": "2026-09-30",
  "group_by": "model",
  "data": [
    {
      "date": null,
      "key_id": null,
      "model": "anthropic/claude-sonnet-4-5",
      "requests": 1204,
      "errors": 3,
      "prompt_tokens": 5120331,
      "completion_tokens": 402118,
      "cost_usd": 21.3714
    }
  ]
}

Each row fills in the field it's grouped by (date, key_id or model) and leaves the other two null. errors counts requests that got a 4xx or 5xx answer. Usage covers the days your plan keeps logs for.

Time ranges

Request logs and metrics take a time range in one of two ways:

Query parameterMeaning
rangeA preset ending now: 1h, 24h, 7d, 30d, or month (since the first of this month, UTC).
start, endBoth UTC instants (2026-10-15T09:00Z) or both days (2026-10-15; an end day is included whole). end defaults to now; send start to use them, and it overrides range.

A range never reaches further back than your plan keeps logs; the answer's range object gives the start and end used, as Unix seconds, and clamped_to_retention: true when the start was moved up. A range that ends before the oldest log kept answers 400 invalid_range.

Request logs

GET /v1/management/logs (scope logs:read) lists requests, newest first, by default over the last 24 hours. A personal management key reads your own traffic; an organization's reads the organization's. Rows carry metadata only: what was sent and answered is in one request's detail.

Query parameterMeaning
range, start, endSee time ranges.
key_idOnly this virtual key's requests.
provider, modelOnly this provider (anthropic) or model (openai/gpt-5-mini).
statussucceeded, failed, or one error type, such as rate_limit_exceeded, upstream_error or guardrail_blocked.
session_id, end_userOnly one session (x-tempr-session-id) or end user.
operationchat, embeddings or mcp.
planWho paid: any plan login, none (the account), coding, claude or chatgpt.
team_idOnly one team's requests. Organization keys only.
metadataA metadata key and value from x-tempr-metadata, written team=search.
min_duration_msOnly requests that took at least this long.
limit, afterUp to 100 rows a page (default 20); after is the last request_id of the previous page.
{
  "object": "list",
  "range": { "preset": "24h", "start": 1792044000, "end": 1792130400 },
  "data": [
    {
      "object": "request_log",
      "request_id": "0f6a3c1e-2b7d-4c55-9a51-3f0e8d2b6c41",
      "created_at": 1792129812,
      "key_id": "5d1f0c2a-8e47-4b1c-a0d2-7e6f9b3c1a20",
      "key_label": "Support bot",
      "operation": "chat",
      "provider": "anthropic",
      "model": "anthropic/claude-sonnet-4-5",
      "http_status": 429,
      "succeeded": false,
      "error_type": "rate_limit_exceeded",
      "error_message": "Rate limit reached for requests",
      "prompt_tokens": 0,
      "completion_tokens": 0,
      "duration_ms": 412,
      "streamed": true,
      "attempts": 2,
      "session_id": "chat-8812"
    }
  ],
  "first_id": "0f6a3c1e-2b7d-4c55-9a51-3f0e8d2b6c41",
  "last_id": "0f6a3c1e-2b7d-4c55-9a51-3f0e8d2b6c41",
  "has_more": true
}

Fields with nothing to say are left out. fell_back is true when a fallback model answered; attempts counts the models and provider keys tried when there was more than one; guardrail_categories lists what a guardrail flagged. cost_usd is the provider cost Tempr worked out for the request.

One request

GET /v1/management/logs/{request_id} (scope logs:read) returns one request, whatever its age within your retention: the row above as request, and its routing (fallback_attempts and key_rotation_attempts, each with provider, model, key label, outcome and status), guardrail outcome, cache savings, the prompt template and version, the plan that paid, and its metadata. The request id is also in every Gateway response's x-tempr-request-id header.

Add include_content=true for the prompt and answer Tempr captured, as content.request_body and content.response_body, with secrets and personal data masked (the same masks as redaction, plus your own data-loss prevention rules). Nothing is captured when content capture is off; content.captured is then false. For an organization, reading content is recorded in the audit log, as it is in the Portal.

Metrics

GET /v1/management/metrics (scope usage:read) gives the numbers on the Portal's Gateway dashboard for a range (default 7d): totals (requests, failures, error rate, cost, cache savings, tokens, median and p95 latency and time to first token), a series over time (hourly up to three days, daily beyond, UTC), and the same by model, provider and key, with guardrail and prompt-cache totals. Narrow it with key_id, provider, model or operation; add compare=true for previous_totals, the same length of time just before.

Plan

GET /v1/management/plan (scope usage:read) gives the Gateway plan and where it stands: plan and status, the billing period (period_start, period_end), requests_used of requests_included and quota_percent, the rpm_limit, overage (mode, requests and estimated cost so far, rate and cap), log_retention_days, whether content_capture and redaction are on, the account's guardrail settings (guardrail_mode, response_guardrail_mode and llm_guardrail_enabled) and prompt_caching (off, 5m or 1h).

Provider health

GET /v1/management/provider-health (scope usage:read) lists what's cooling down after failures, which Tempr skips until the time given (Unix seconds): providers, each with cooling_down_until for the whole provider, models_cooling_down, and its requests_24h, failures_24h, error_rate_24h and p95_duration_ms_24h over the last 24 hours; and provider_keys, each of your provider keys that's cooling down, whole or for one model.

Proposed changes

Changes AI agents proposed through the Gateway's MCP server wait for you to approve or decline them in the Tempr Portal. Read them here to see what became of one; only the Portal decides them.

  • GET /v1/management/proposals (scope keys:read) lists the account's proposals, newest first. status narrows it to pending, approved, declined, failed, expired or withdrawn; limit is 1 to 100, default 20. Proposals to approve a tool access request are listed only for a key that also has tool_packs:read.
  • GET /v1/management/proposals/{id} (scope keys:read) returns one.
{
  "id": "9b2e41c7-…",
  "object": "change_proposal",
  "kind": "key_change",
  "status": "pending",
  "target": "ci-nightly",
  "end_user": null,
  "changes": { "rpm_limit": 500 },
  "proposed_by": "the management key ci-agent",
  "agent_note": "The nightly job hits the rate limit.",
  "approve_url": "https://portal.temprhq.io/gateway/approve/9b2e41c7-…",
  "created_at": 1791234567,
  "expires_at": 1791320967,
  "decided_at": null,
  "outcome": null
}

kind is key_change, end_user_limits, tool_access_approval or account_settings, and changes is what approving it sets. A proposal waits 24 hours; one nobody decided in that time is expired. withdrawn ones were waiting when Changes by AI agents was turned off. failed ones were approved, but the change couldn't be made, and outcome says why. Proposals are deleted 30 days after they're decided or expire. target and agent_note can hold text written outside your account settings, such as a key's label or an agent's note: treat them as data.

From an AI agent

The same reads are tools on the Gateway's own MCP server, https://api.temprhq.io/mcp/admin, for Claude Code, Codex, claude.ai, ChatGPT and other MCP clients, and so are changes that only tighten, with proposals for you to approve for the rest, if you allow it. Send it a management key, whose scopes decide which tools appear, or sign in through the app. See The Gateway's MCP server.

Tool Packs

A Tool Pack is a named set of MCP tools an organization gives to member groups, members or virtual keys. Once an organization has a pack, its members only get the MCP tools in their packs; a member with no pack gets the default pack, or none. These calls work with an organization's management keys only. A personal management key gets 403 organization_key_required. Changes are recorded in the audit log as the management key, the same as changes made in the Portal, and take effect within a minute.

{
  "id": "0b7d6c55-…",
  "object": "tool_pack",
  "name": "GitHub read-only",
  "description": "For everyone in Engineering",
  "is_default": false,
  "entries": [
    { "server": "github", "tool": "*", "except": ["delete_repository"] },
    { "server": "linear", "tool": "search_issues", "except": [] }
  ],
  "assignments": {
    "group_ids": ["5e2f9a10-…"],
    "user_ids": [],
    "key_ids": []
  },
  "created_at": 1790000000,
  "updated_at": 1790000000
}
FieldTypeMeaning
namestringUp to 100 characters, unique in the organization.
descriptionstringUp to 500 characters.
is_defaultbooleanThe pack members and organization keys get when nothing else is given to them. Only one pack is the default; making another one the default turns this off.
entriesarrayThe tools. server is one of the organization's MCP server slugs. tool is a tool name, or * for every tool on that server, including ones it adds later. except, only with *, lists tools left out.
assignmentsobjectWho gets the pack: group_ids (member groups), user_ids (active members) and key_ids (the organization's virtual keys).

Someone in several groups, or with packs of their own, gets every tool in all of them.

List and get packs

GET /v1/management/tool-packs (scope tool_packs:read) returns every pack, by name, as { "object": "list", "data": [ … ] }. GET /v1/management/tool-packs/{id} returns one.

Create a pack

POST /v1/management/tool-packs (scope tool_packs:write) creates a pack and answers 201 with it. name is required; the other fields default to none and false.

curl https://api.temprhq.io/v1/management/tool-packs \
  -H "Authorization: Bearer tmk_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "GitHub read-only",
    "entries": [ { "server": "github", "tool": "*", "except": ["delete_repository"] } ],
    "assignments": { "group_ids": ["5e2f9a10-…"] }
  }'

Change or delete a pack

PATCH /v1/management/tool-packs/{id} (scope tool_packs:write) changes the fields you send and leaves the rest. entries replaces all of the pack's tools. In assignments, each list you send replaces that list, and a list you leave out stays as it is. description can be cleared with null; name can't.

DELETE /v1/management/tool-packs/{id} (scope tool_packs:write) deletes a pack, and everyone loses the tools it gave them. It answers { "id": "…", "object": "tool_pack", "deleted": true }.

Give or take away a pack

POST /v1/management/tool-packs/{id}/assignments (scope tool_packs:write) gives the pack to more groups, members or keys, and DELETE /v1/management/tool-packs/{id}/assignments takes it away. Both take a body with any of group_ids, user_ids and key_ids, change only the ids you send, and answer with the pack.

curl https://api.temprhq.io/v1/management/tool-packs/0b7d6c55-…/assignments \
  -H "Authorization: Bearer tmk_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "user_ids": ["c41e7a2b-…"] }'

Tool Packs settings

GET /v1/management/tool-packs/settings (scope tool_packs:read) and PATCH /v1/management/tool-packs/settings (scope tool_packs:write) read and change the organization's Tool Packs settings. only_managed_servers, when true, also refuses MCP servers the organization doesn't manage while it uses Tool Packs; by default they're allowed. uses_tool_packs says whether the organization has any pack, and can't be set.

{
  "object": "tool_pack_settings",
  "only_managed_servers": true,
  "uses_tool_packs": true
}

Member groups

A member group is a set of an organization's members, for giving them Tool Packs, and a monthly budget and models of their own. These calls also need an organization's management key.

{
  "id": "5e2f9a10-…",
  "object": "member_group",
  "name": "Engineering",
  "description": "",
  "source": "portal",
  "member_user_ids": ["c41e7a2b-…", "f0a93d18-…"],
  "monthly_budget_usd": 200,
  "budget_enforcement": "hard_cap",
  "allowed_models": ["openai/gpt-4o-mini", "anthropic/*"],
  "spend_this_month_usd": 41.27,
  "budget_rules": [{ "period": "week", "meter": "cost", "limit": 500, "action": "block" }],
  "key_policy": { "guardrail_mode": { "value": "redact_secrets", "locked": true }, "max_budget_usd": { "value": 100, "locked": false } },
  "created_at": 1790000000,
  "updated_at": 1790000000
}

source is portal for a group made in the Portal or with this API, or directory for one mirrored from your company directory through directory sync. A directory group can be read, given packs and given a budget and models, but renaming, deleting or setting the members of one answers 409 directory_group_read_only: change it in the directory instead.

FieldTypeDescription
monthly_budget_usdnumber or nullThe group's monthly budget in US dollars, more than 0 and at most 1,000,000. Null: no budget.
budget_enforcementstringsoft_alert (the default: owners and admins are emailed at 80% and 100%, once a month each) or hard_cap (members' requests are refused with 402 group_budget_exceeded once it's spent).
allowed_modelsarray of strings or nullThe models the group's members may use: provider/model ids, or provider/* for all of a provider's models, up to 200. Null or empty: every model.
spend_this_month_usdnumberRead-only. The group's spend this calendar month (UTC): its members' in the organization, and its team keys'.
budget_rulesarray or nullBudget rules for the group's team keys, counted together, in the key field's shape. Up to 5; null or empty for none.
key_policyobject or nullSettings for the group's team keys. Each is { "value": …, "locked": true|false }: a new team key starts with an unlocked one when it sets none of its own, and a locked one replaces every team key's own on each request and can't be changed on a key (400 field_locked). The settings: max_budget_usd, rpm_limit, tpm_limit, guardrail_mode, response_guardrail_mode, llm_guardrail_enabled, content_capture_enabled, prompt_caching_enabled, caller_credentials_enabled, default_model (its value { "model": "…", "force": true|false }), when_used_up_model and allowed_ips. Sending it replaces the whole policy; null clears it.

A member in several groups may use the models of all of them, unless one of their groups has no list, and never more than their own allowed models. Every budget on the way applies: theirs, each hard-capped group's and the organization's. See Group budgets and models.

  • GET /v1/management/groups (scope tool_packs:read) lists the groups, by name, and GET /v1/management/groups/{id} returns one.
  • POST /v1/management/groups (scope tool_packs:write) creates a group from name (required, up to 100 characters, unique), description, member_user_ids, monthly_budget_usd, budget_enforcement, allowed_models, budget_rules and key_policy, and answers 201 with it.
  • PATCH /v1/management/groups/{id} (scope tool_packs:write) changes name, description, monthly_budget_usd, budget_enforcement, allowed_models, budget_rules or key_policy, and leaves the fields you don't send. A directory group takes all but the first two.
  • PUT /v1/management/groups/{id}/members (scope tool_packs:write) sets the members to exactly the user_ids you send, each an active member of the organization: { "user_ids": ["c41e7a2b-…"] }.
  • DELETE /v1/management/groups/{id} (scope tool_packs:write) deletes a group, and the packs given to it no longer reach its members through it. A group with active team keys answers 409 group_has_keys.

Tool access requests

When a member's agent calls a tool their Tool Packs leave out, the refusal links to a Portal page where they can ask for it, with a reason. Owners and admins decide in the Portal (Tool Packs, then Requests), or with these calls. An approval is a grant for that member and tool, beside their packs: it lasts 1 hour, 1 day, 1 week or with no limit, and can be revoked. The member is emailed either way, and every step is in the audit log, as the management key for these calls. These calls need an organization's management key.

{
  "id": "7c1f4e22-…",
  "object": "tool_access_request",
  "user_id": "c41e7a2b-…",
  "user_email": "dana@example.com",
  "server": "github",
  "tool": "create_pull_request",
  "reason": "Opening the release PR for the 2.4 branch.",
  "status": "approved",
  "duration": "1d",
  "decided_at": 1790003600,
  "decision_note": null,
  "expires_at": 1790090000,
  "created_at": 1790000000,
  "updated_at": 1790003600
}

status is pending, approved, declined, expired (an approval that ran out) or revoked. duration is 1h, 1d, 1w or none once approved, and null while pending or when declined. A member has at most one pending request per tool; asking again changes its reason.

  • GET /v1/management/tool-access-requests (scope tool_packs:read) lists the requests, newest first. ?status=pending (or any other status) narrows them.
  • POST /v1/management/tool-access-requests/{id}/approve (scope tool_packs:write) approves a pending request for duration, which is required: { "duration": "1h" }, "1d", "1w" or "none" for no limit. It answers with the request; the tool works from the member's next call.
  • POST /v1/management/tool-access-requests/{id}/decline (scope tool_packs:write) declines a pending request, with an optional note for the member of up to 500 characters: { "note": "Use the read-only pack for this." }, or {}. It answers with the request.

Approving or declining a request that isn't pending answers 409 tool_access_request_decided.

Grants

{
  "id": "e93b0a41-…",
  "object": "tool_access_grant",
  "user_id": "c41e7a2b-…",
  "user_email": "dana@example.com",
  "server": "github",
  "tool": "create_pull_request",
  "status": "active",
  "expires_at": 1790090000,
  "request_id": "7c1f4e22-…",
  "created_at": 1790003600,
  "revoked_at": null
}
  • GET /v1/management/tool-access-grants (scope tool_packs:read) lists the grants in effect, newest first. ?include_ended=true adds the expired and revoked ones; status is active, expired or revoked.
  • DELETE /v1/management/tool-access-grants/{id} (scope tool_packs:write) revokes a grant, and answers with it. The tool is refused from the member's next call. Revoking a grant that already ended changes nothing.

Org rules and skills

An organization's rules are instructions every agent it reaches follows, added to each model call in order. Its skills are packages in the open Agent Skills format: a SKILL.md with a name and a description, plus optional text files, which an agent reads when a task calls for it. Each one is required, which a member can't switch off, or recommended. Each one goes to everyone in the organization, or only to the member groups and members it's given to.

These calls are how an organization keeps its skills in a git repository and publishes them from CI: a job that runs on every merge sends each skill folder as a .zip, or its SKILL.md and files as JSON. They work with an organization's management keys only; a personal management key gets 403 organization_key_required. Every change is kept as a version an owner or admin can restore in the Portal, is recorded in the audit log as the management key, and reaches members' agents within a minute.

Rules

{
  "id": "9a4e1c70-…",
  "object": "org_rule",
  "title": "Spelling",
  "body": "Always write in British English.",
  "required": true,
  "position": 0,
  "assignments": {
    "group_ids": [],
    "user_ids": []
  },
  "created_at": 1790000000,
  "updated_at": 1790000000
}
FieldTypeMeaning
titlestringUp to 120 characters. Shown in the Portal and to members; not sent to the model.
bodystringThe rule, in Markdown, sent to the model as written. Up to 16 KB; an organization's rules can be up to 128 KB together, and it can have up to 100.
requiredbooleantrue: members can't switch it off, and it wins over a project's own instructions. false (the default): recommended, so members can switch it off and a project's instructions win.
positionintegerIts place in the order the rules are given to the model, from 0. Read only; set the order with PUT …/rules/order.
group_ids, user_idsarray of idsIn a request: who gets the rule, member groups and active members. Both empty, or never sent, gives it to everyone. A response shows them under assignments.
  • GET /v1/management/agent-config/rules (scope agent_config:read) returns the rules in the order they're given to the model, as { "object": "list", "data": [ … ] }. GET /v1/management/agent-config/rules/{id} returns one.
  • POST /v1/management/agent-config/rules (scope agent_config:write) adds a rule at the end and answers 201 with it. title and body are required.
  • PATCH /v1/management/agent-config/rules/{id} (scope agent_config:write) changes the fields you send and leaves the rest. group_ids or user_ids, when sent, replaces that list.
  • PUT /v1/management/agent-config/rules/order (scope agent_config:write) puts the rules in the order of ids and answers with the list. Rules you leave out keep their order, after the ones you name: { "ids": ["9a4e1c70-…", "e1b03f42-…"] }.
  • DELETE /v1/management/agent-config/rules/{id} (scope agent_config:write) deletes a rule and answers { "id": "…", "object": "org_rule", "deleted": true }. It can be restored in the Portal.
curl https://api.temprhq.io/v1/management/agent-config/rules \
  -H "Authorization: Bearer tmk_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Spelling", "body": "Always write in British English.", "required": true }'

Skills

A skill is named by the name in its SKILL.md frontmatter: up to 64 lowercase letters, digits and single hyphens, unique in the organization. The calls below take that name in the path.

{
  "id": "3c8f20d1-…",
  "object": "org_skill",
  "name": "release-hotfix",
  "description": "Cut a hotfix release. Use when asked to ship a fix to production outside the release train.",
  "required": false,
  "files": ["references/checklist.md", "scripts/tag.sh"],
  "size_bytes": 6120,
  "content_hash": "4f1a…",
  "assignments": {
    "group_ids": ["5e2f9a10-…"],
    "user_ids": []
  },
  "created_at": 1790000000,
  "updated_at": 1790000000
}
FieldTypeMeaning
skill_mdstringIn a request: the whole SKILL.md, frontmatter included, up to 64 KB. Its name and description (up to 1,024 characters) are checked against the Agent Skills format, and the error says exactly what to fix. Other frontmatter fields, such as Claude Code's allowed-tools, stay in the file and are ignored.
filesarrayIn a request: the skill's other files as { "path": "references/api.md", "content": "…" }, text only, up to 100. In a response: their paths. A skill can be up to 1 MB in all.
requiredbooleantrue: every member it reaches has it, and can't switch it off. false (the default): recommended.
group_ids, user_idsarray of idsIn a request: who gets the skill, as for a rule. A response shows them under assignments.
content_hashstringA SHA-256 of SKILL.md and the files, which changes whenever the content does. Read only.
  • GET /v1/management/agent-config/skills (scope agent_config:read) returns every skill, by name, without its content. GET /v1/management/agent-config/skills/{name} returns one.
  • GET /v1/management/agent-config/skills/{name}/content (scope agent_config:read) returns its content in the shape a request takes it: { "name": "…", "object": "org_skill_content", "skill_md": "…", "content_hash": "…", "files": [ { "path": "…", "content": "…" } ] }.
  • POST /v1/management/agent-config/skills (scope agent_config:write) adds a skill and answers 201 with it. A name that's taken answers 409 skill_exists; change that skill with PATCH instead.
  • PATCH /v1/management/agent-config/skills/{name} (scope agent_config:write) changes the fields you send and leaves the rest. skill_md replaces SKILL.md, and files, when sent, replaces all of the other files; a new name in the frontmatter renames the skill. Sending the same content again adds no version and no audit entry, so a CI job can send every skill on every run.
  • DELETE /v1/management/agent-config/skills/{name} (scope agent_config:write) deletes a skill and answers { "id": "…", "name": "…", "object": "org_skill", "deleted": true }. It can be restored in the Portal.

POST and PATCH take JSON, or multipart/form-data with the skill in a file field: a .zip with SKILL.md at the top or in one folder named after the skill (the layout skills repositories use), or a lone SKILL.md. The upload can be up to 5 MB. required, group_ids and user_ids go in form fields of those names, with ids repeated or comma-separated.

curl https://api.temprhq.io/v1/management/agent-config/skills \
  -H "Authorization: Bearer tmk_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "skill_md": "---\nname: release-hotfix\ndescription: Cut a hotfix release. Use when asked to ship a fix outside the release train.\n---\n\n1. Read references/checklist.md first.\n",
    "files": [ { "path": "references/checklist.md", "content": "- Tests green\n- Changelog entry\n" } ],
    "required": false
  }'

From CI, publishing a skill folder from a repository:

cd skills && zip -r release-hotfix.zip release-hotfix
curl -X PATCH https://api.temprhq.io/v1/management/agent-config/skills/release-hotfix \
  -H "Authorization: Bearer $TEMPR_MANAGEMENT_KEY" \
  -F "file=@release-hotfix.zip"

A 404 skill_not_found from that PATCH means the skill is new: send the same upload with POST /v1/management/agent-config/skills.

Give or take away a rule or skill

POST /v1/management/agent-config/rules/{id}/assignments and POST /v1/management/agent-config/skills/{name}/assignments (scope agent_config:write) give a rule or skill to more groups or members. DELETE /v1/management/agent-config/rules/{id}/assignments and DELETE /v1/management/agent-config/skills/{name}/assignments take it away. Both take a body with group_ids, user_ids or both, change only the ids you send, and answer with the rule or skill. Taking away the last group or member gives it back to everyone. Group ids come from member groups, which a key reads with tool_packs:read.

curl https://api.temprhq.io/v1/management/agent-config/skills/release-hotfix/assignments \
  -H "Authorization: Bearer tmk_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "group_ids": ["5e2f9a10-…"] }'

Agent settings

An organization's settings for its members' Tempr Code and CLI, as on the Portal's Agent playbook page. local_models_allowed says whether they may use local models: Ollama, LM Studio and other OpenAI-compatible servers on a member's own machine. Those turns go to the model directly, so the Gateway's policies and logs don't reach them. It's on by default; turned off, the clients stop offering local models at their next config fetch.

  • GET /v1/management/agent-config/settings (scope agent_config:read) returns { "object": "agent_config_settings", "local_models_allowed": true }.
  • PATCH /v1/management/agent-config/settings (scope agent_config:write) changes the settings you send, and answers with them all. Each change is in the organization's audit log.
curl -X PATCH https://api.temprhq.io/v1/management/agent-config/settings   -H "Authorization: Bearer tmk_xxxxxxxxxxxxxxxxxxxxxxxx"   -H "Content-Type: application/json"   -d '{ "local_models_allowed": false }'

Errors

Errors use the same shape as the rest of the Gateway API, and param names the field at fault when there is one:

{
  "error": {
    "message": "The RPM limit can't be negative.",
    "type": "invalid_request_error",
    "code": "invalid_value",
    "param": "rpm_limit"
  }
}
StatusTypeCodeMeaning
400invalid_request_errorinvalid_bodyThe body isn't a JSON object.
400invalid_request_errorinvalid_valueA field or query parameter is missing, unknown, the wrong type or out of range. message says which.
400invalid_request_errorfield_lockedThe key belongs to a team that locks this setting, so the key can't be given another value. param names the setting; change it on the team (key_policy).
400invalid_request_errorinvalid_cursorafter isn't the id of a key or request in the list.
400invalid_request_errorinvalid_rangerange isn't one of the presets; start and end can't be read, mix an instant with a day, or are the wrong way round; end came without start; or the range ends before the oldest log your plan keeps.
401authentication_errormissing_management_keyNo Authorization: Bearer header.
401authentication_errorinvalid_management_keyThe key doesn't exist or has been revoked. A virtual key gets this too.
401authentication_errormanagement_key_expiredThe management key is past its expiry date.
403permission_errorip_not_allowedThe request came from an address outside the management key's IP allowlist.
403permission_errorplan_requiredThe account isn't on a paid-up Pro, Scale or Enterprise plan.
403permission_errorinsufficient_scopeThe management key doesn't have the scope the call needs.
403permission_errororganization_key_requiredA Tool Packs, member groups, or rules and skills call made with a personal management key. Use one of the organization's.
404invalid_request_errorkey_not_foundNo virtual key with that id belongs to this account.
404invalid_request_errorrequest_not_foundNo request with that id is in the account's logs: it isn't the account's, or it's older than the plan keeps logs.
404invalid_request_errorplan_not_foundThe account has no Gateway plan.
404invalid_request_errortool_pack_not_foundNo Tool Pack with that id belongs to this organization.
404invalid_request_errorgroup_not_foundNo member group with that id belongs to this organization.
404invalid_request_errorrule_not_foundNo rule with that id belongs to this organization.
404invalid_request_errorskill_not_foundNo skill with that name belongs to this organization.
404invalid_request_errortool_access_request_not_foundNo tool access request with that id belongs to this organization.
404invalid_request_errortool_access_grant_not_foundNo tool access grant with that id belongs to this organization.
404invalid_request_errorproposal_not_foundNo proposed change with that id belongs to this account, or it's one to approve tool access and the key can't read tool access.
409invalid_request_errordirectory_group_read_onlyThe group comes from your company directory, so it can't be renamed, deleted or given members here.
409invalid_request_errorgroup_has_keysThe group is a team with active virtual keys, so it can't be deleted. Move its keys to another team (group_id) or revoke them first.
409invalid_request_errortool_access_request_decidedThe tool access request was already approved or declined, so it can't be decided again.
409invalid_request_errorskill_existsThe skill's name, new or renamed, is taken by another skill in the organization. To change that one, use PATCH.