BlockVectra Keys API

API key creation, query, renaming, rotation, and revocation

Operations 6

GET /keys List API keys #
POST /keys Create key (secret displayed only once) #
GET /keys/{key_id} Single key #
PATCH /keys/{key_id} Rename key #
POST /keys/{key_id}/rotate Rotate key (new secret displayed only once) #
POST /keys/{key_id}/revoke Revoke key (idempotent) #

Specifications

Schemas & Data

📊
JSONSchema
https://artifacts.apis.io/blockvectra/json-schema/blockvectra-freshness-envelope-schema.json
📊
JSONSchema
https://artifacts.apis.io/blockvectra/json-schema/blockvectra-token-transfer-list-envelope-schema.json
📊
JSONSchema
https://artifacts.apis.io/blockvectra/json-schema/blockvectra-address-transfer-list-envelope-schema.json
📊
JSONSchema
https://artifacts.apis.io/blockvectra/json-schema/blockvectra-tokens-batch-envelope-schema.json
📊
JSONSchema
https://artifacts.apis.io/blockvectra/json-schema/blockvectra-dex-swap-list-envelope-schema.json
📊
JSONSchema
https://artifacts.apis.io/blockvectra/json-schema/blockvectra-transaction-list-envelope-schema.json
📊
JSONSchema
https://artifacts.apis.io/blockvectra/json-schema/blockvectra-key-view-schema.json
📊
JSONSchema
https://artifacts.apis.io/blockvectra/json-schema/blockvectra-balance-response-schema.json
📊
JSONSchema
https://artifacts.apis.io/blockvectra/json-schema/blockvectra-plans-response-schema.json
📊
JSONSchema
https://artifacts.apis.io/blockvectra/json-schema/blockvectra-resets-response-schema.json
📊
JSONSchema
https://artifacts.apis.io/blockvectra/json-schema/blockvectra-identity-view-schema.json
📊
JSONSchema
https://artifacts.apis.io/blockvectra/json-schema/blockvectra-session-response-schema.json
📊
JSONSchema
https://artifacts.apis.io/blockvectra/json-schema/blockvectra-subscription-create-schema.json
📊
JSONSchema
https://artifacts.apis.io/blockvectra/json-schema/blockvectra-subscription-patch-schema.json
📊
JSONSchema
https://artifacts.apis.io/blockvectra/json-schema/blockvectra-remove-result-schema.json
📊
JSONSchema
https://artifacts.apis.io/blockvectra/json-schema/blockvectra-add-result-schema.json
📊
JSONSchema
https://artifacts.apis.io/blockvectra/json-schema/blockvectra-event-history-page-schema.json
📊
JSONSchema
https://artifacts.apis.io/blockvectra/json-schema/blockvectra-subscription-list-schema.json
📊
JSONSchema
https://artifacts.apis.io/blockvectra/json-schema/blockvectra-faucet-claim-accepted-schema.json
📊
JSONSchema
https://artifacts.apis.io/blockvectra/json-schema/blockvectra-faucet-claim-request-schema.json
📊
JSONSchema
https://artifacts.apis.io/blockvectra/json-schema/blockvectra-json-rpc-response-body-schema.json

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/blockvectra:blockvectra-keys-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

blockvectra-keys-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: BlockVectra Console Keys API
  version: 2.1.2
  description: Console account management APIs for wallet login, identities, API keys, plans, balances, charges, top-ups, usage and reset chances. Product APIs (JSON-RPC, Data API and WebSocket) do not depend on these account management APIs.
servers:
- url: https://console-api.blockvectra.com/v1
tags:
- name: Keys
  description: API key creation, query, renaming, rotation, and revocation
paths:
  /keys:
    get:
      tags:
      - Keys
      operationId: listKeys
      summary: List API keys
      description: Returns account keys in descending order of `created_at`, unpaginated. Revoked keys excluded by default; included when `include_revoked=true`.
      security:
      - SessionToken: []
      parameters:
      - $ref: '#/components/parameters/IncludeRevokedQuery'
      responses:
        '200':
          description: API key list.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KeyListResponse'
              examples:
                keysList:
                  $ref: '#/components/examples/keysList'
                keysListIncludeRevoked:
                  $ref: '#/components/examples/keysListIncludeRevoked'
        '400':
          description: '`include_revoked` is not `true`/`false`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                keysListBadFlag:
                  $ref: '#/components/examples/keysListBadFlag'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
    post:
      tags:
      - Keys
      operationId: createKey
      summary: Create key (secret displayed only once)
      description: Create an active API key; free-account default CU limits are returned by GET /plans in key_defaults and paid-account lower bounds in paid_key_minimums when published; the actual limits of a key are cu_per_sec and burst_cu on GET /keys. The new key is usable within about 5 seconds. Optional cu_cap sets a lifetime CU cap; expires_in_secs and expires_at are mutually exclusive expiration options. An expired key returns 403 key_expired; an exhausted cap returns 403 key_cap_exhausted. Rotation preserves the cap and expiration.
      security:
      - SessionToken: []
      requestBody:
        $ref: '#/components/requestBodies/KeyCreateBody'
      responses:
        '201':
          description: New key and its secret (`api_key`, displayed only this once).
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KeyCreateResponse'
              examples:
                keyCreated:
                  $ref: '#/components/examples/keyCreated'
        '400':
          description: 'Invalid label (empty, exceeding 64 chars, control characters), unknown fields; `expires_in_secs` and `expires_at` both provided or malformed; expiration not in future or exceeding maximum validity (`reason: expires_at`), `cu_cap` not an integer in 1–9007199254740991 (zero, negative, above the maximum, fractional, string, boolean or null; `reason: cu_cap`).'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                keyCreateBadLabel:
                  $ref: '#/components/examples/keyCreateBadLabel'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: 'Unrevoked and unexpired key limit reached, including disabled keys; error.reason is active_keys and error.limit is the effective policy limit. This check precedes the daily creation cap.

            '
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                keyCreateLimitUnfunded:
                  $ref: '#/components/examples/keyCreateLimitUnfunded'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '429':
          description: 'Rate limit exceeded, or daily key creation limit reached for account (with reason: daily_creations).'
          headers:
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                keyRateLimitedDailyCreations:
                  $ref: '#/components/examples/keyRateLimitedDailyCreations'
                errRateLimited:
                  $ref: '#/components/examples/errRateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
  /keys/{key_id}:
    get:
      tags:
      - Keys
      operationId: getKey
      summary: Single key
      security:
      - SessionToken: []
      parameters:
      - $ref: '#/components/parameters/KeyIdPath'
      responses:
        '200':
          description: API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KeyView'
              examples:
                keyGet:
                  $ref: '#/components/examples/keyGet'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Key does not exist or does not belong to this account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                keyGetOtherAccount:
                  $ref: '#/components/examples/keyGetOtherAccount'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalError'
    patch:
      tags:
      - Keys
      operationId: renameKey
      summary: Rename key
      description: Only `label` can be modified; revoked keys can also be renamed.
      security:
      - SessionToken: []
      parameters:
      - $ref: '#/components/parameters/KeyIdPath'
      requestBody:
        $ref: '#/components/requestBodies/KeyRenameBody'
      responses:
        '200':
          description: Renamed key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KeyView'
              examples:
                keyRenamed:
                  $ref: '#/components/examples/keyRenamed'
        '400':
          description: Invalid label, unknown fields.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                keyRenameEmptyLabel:
                  $ref: '#/components/examples/keyRenameEmptyLabel'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Key does not exist or does not belong to this account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                keyRenameOtherAccount:
                  $ref: '#/components/examples/keyRenameOtherAccount'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalError'
  /keys/{key_id}/rotate:
    post:
      tags:
      - Keys
      operationId: rotateKey
      summary: Rotate key (new secret displayed only once)
      description: 'Permitted for `active`, unexpired keys only. Creates a new key (inheriting label, limits, `cu_cap`, `expires_at`, and settled `cu_spent`; quota not reset by rotation) and revokes old key; the old key is refused by the gateways normally within about 1 second, and at most 5 seconds if a change notification is lost; `replaced` in response is revoked old key whose `replaced_by_key_id` matches new key `key_id`.


        The original key must first be enabled, unrevoked and unexpired, otherwise 409 key_not_active. Rotation does not add to the live-key count but remains subject to the rolling 24-hour creation cap (429 rate_limited, error.reason daily_creations, Retry-After: 3600). It preserves the original limits, expiry and settled spending rather than resetting them.'
      security:
      - SessionToken: []
      parameters:
      - $ref: '#/components/parameters/KeyIdPath'
      responses:
        '201':
          description: New key, its secret (displayed only this once), and revoked old key.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KeyRotateResponse'
              examples:
                keyRotated:
                  $ref: '#/components/examples/keyRotated'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Key does not exist or does not belong to this account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                keyRotateOtherAccount:
                  $ref: '#/components/examples/keyRotateOtherAccount'
        '409':
          description: Key is not `active`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                keyRotateNotActive:
                  $ref: '#/components/examples/keyRotateNotActive'
        '429':
          description: 'Rate limit exceeded, or daily key creation limit reached for account (rotations count as new keys, with reason: daily_creations).'
          headers:
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                keyRateLimitedDailyCreations:
                  $ref: '#/components/examples/keyRateLimitedDailyCreations'
                errRateLimited:
                  $ref: '#/components/examples/errRateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
  /keys/{key_id}/revoke:
    post:
      tags:
      - Keys
      operationId: revokeKey
      summary: Revoke key (idempotent)
      description: Both `active` and `disabled` keys can be revoked; returns 200 verbatim if already revoked. Data plane returns 401 `invalid_api_key` for this key within ~5 s.
      security:
      - SessionToken: []
      parameters:
      - $ref: '#/components/parameters/KeyIdPath'
      responses:
        '200':
          description: Revoked key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KeyView'
              examples:
                keyRevoked:
                  $ref: '#/components/examples/keyRevoked'
                keyRevokedAgain:
                  $ref: '#/components/examples/keyRevokedAgain'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Key does not exist or does not belong to this account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                keyRevokeOtherAccount:
                  $ref: '#/components/examples/keyRevokeOtherAccount'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalError'
components:
  headers:
    RetryAfter:
      description: Suggested wait time in seconds.
      required: true
      schema:
        type: integer
        minimum: 1
      example: 6
    CacheControl:
      description: No store (included in all responses).
      required: true
      schema:
        type: string
        const: no-store
    WwwAuthenticate:
      description: '401 always includes `WWW-Authenticate: Bearer`.'
      required: true
      schema:
        type: string
        const: Bearer
  examples:
    keyRotateOtherAccount:
      summary: Key belonging to another account
      value:
        error:
          code: not_found
          message: key not found
    keyGet:
      summary: Single key
      value:
        key_id: k_0123456789ab
        label: prod-backend
        status: active
        cu_per_sec: 100
        burst_cu: 400
        created_at: 2026-09-26 08:01:00+00:00
        created_by: console
        revoked_at: null
        replaced_by_key_id: null
        cu_cap: null
        cu_spent: 0
        expires_at: null
    errUnauthenticated:
      summary: Invalid session (missing, malformed, unknown, expired, or revoked)
      value:
        error:
          code: unauthenticated
          message: missing or invalid session token
    keyCreateLimitUnfunded:
      summary: Active key limit reached (20 keys)
      value:
        error:
          code: key_limit_reached
          message: account already has the maximum number of unrevoked keys; revoke one first
          reason: active_keys
          limit: 20
    keysListBadFlag:
      summary: include_revoked is not a boolean
      value:
        error:
          code: invalid_request
          message: include_revoked must be true or false
    errInternal:
      summary: Internal error
      value:
        error:
          code: internal
          message: internal error
    errUnauthenticatedLinkChallenge:
      summary: purpose=link without session
      value:
        error:
          code: unauthenticated
          message: missing or invalid session token
    keyRenamed:
      summary: Renamed key
      value:
        key_id: k_0123456789ab
        label: staging-backend
        status: active
        cu_per_sec: 100
        burst_cu: 400
        created_at: 2026-09-26 08:01:00+00:00
        created_by: console
        revoked_at: null
        replaced_by_key_id: null
        cu_cap: null
        cu_spent: 0
        expires_at: null
    reqKeyControlChar:
      summary: label contains control characters
      value:
        label: "prod\tbackend"
    keyRenameEmptyLabel:
      summary: Empty label
      value:
        error:
          code: invalid_request
          message: label must be 1-64 characters without control characters
    reqKeyEmptyLabel:
      summary: Empty label
      value:
        label: ''
    keyCreated:
      summary: New key (secret displayed only once)
      value:
        key:
          key_id: k_0123456789ab
          label: prod-backend
          status: active
          cu_per_sec: 100
          burst_cu: 400
          created_at: 2026-09-26 08:01:00+00:00
          created_by: console
          revoked_at: null
          replaced_by_key_id: null
          cu_cap: null
          cu_spent: 0
          expires_at: null
        api_key: rgw_4f2d0c3b9a8e7d6c5b4a39281706f5e4d3c2b1a09f8e7d6c5b4a3928170605f4
    keyRotateNotActive:
      summary: Rotating revoked key
      value:
        error:
          code: key_not_active
          message: only an active key can be rotated
    keysListIncludeRevoked:
      summary: include_revoked=true, including revoked and replaced keys
      value:
        items:
        - key_id: k_89abcdef0123
          label: prod-backend
          status: active
          cu_per_sec: 100
          burst_cu: 400
          created_at: 2026-09-26 09:00:00+00:00
          created_by: console
          revoked_at: null
          replaced_by_key_id: null
          cu_cap: null
          cu_spent: 0
          expires_at: null
        - key_id: k_0123456789ab
          label: prod-backend
          status: revoked
          cu_per_sec: 100
          burst_cu: 400
          created_at: 2026-09-26 08:01:00+00:00
          created_by: console
          revoked_at: 2026-09-26 09:00:00+00:00
          replaced_by_key_id: k_89abcdef0123
          cu_cap: null
          cu_spent: 0
          expires_at: null
    keysList:
      summary: Active keys
      value:
        items:
        - key_id: k_0123456789ab
          label: prod-backend
          status: active
          cu_per_sec: 100
          burst_cu: 400
          created_at: 2026-09-26 08:01:00+00:00
          created_by: console
          revoked_at: null
          replaced_by_key_id: null
          cu_cap: null
          cu_spent: 0
          expires_at: null
        - key_id: k_cdef01234567
          label: null
          status: disabled
          cu_per_sec: 1000
          burst_cu: 4000
          created_at: 2026-09-20 10:00:00+00:00
          created_by: operator
          revoked_at: null
          replaced_by_key_id: null
          cu_cap: null
          cu_spent: 0
          expires_at: null
    keyRenameOtherAccount:
      summary: Key belonging to another account
      value:
        error:
          code: not_found
          message: key not found
    keyCreateBadLabel:
      summary: label contains control characters
      value:
        error:
          code: invalid_request
          message: label must be 1-64 characters without control characters
    payloadTooLarge:
      summary: Payload too large
      value:
        error:
          code: payload_too_large
          message: payload too large
    keyRotated:
      summary: New key, its secret, and revoked old key
      value:
        key:
          key_id: k_89abcdef0123
          label: prod-backend
          status: active
          cu_per_sec: 100
          burst_cu: 400
          created_at: 2026-09-26 09:00:00+00:00
          created_by: console
          revoked_at: null
          replaced_by_key_id: null
          cu_cap: null
          cu_spent: 0
          expires_at: null
        api_key: rgw_7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b
        replaced:
          key_id: k_0123456789ab
          label: prod-backend
          status: revoked
          cu_per_sec: 100
          burst_cu: 400
          created_at: 2026-09-26 08:01:00+00:00
          created_by: console
          revoked_at: 2026-09-26 09:00:00+00:00
          replaced_by_key_id: k_89abcdef0123
          cu_cap: null
          cu_spent: 0
          expires_at: null
    reqKeyCreate:
      summary: Create new key
      value:
        label: prod-backend
    keyGetOtherAccount:
      summary: Key belonging to another account (indistinguishable from nonexistent)
      value:
        error:
          code: not_found
          message: key not found
    keyRevokedAgain:
      summary: Revoke again (idempotent, returned verbatim)
      value:
        key_id: k_0123456789ab
        label: prod-backend
        status: revoked
        cu_per_sec: 100
        burst_cu: 400
        created_at: 2026-09-26 08:01:00+00:00
        created_by: console
        revoked_at: 2026-09-26 10:00:00+00:00
        replaced_by_key_id: null
        cu_cap: null
        cu_spent: 0
        expires_at: null
    keyRateLimitedDailyCreations:
      summary: Daily key creation limit reached for account
      value:
        error:
          code: rate_limited
          message: rate limit exceeded
          reason: daily_creations
    keyRevoked:
      summary: Revoked key
      value:
        key_id: k_0123456789ab
        label: prod-backend
        status: revoked
        cu_per_sec: 100
        burst_cu: 400
        created_at: 2026-09-26 08:01:00+00:00
        created_by: console
        revoked_at: 2026-09-26 10:00:00+00:00
        replaced_by_key_id: null
        cu_cap: null
        cu_spent: 0
        expires_at: null
    errServiceUnavailable:
      summary: Service unavailable
      value:
        error:
          code: service_unavailable
          message: the service is temporarily unavailable
    keyRevokeOtherAccount:
      summary: Key belonging to another account
      value:
        error:
          code: not_found
          message: key not found
    errRateLimited:
      summary: Rate limit exceeded
      value:
        error:
          code: rate_limited
          message: rate limit exceeded
    reqKeyRename:
      summary: Rename key
      value:
        label: staging-backend
  schemas:
    KeyLabelRequest:
      type: object
      additionalProperties: false
      required:
      - label
      properties:
        label:
          $ref: '#/components/schemas/KeyLabel'
    KeyListResponse:
      type: object
      additionalProperties: false
      required:
      - items
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/KeyView'
    KeyCreateRequest:
      type: object
      additionalProperties: false
      required:
      - label
      properties:
        label:
          $ref: '#/components/schemas/KeyLabel'
        cu_cap:
          type: integer
          minimum: 1
          maximum: 9007199254740991
          description: Lifetime CU cap for this key; unlimited if omitted.
        expires_in_secs:
          type: integer
          minimum: 1
          maximum: 2147483647
          description: Seconds until expiration from current server time (converted to absolute time); mutually exclusive with `expires_at`.
        expires_at:
          type: string
          format: date-time
          description: Expiration timestamp, RFC 3339 string with timezone (`Z` or offset); must be in the future within maximum validity; mutually exclusive with `expires_in_secs`.
    ApiKeySecret:
      type: string
      pattern: ^rgw_[0-9a-f]{64}$
      description: Key secret, displayed only this once.
    Timestamp:
      type: string
      format: date-time
      pattern: ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$
      description: RFC 3339 UTC, second precision, ending in `Z` (`2026-09-26T08:00:00Z`); no fractional seconds, without `+00:00` timezone offsets.
      example: 2026-09-26 08:00:00+00:00
    KeyCreateResponse:
      type: object
      additionalProperties: false
      required:
      - key
      - api_key
      properties:
        key:
          $ref: '#/components/schemas/KeyView'
        api_key:
          $ref: '#/components/schemas/ApiKeySecret'
    KeyView:
      type: object
      additionalProperties: false
      required:
      - key_id
      - label
      - status
      - cu_per_sec
      - burst_cu
      - created_at
      - created_by
      - revoked_at
      - replaced_by_key_id
      - cu_cap
      - cu_spent
      - expires_at
      properties:
        key_id:
          type: string
          pattern: ^k_[0-9a-f]{12}$
        label:
          description: Null for keys created by support.
          oneOf:
          - $ref: '#/components/schemas/KeyLabel'
          - type: 'null'
        status:
          type: string
          enum:
          - active
          - disabled
          - revoked
        cu_per_sec:
          type: integer
          description: Actual per-key token bucket refill rate (CU/s), including any paid floor or higher custom limit. Effective aggregate across all chains and Data API for this key.
        burst_cu:
          type: integer
          description: Actual per-key token bucket capacity (CU), including any paid floor or higher custom limit. Effective aggregate across all chains and Data API for this key.
        created_at:
          $ref: '#/components/schemas/Timestamp'
        created_by:
          type: string
          enum:
          - console
          - operator
          description: '`console` for self-created by client, `operator` for created by support.'
        revoked_at:
          oneOf:
          - $ref: '#/components/schemas/Timestamp'
          - type: 'null'
        replaced_by_key_id:
          type:
          - string
          - 'null'
          pattern: ^k_[0-9a-f]{12}$
          description: New key created from rotation; null if never rotated.
        cu_cap:
          type:
          - integer
          - 'null'
          minimum: 1
          maximum: 9007199254740991
          description: Lifetime CU cap for this key; null if unlimited. Inherited by rotated new key.
        cu_spent:
          type: integer
          minimum: 0
          maximum: 9007199254740991
          description: Settled CU counted toward key cap (settled hourly; excludes unsettled usage in latest period); inherited by rotated new key.
        expires_at:
          description: Expiration timestamp; null if no expiration. Data plane rejects expired key (403 `key_expired`), status remains `active`. Inherited by rotated new key.
          oneOf:
          - $ref: '#/components/schemas/Timestamp'
          - type: 'null'
    KeyRotateResponse:
      type: object
      additionalProperties: false
      required:
      - key
      - api_key
      - replaced
      properties:
        key:
          $ref: '#/components/schemas/KeyView'
        api_key:
          $ref: '#/components/schemas/ApiKeySecret'
        replaced:
          $ref: '#/components/schemas/KeyView'
    KeyLabel:
      type: string
      minLength: 1
      maxLength: 64
      pattern: ^[^\u0000-\u001F\u007F]{1,64}$
      description: 1–64 characters, without control characters.
    ErrorResponse:
      type: object
      additionalProperties: false
      required:
      - error
      properties:
        error:
          type: object
          additionalProperties: false
          required:
          - code
          - message
          properties:
            code:
              type: string
              enum:
              - invalid_request
              - siwe_invalid
              - oauth_invalid
              - login_code_invalid
              - unauthenticated
              - user_disabled
              - not_found
              - provider_disabled
              - method_not_allowed
              - identity_in_use
              - identity_limit_reached
              - last_identity
              - key_not_active
              - key_limit_reached
              - no_reset_available
              - payload_too_large
              - rate_limited
              - signup_rate_limited
              - signup_paused
              - usage_unavailable
              - service_unavailable
              - internal
            message:
              type: string
              description: Human-readable explanation, subject to wording adjustments.
            reason:
              type: string
              enum:
              - expired
              - chain_mismatch
              - domain_mismatch
              - signature
              - active_keys
              - daily_creations
              - invalid_username
              - nothing_to_reset
              - per_ip
              - global
              - cu_cap
              - expires_at
              - audience
              description: 'Machine-readable reason (siwe_invalid: expired, chain_mismatch, domain_mismatch, signature; key_limit_reached: active_keys; rate_limited: daily_creations for 24h key creation limit; invalid_request: invalid_username, cu_cap, expires_at; no_reset_available: nothing_to_reset when balance is already at or above target; signup_rate_limited: per_ip or global).'
            limit:
              type: integer
              description: Limit threshold value (e.g. active_keys limit for key_limit_reached).
  parameters:
    IncludeRevokedQuery:
      name: include_revoked
      in: query
      required: false
      description: '`true` to include revoked keys.'
      schema:
        type: boolean
        default: false
      example: true
    KeyIdPath:
      name: key_id
      in: path
      required: true
      schema:
        type: string
        pattern: ^k_[0-9a-f]{12}$
      example: k_0123456789ab
  requestBodies:
    KeyCreateBody:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/KeyCreateRequest'
          examples:
            reqKeyCreate:
              $ref: '#/components/examples/reqKeyCreate'
            reqKeyControlChar:
              $ref: '#/components/examples/reqKeyControlChar'
    KeyRenameBody:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/KeyLabelRequest'
          examples:
            reqKeyRename:
              $ref: '#/components/examples/reqKeyRename'
            reqKeyEmptyLabel:
              $ref: '#/components/examples/reqKeyEmptyLabel'
  responses:
    InternalError:
      description: Internal server error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            errInternal:
              $ref: '#/components/examples/errInternal'
    PayloadTooLarge:
      description: Request body exceeds 64 KiB limit. Exceeding this limit returns 413 (`payload_too_large`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            payloadTooLarge:
              $ref: '#/components/examples/payloadTooLarge'
    ServiceUnavailable:
      description: Service unavailable; includes Retry-After header.
      headers:
        Retry-After:
          description: Wait time before retrying, in seconds.
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            errServiceUnavailable:
              $ref: '#/components/examples/errServiceUnavailable'
    TooManyRequests:
      description: Rate limit exceeded.
      headers:
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            errRateLimited:
              $ref: '#/components/examples/errRateLimited'
    Unauthorized:
      description: Missing or invalid session (without distinguishing cause).
      headers:
        WWW-Authenticate:
          $ref: '#/components/headers/WwwAuthenticate'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            errUnauthenticated:
              $ref: '#/components/examples/errUnauthenticated'
            errUnauthenticatedLinkChallenge:
              $ref: '#/components/examples/errUnauthenticatedLinkChallenge'
  securitySchemes:
    SessionToken:
      type: http
      scheme: bearer
      bearerFormat: rgs_<64-character lowercase hex>
      description: '`Authorization: Bearer rgs_...`, issued by `POST /auth/siwe/login` or `POST /auth/oauth/token`. Absolute TTL 7 days,

        idle 24 hours. Invalid returns 401 `unauthenticated` with `WWW-Authenticate: Bearer`.

        '