B2BB2B LLM

Partner API endpoints

Complete Partner API request and response examples for keys, groups, requests, and transactions.

Partner API endpoints

Base URL:

https://p-api.model-gate.com

All requests require:

Authorization: Bearer mg_partner_...
Accept: application/json

All monetary and limit values are JSON decimal strings. Do not parse them as binary floating-point numbers.

Common Bank integration rules

All external timestamps are UTC RFC3339. Collection endpoints use limit (1–100) plus an opaque cursor; never parse or manufacture cursor contents. POST, PATCH, and DELETE requests require Idempotency-Key; retry the same operation with the same key after timeouts. Idempotency records are retained for 7 days. Responses include X-Request-ID, are Cache-Control: no-store, and all errors are JSON. Rate-limit responses are HTTP 429 with Retry-After and X-RateLimit-* headers. Unknown body/query fields are rejected.

The machine-readable OpenAPI 3.1 contract is distributed as resources/contracts/partner-api.openapi.yaml.

Create an API key

Request

curl -X POST https://p-api.model-gate.com/api/v1/partner/keys \
  -H "Authorization: Bearer mg_partner_..." \
  -H "Idempotency-Key: operation-unique-001" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Telegram user 123",
    "group_id": "GROUP_PUBLIC_ID",
    "rpm_limit": 30,
    "concurrency_limit": 2,
    "spend_limit": "20.0000000000",
    "usage_price_basis": "official_price",
    "usage_price_multiplier": "0.900000"
  }'

Response — 201

{
  "data": {
    "public_id": "KEY_PUBLIC_ID",
    "group_id": "GROUP_PUBLIC_ID",
    "name": "Telegram user 123",
    "key_prefix": "mg_live_ab12",
    "key": "mg_live_ab12...",
    "status": "active",
    "rpm_limit": 30,
    "concurrency_limit": 2,
    "spend_limit": "20.0000000000",
    "usage": "0.0000000000",
    "total_spent": "0.0000000000",
    "usage_price_basis": "official_price",
    "usage_price_multiplier": "0.900000",
    "effective_usage_price_basis": "official_price",
    "effective_usage_price_multiplier": "0.900000"
  }
}

The complete key value is returned only after creation or rotation.

List API keys

Request

curl https://p-api.model-gate.com/api/v1/partner/keys \
  -H "Authorization: Bearer mg_partner_..."

Response — 200

{
  "data": [
    {
      "public_id": "KEY_PUBLIC_ID",
      "group_id": "GROUP_PUBLIC_ID",
      "name": "Telegram user 123",
      "status": "active",
      "spend_limit": "20.0000000000",
      "usage": "3.2500000000",
      "total_spent": "1.1400000000"
    }
  ]
}

The list is ordered newest first and uses opaque cursor pagination. Pass limit=1..100; when meta.has_more is true, send meta.next_cursor as the next cursor.

Get one API key

Request

curl https://p-api.model-gate.com/api/v1/partner/keys/KEY_PUBLIC_ID \
  -H "Authorization: Bearer mg_partner_..."

Response — 200

{
  "data": {
    "public_id": "KEY_PUBLIC_ID",
    "group_id": "GROUP_PUBLIC_ID",
    "name": "Telegram user 123",
    "status": "active",
    "rpm_limit": 30,
    "concurrency_limit": 2,
    "spend_limit": "20.0000000000",
    "usage": "3.2500000000",
    "total_spent": "1.1400000000",
    "effective_usage_price_basis": "official_price",
    "effective_usage_price_multiplier": "0.900000"
  }
}

Update an API key

PATCH replaces only the supported mutable values. To remove the key from a group, send an empty group_id. To inherit valuation settings, send null for the key-level basis and multiplier.

Request

curl -X PATCH https://p-api.model-gate.com/api/v1/partner/keys/KEY_PUBLIC_ID \
  -H "Authorization: Bearer mg_partner_..." \
  -H "Idempotency-Key: operation-unique-001" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Updated user name",
    "group_id": "GROUP_PUBLIC_ID",
    "rpm_limit": 60,
    "concurrency_limit": 4,
    "spend_limit": "40.0000000000",
    "usage_price_basis": null,
    "usage_price_multiplier": null
  }'

Response — 200

{
  "data": {
    "public_id": "KEY_PUBLIC_ID",
    "spend_limit": "40.0000000000",
    "usage": "3.2500000000",
    "effective_usage_price_basis": "user_price",
    "effective_usage_price_multiplier": "1.000000"
  }
}

Delete an API key

Request

curl -X DELETE https://p-api.model-gate.com/api/v1/partner/keys/KEY_PUBLIC_ID \
  -H "Authorization: Bearer mg_partner_..." \
  -H "Idempotency-Key: operation-unique-001" \

Response — 200

{"data":{"deleted":true}}

Freeze and unfreeze a key

Freeze request

curl -X POST https://p-api.model-gate.com/api/v1/partner/keys/KEY_PUBLIC_ID/freeze \
  -H "Authorization: Bearer mg_partner_..." \
  -H "Idempotency-Key: operation-unique-001" \

Freeze response

{"data":{"public_id":"KEY_PUBLIC_ID","status":"frozen"}}

Unfreeze request

curl -X POST https://p-api.model-gate.com/api/v1/partner/keys/KEY_PUBLIC_ID/unfreeze \
  -H "Authorization: Bearer mg_partner_..." \
  -H "Idempotency-Key: operation-unique-001" \

Unfreeze response

{"data":{"public_id":"KEY_PUBLIC_ID","status":"active"}}

Unfreezing requires a verified owner email.

Rotate a key

Request

curl -X POST https://p-api.model-gate.com/api/v1/partner/keys/KEY_PUBLIC_ID/rotate \
  -H "Authorization: Bearer mg_partner_..." \
  -H "Idempotency-Key: operation-unique-001" \

Response — 200

{
  "data": {
    "public_id": "KEY_PUBLIC_ID",
    "status": "active",
    "key_prefix": "mg_live_cd34",
    "key": "mg_live_cd34..."
  }
}

Reset key usage

Request

curl -X POST https://p-api.model-gate.com/api/v1/partner/keys/KEY_PUBLIC_ID/reset-usage \
  -H "Authorization: Bearer mg_partner_..." \
  -H "Idempotency-Key: operation-unique-001" \

Response — 200

{
  "data": {
    "public_id": "KEY_PUBLIC_ID",
    "usage": "0.0000000000",
    "total_spent": "1.1400000000"
  }
}

Resetting usage does not change lifetime spend or the account balance.

Get key usage

Request

curl https://p-api.model-gate.com/api/v1/partner/keys/KEY_PUBLIC_ID/usage \
  -H "Authorization: Bearer mg_partner_..."

Response — 200

{
  "data": {
    "key_id": "KEY_PUBLIC_ID",
    "usage": "3.2500000000",
    "total_spent": "1.1400000000"
  }
}

Get spend limit and remaining usage

Request

curl https://p-api.model-gate.com/api/v1/partner/keys/KEY_PUBLIC_ID/spent-limit \
  -H "Authorization: Bearer mg_partner_..."

Response — 200

{
  "data": {
    "key_id": "KEY_PUBLIC_ID",
    "spend_limit": "20.0000000000",
    "usage": "3.2500000000",
    "remaining": "16.7500000000"
  }
}

When spend_limit is zero, it is unlimited and remaining is null.

Get the latest requests for a key

Detailed request history is a hot-retention dataset controlled by API_REQUESTS_HOT_RETENTION_DAYS (default 7 days). The response metadata reports the active retention window. Use balance transactions for longer-term financial reconciliation.

limit is optional, defaults to 10, and must be an integer from 1 to 100. Finalized requests are ordered by finished_at newest first. When meta.has_more is true, pass meta.next_cursor as the opaque before query parameter to retrieve the next older page. Do not parse or construct cursors yourself.

The response contains immutable user and official per-million-token rates for requests completed after migration 059_request_pricing_audit_snapshot.sql. It also calculates pricing_snapshot.usage_price from the saved basis, multiplier, and historical rates without storing another rate set. This makes the independent usage-limit valuation auditable after catalog prices change. Historical requests created before migration 059 return pricing_snapshot.available: false rather than substituting current prices. Requests executed through Claude/OpenAI-compatible batch APIs are explicitly marked with request_mode: "batch" and include their protocol, batch job ID, custom_id, and the snapshotted Model Gate batch price multiplier.

Latency uses stable Business/Partner names: gateway_overhead_ms, upstream_first_token_ms, and e2e_first_token_ms. The E2E first-token value is measured from Model Gate request start to the first real content token and excludes client-side network/TLS time. The Partner API exposes only the explicit e2e_first_token_ms name; the internal storage column remains api_requests.first_token_ms.

Request

curl "https://p-api.model-gate.com/api/v1/partner/keys/KEY_PUBLIC_ID/requests?limit=10" \
  -H "Authorization: Bearer mg_partner_..."

Response — 200

{
  "data": [
    {
      "request_id": "01KZ...",
      "key_id": "KEY_PUBLIC_ID",
      "group_id": "GROUP_PUBLIC_ID",
      "model": "claude-opus-4.7",
      "endpoint": "messages",
      "method": "POST",
      "status": "succeeded",
      "status_code": 200,
      "is_stream": false,
      "request_mode": "batch",
      "batch": {
        "protocol": "claude",
        "job_id": "msgbatch_01K...",
        "custom_id": "request-1",
        "price_multiplier": "0.5"
      },
      "tokens": {
        "input": 120,
        "output": 45,
        "cached": 0,
        "cache_write": 0,
        "reasoning": 0,
        "total": 165
      },
      "cost": "0.0000345",
      "usage_cost": "0.00345",
      "official_base_cost": "0.000345",
      "currency": "USD",
      "usage_pricing": {
        "basis": "official_price",
        "base_field": "official_base_cost",
        "base_amount": "0.000345",
        "multiplier": "10",
        "usage_cost": "0.00345",
        "formula": "round(base_amount * multiplier, 10)"
      },
      "pricing_snapshot": {
        "available": true,
        "unit": "per_1m_tokens",
        "user_price": {
          "input": "0.2",
          "output": "1",
          "cache_read": "0.02",
          "cache_write": "0.25",
          "reasoning": "1"
        },
        "official_price": {
          "input": "1",
          "output": "5",
          "cache_read": "0.1",
          "cache_write": "1.25",
          "reasoning": "5"
        },
        "usage_price": {
          "input": "10",
          "output": "50",
          "cache_read": "1",
          "cache_write": "12.5",
          "reasoning": "50"
        }
      },
      "duration_ms": 842,
      "gateway_overhead_ms": 34,
      "upstream_first_token_ms": 156,
      "e2e_first_token_ms": 190,
      "settlement_status": "settled",
      "started_at": "2026-08-05T10:00:00Z",
      "finished_at": "2026-08-05T10:00:00.842Z"
    }
  ],
  "meta": {
    "key_id": "KEY_PUBLIC_ID",
    "limit": 10,
    "returned": 1,
    "order": "finished_at_desc",
    "next_cursor": "eyJ0IjoiMjAyNi0wOC0wNSAxMDowMDowMCIsInAiOiIwMUt...",
    "has_more": true
  }
}

To continue with the next older page:

curl "https://p-api.model-gate.com/api/v1/partner/keys/KEY_PUBLIC_ID/requests?limit=10&before=NEXT_CURSOR" \
  -H "Authorization: Bearer mg_partner_..."

An invalid before cursor returns HTTP 422. The cursor contains only the finish-time position and external request public ID; internal numeric request IDs are never exposed.

For synchronous and native async requests, request_mode is sync or async and the batch object is omitted. The saved token-rate snapshot allows the historical base calculation to be reproduced as sum(tokens × snapshotted_rate / 1,000,000). Settlement rounds the selected base amount to 10 decimal places and then calculates round(base_amount × multiplier, 10). The stored cost, official_base_cost, and usage_cost fields remain authoritative.

Raw request and response bodies are never returned by this endpoint.

List balance transactions

Request

curl https://p-api.model-gate.com/api/v1/partner/transactions \
  -H "Authorization: Bearer mg_partner_..."

Response — 200

{
  "data": [
    {
      "transaction_id": "TRANSACTION_PUBLIC_ID",
      "type": "debit",
      "source": "api_usage_minute",
      "billing_minute_num": 29769120,
      "amount": "-0.0232000000",
      "request_count": 100,
      "balance_before": null,
      "balance_after": null,
      "api_key_id": null,
      "created_at": "2026-08-05T10:00:00Z"
    }
  ]
}

Billable inference is represented as one wallet-ledger row per billing owner and finish-time minute. transaction_id is the durable public ledger identifier. request_count is the number of requests included in that minute debit; billing_minute_num is floor(unix(finished_at)/60). Raw internal ledger id / source_id values are not returned. Aggregate API-usage rows intentionally return null for balance_before, balance_after, and api_key_id; exact key/group/request detail remains available from request history and the panel minute drill-down.

Get current balance

curl https://p-api.model-gate.com/api/v1/partner/balance \
  -H "Authorization: Bearer mg_partner_..."
{"data":{"balance":"1234.5678900000","currency":"USD","as_of":"2026-08-28T07:00:00Z"}}

Use this endpoint after account.balance_low callbacks to reconcile the current account wallet without requiring a Model API credential.

List Partner audit events

curl "https://p-api.model-gate.com/api/v1/partner/audit-events?limit=100" \
  -H "Authorization: Bearer mg_partner_..."

Audit events record successful Partner management mutations with request ID, action, target, source IP, status, safe metadata, and UTC timestamp. Secrets, bearer tokens, API-key plaintext, idempotency keys, request fingerprints, and replay bodies are not stored in audit metadata. Use cursor for subsequent pages and optional action filtering.

Create a group

Request

curl -X POST https://p-api.model-gate.com/api/v1/partner/groups \
  -H "Authorization: Bearer mg_partner_..." \
  -H "Idempotency-Key: operation-unique-001" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Telegram bot A",
    "description": "Keys created by bot A",
    "rpm_limit": 100,
    "concurrency_limit": 20,
    "spend_limit": "1000.0000000000",
    "usage_price_basis": "official_price",
    "usage_price_multiplier": "0.900000"
  }'

Response — 201

{
  "data": {
    "public_id": "GROUP_PUBLIC_ID",
    "name": "Telegram bot A",
    "status": "active",
    "spend_limit": "1000.0000000000",
    "usage": "0.0000000000",
    "total_spent": "0.0000000000",
    "usage_price_basis": "official_price",
    "usage_price_multiplier": "0.900000"
  }
}

List groups

Request

curl https://p-api.model-gate.com/api/v1/partner/groups \
  -H "Authorization: Bearer mg_partner_..."

Response

{"data":[{"public_id":"GROUP_PUBLIC_ID","name":"Telegram bot A","status":"active","usage":"0.0000000000"}]}

Get or update a group

Get request

curl https://p-api.model-gate.com/api/v1/partner/groups/GROUP_PUBLIC_ID \
  -H "Authorization: Bearer mg_partner_..."

Get response

{"data":{"public_id":"GROUP_PUBLIC_ID","name":"Telegram bot A","spend_limit":"1000.0000000000","usage":"12.0000000000"}}

Update request

curl -X PATCH https://p-api.model-gate.com/api/v1/partner/groups/GROUP_PUBLIC_ID \
  -H "Authorization: Bearer mg_partner_..." \
  -H "Idempotency-Key: operation-unique-001" \
  -H "Content-Type: application/json" \
  -d '{"name":"Telegram bot A production","status":"active","spend_limit":"2000.0000000000","usage_price_basis":"user_price","usage_price_multiplier":"1.200000"}'

Update response

{"data":{"public_id":"GROUP_PUBLIC_ID","name":"Telegram bot A production","spend_limit":"2000.0000000000","usage_price_basis":"user_price","usage_price_multiplier":"1.200000"}}

Delete a group

A non-empty group is not deleted. Move or delete all member API keys first; otherwise the API returns HTTP 409 with group_not_empty.

Request

curl -X DELETE https://p-api.model-gate.com/api/v1/partner/groups/GROUP_PUBLIC_ID \
  -H "Authorization: Bearer mg_partner_..." \
  -H "Idempotency-Key: operation-unique-001" \

Response

{"data":{"deleted":true}}

Keys are detached according to the database foreign-key behavior. Verify membership before deletion.

Reset group usage

Request

curl -X POST https://p-api.model-gate.com/api/v1/partner/groups/GROUP_PUBLIC_ID/reset-usage \
  -H "Authorization: Bearer mg_partner_..." \
  -H "Idempotency-Key: operation-unique-001" \

Response

{"data":{"public_id":"GROUP_PUBLIC_ID","usage":"0.0000000000","total_spent":"8.5000000000"}}

List group members

Request

curl https://p-api.model-gate.com/api/v1/partner/groups/GROUP_PUBLIC_ID/members \
  -H "Authorization: Bearer mg_partner_..."

Response

{"data":[{"public_id":"KEY_PUBLIC_ID","name":"Telegram user 123","status":"active","usage":"3.2500000000","total_spent":"1.1400000000"}]}

Add a key to a group

Request

curl -X POST https://p-api.model-gate.com/api/v1/partner/groups/GROUP_PUBLIC_ID/members \
  -H "Authorization: Bearer mg_partner_..." \
  -H "Idempotency-Key: operation-unique-001" \
  -H "Content-Type: application/json" \
  -d '{"key_id":"KEY_PUBLIC_ID"}'

Response

The response is the updated group member list:

{"data":[{"public_id":"KEY_PUBLIC_ID","name":"Telegram user 123","status":"active"}]}

Remove a key from a group

Request

curl -X DELETE https://p-api.model-gate.com/api/v1/partner/groups/GROUP_PUBLIC_ID/members/KEY_PUBLIC_ID \
  -H "Authorization: Bearer mg_partner_..." \
  -H "Idempotency-Key: operation-unique-001" \

Response

{"data":{"removed":true}}

Get group usage

Request

curl https://p-api.model-gate.com/api/v1/partner/groups/GROUP_PUBLIC_ID/usage \
  -H "Authorization: Bearer mg_partner_..."

Response

{"data":{"group_id":"GROUP_PUBLIC_ID","usage":"12.0000000000","total_spent":"8.5000000000","usage_reset_at":"2026-08-01T00:00:00Z"}}

Get group statistics

from and to accept only UTC RFC3339 timestamps ending in Z (fractional seconds up to 6 digits are allowed). Statistics are computed only from completed-request aggregates keyed by finished_at; the currently open minute is intentionally excluded, so results may lag by up to the minute aggregation cadence.

average_duration_ms is the average full Model Gate request duration (duration_ms) across completed requests in the selected period. It is not E2E first-token latency, upstream first-token latency, or gateway overhead. The Partner API exposes only this explicit metric name.

Request

curl "https://p-api.model-gate.com/api/v1/partner/groups/GROUP_PUBLIC_ID/stats?from=2026-08-01T00:00:00Z&to=2026-08-05T23:59:59Z" \
  -H "Authorization: Bearer mg_partner_..."

Response

{
  "data": {
    "group_id": "GROUP_PUBLIC_ID",
    "requests": 120,
    "errors": 3,
    "input_tokens": 50000,
    "output_tokens": 12000,
    "cached_tokens": 8000,
    "cache_write_tokens": 1200,
    "reasoning_tokens": 2000,
    "cost": "8.5000000000",
    "average_duration_ms": "842.50",
    "from": "2026-08-01T00:00:00Z",
    "to": "2026-08-05T23:59:59Z"
  }
}

Get an asynchronous result

Request

curl https://p-api.model-gate.com/api/v1/requests/01KZ... \
  -H "Authorization: Bearer mg_partner_..."

Processing response

{
  "data": {
    "request_id": "01KZ...",
    "status": "processing",
    "created_at": "2026-08-05T10:00:00Z",
    "started_at": "2026-08-05T10:00:00Z",
    "completed_at": null,
    "expires_at": "2026-08-06T10:00:00Z"
  }
}

Completed response

{
  "data": {
    "request_id": "01KZ...",
    "status": "completed",
    "response_status": 200,
    "response_headers": {"Content-Type":"application/json"},
    "response": {"id":"resp_example","status":"completed"},
    "completed_at": "2026-08-05T10:00:05Z",
    "expires_at": "2026-08-06T10:00:00Z"
  }
}

Common errors

Invalid Partner API key — 401

{"error":{"type":"invalid_token","message":"Invalid Partner API key"}}

Resource not found — 404

{"error":{"type":"not_found","message":"API key not found"}}

Invalid request — 422

{"error":{"type":"invalid_request","message":"limit must be an integer from 1 to 100"}}

Mutating Partner API JSON bodies are limited to 1 MiB.

Idempotency conflict — 409

The same Idempotency-Key was reused for a different request. Generate a new key for a new logical operation.

Rate limit — 429

The response contains error.code = partner_rate_limit_exceeded and Retry-After. Retry only after the indicated delay.

Method not allowed — 405

The Partner host returns a JSON error plus the HTTP Allow header; it never falls back to an HTML error page.

Business credential principal rules

For a Business Partner API token, only the verified organization owner is accepted. Keys created through Partner API use that owner as both billing owner and credential principal. Employee-principal assignment is performed in the web panel. group_id, when supplied, must identify an active group owned by the Business account; invalid values return HTTP 422 and never fall back to an ungrouped key. Request-history responses may include principal_user_id to identify the credential principal independently from billing ownership.