Teams group the people and credentials that share limits. API keys are the credentials your applications send as Authorization: Bearer ml-... to https://app.manylayers.io/v1. This page covers teams, admin-created API keys and their per-key limits; for finer-grained rules see Rate limiting, Budgets and Policies. Personal access tokens and virtual account tokens are covered in Credentials. In the console, keys live in Gateway → API Keys.

Teams

Teams are your primary isolation unit. Create a team for each department, project, or application that needs its own access controls and limits. Each team has:
  • Model allow-list (models) — which logical models the team can access, or *. A team with no list is unrestricted.
  • Rate limits — requests per minute (rpm_limit) and tokens per minute (tpm_limit); 0 is unlimited.
  • Monthly token budget (monthly_token_budget) — a cap on total tokens consumed in the calendar month, in UTC; 0 is unlimited.
  • Cache setting (cache_enabled) — opt in or out of response caching.
  • Firewall mode (firewall_mode) — override the gateway default: off, audit or enforce. See Prompt injection.
  • Default routing config (default_config_id) — which routing config applies to the team’s requests when the request names none.
curl -X POST https://app.manylayers.io/admin/teams \
  -H "Authorization: Bearer ml_pat_..." -H "Content-Type: application/json" \
  -d '{"name": "engineering", "monthly_token_budget": 1000000,
       "rpm_limit": 60, "tpm_limit": 100000,
       "models": ["gpt-4o", "gpt-4o-mini"], "cache_enabled": false}'
teams:
  - name: engineering
    monthly_token_budget: 1000000
    rpm_limit: 60
    tpm_limit: 100000
    models: ["gpt-4o", "gpt-4o-mini"]
    cache_enabled: false
    firewall_mode: ""    # "" = use gateway default

Team management API

MethodPathDescription
GET/admin/teamsList the organization’s teams, each with its models
POST/admin/teamsCreate a team, or update the one with the same name
Both are organization-level operations: they need the organization Owner (or the deployment operator credential). A workspace administrator manages the workspace’s own teams from Gateway → Access → Teams, and POST returns 409 team_in_workspace for a name that belongs to a workspace team.

API keys

Each API key is filed under exactly one organization team and can be bound to one workspace. A workspace-bound key resolves models from that workspace’s providers only; a key with no workspace_id is team-scoped. Limits stack: the key’s limits apply on top of the team’s and any policies, and the most restrictive one binds. Keys seeded from a self-hosted gateway.yaml take only a name, the key value and a role; the key is stored as a SHA-256 hash.
teams:
  - name: engineering
    api_keys:
      - name: dev
        key: ${MANYLAYERS_DEV_KEY}
        role: member
Per-key limits, budgets and expiry are set when you create or update a key through the admin API:
curl https://app.manylayers.io/admin/keys \
  -H "Authorization: Bearer ml_pat_..." -H "Content-Type: application/json" \
  -d '{
    "team": "engineering",
    "workspace_id": "ws_123",
    "name": "dev",
    "role": "member",
    "budget_usd_monthly": 50.00,
    "budget_reset_period": "monthly",
    "rpm_limit": 100,
    "tpm_limit": 20000,
    "expires_at": "2027-01-01T00:00:00Z"
  }'
The response includes the new key as key (prefix ml-). It is shown once; only a hash is stored. team (or a user_id owner, whose own team is used) and name are required; role is member (default), editor or admin.

Per-key controls

ControlDefaultDescription
budget_usd_monthly0 (unlimited)USD spending cap per budget period. Returns HTTP 402 when exhausted.
budget_reset_periodmonthlydaily, weekly, monthly or never.
rpm_limit0 (unlimited)Requests per minute for this specific key
tpm_limit0 (unlimited)Tokens per minute for this specific key
expires_atnoneHard expiry (RFC 3339). After this time, the key returns 401 key_expired.
PATCH /admin/keys/{id} replaces all limits at once. Any limit you leave out of the body is reset to 0 (unlimited) and expires_at is cleared, so always send the full set. name and workspace_id are only changed when you send them.

Key management API

Pass ?workspace_id= when your credential can reach more than one workspace.
MethodPathDescription
GET/admin/keysList admin-created keys (?team_id=; ?credential_type=all|api_key|pat|vat to include other credential types)
POST/admin/keysCreate a new key
DELETE/admin/keys/{id}Disable a key
POST/admin/keys/{id}/enableRe-enable a disabled key
DELETE/admin/keys/{id}/permanentDelete a key permanently
PATCH/admin/keys/{id}Update name, limits, expiry or workspace binding
Issuing and changing keys needs gateway.apikeys.manage; a member may issue a member-role key that they own themselves.

Budget exhaustion response

When a per-key USD budget is exhausted, the gateway returns HTTP 402 Payment Required, with X-ManyLayers-Limit: api_key and no Retry-After:
{
  "error": {
    "message": "API key budget exceeded",
    "type": "insufficient_quota",
    "code": "key_budget_exceeded"
  }
}

Usage tracking

EndpointDescription
GET /admin/usageAggregate usage by team and model (group=user or group=key to change the rows)
GET /admin/usage/timeseriesTime-series usage data (bucket=hour|day)
GET /admin/whoamiThe calling credential’s identity: key_id, key_name, team_id, team, role, workspace_id and org_role
Fields and defaults are listed in Budgets.

Monitoring

Prometheus metricLabelsDescription
manylayers_requests_totalteam, model, statusTotal requests by team and model
manylayers_tokens_totalteam, model, directionTokens processed (input, output)
manylayers_request_duration_secondsmodelRequest latency histogram
manylayers_policy_decisions_totaloutcome, policy_type, codePolicy decisions, including rate-limit and budget refusals