openapi: 3.0.3
info:
  title: CDK Quota and Task API
  version: 1.5.0
  description: Unified API for dynamic channels, project-scoped integration contracts, extraction, protocol payment, CDK quota, queues, ledger, audit, persistent multi-project registration accounts, replaceable CDK entitlements, registration execution leases, and opaque project access sessions.
servers:
  - url: /
security:
  - bearerAuth: []
paths:
  /api/v1/channel-catalog:
    get:
      summary: Get the authenticated CDK's dynamic channel tree.
      description: The catalog is generated from the same cloud payment-mode source used by the WebUI. Clients should refresh this endpoint instead of scraping pages or copying cloud scripts.
      responses:
        "200":
          description: Dynamic channel catalog.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChannelCatalogResponse"
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
  /api/v1/projects:
    get:
      summary: Get projects visible to the authenticated task CDK.
      description: A normal CDK receives its single assigned project. An admin task CDK receives all active projects. Registration binding access still requires registration_scope=global for cross-project use. This endpoint never returns CDK plaintext or project secrets.
      responses:
        "200":
          description: Project visibility list.
          content:
            application/json:
              schema:
                type: object
                required: [ok, request_id, projects]
                properties:
                  ok: { type: boolean, example: true }
                  request_id: { type: string, example: req_xxx }
                  projects:
                    type: array
                    items: { $ref: "#/components/schemas/Project" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
  /api/v1/projects/{project_key}/integration:
    get:
      summary: Read the authenticated project's integration contract.
      description: A normal project CDK can only read its own contract. An admin_cdk with registration_scope=global may read any active registration project contract. The response contains no CDK plaintext, administrator key, token, proxy credential or session material.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
      responses:
        "200":
          description: Project-specific endpoint contract and CDK scope.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ProjectIntegrationContract" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
  /api/v1/projects/{project_key}/capabilities:
    get:
      summary: Read explicit machine-readable capabilities for one project.
      description: Use this response to select endpoints and authentication. Do not scrape the WebUI or infer support by comparing OpenAPI path strings. This read-only request does not start CDK validity.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
      responses:
        "200":
          description: Project capability discovery response.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ProjectCapabilitiesResponse" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
  /api/v1/preflight:
    post:
      summary: Validate channel, task type, network and CDK capacity without creating a task or reserving quota.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PreflightRequest"
            examples:
              brazilProtocol:
                value:
                  channel_id: paypal.us-usd.br-proxy
                  channel_revision: channel-r1
                  task_type: protocol
                  payload:
                    payment_mode: PAYPAL全球轮转
                    network_profile_id: br-payment-a
      responses:
        "200":
          description: Valid preflight result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PreflightResponse"
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
        "422": { $ref: "#/components/responses/Error" }
        "429": { $ref: "#/components/responses/Error" }
  /api/v1/cdk/status:
    get:
      summary: Get current CDK state, remaining quota, reservations, concurrency, allowed channels and expiration.
      description: When project_key is supplied, the server is authoritative for project access. A platform master or global-scope admin_cdk receives the requested project as the effective project while card_project_key preserves the card's issuing project. Clients must not infer access from a CDK prefix, length, or card_project_key.
      parameters:
        - in: query
          name: project_key
          required: false
          description: Requested project to validate without starting CDK validity.
          schema: { type: string, example: registration_core }
      responses:
        "200":
          description: CDK status.
          content:
            application/json:
              schema:
                type: object
                required: [ok, request_id, cdk, api, capabilities]
                properties:
                  ok: { type: boolean, example: true }
                  request_id: { type: string, example: req_xxx }
                  cdk: { $ref: "#/components/schemas/CdkQuotaStatus" }
                  card:
                    type: object
                    description: Backward-compatible detailed card object.
                  quota: { $ref: "#/components/schemas/Quota" }
                  api: { $ref: "#/components/schemas/ApiDiscovery" }
                  capabilities:
                    type: object
                    additionalProperties: true
                    description: Project capabilities when project_key is supplied; otherwise a discovery hint.
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
  /api/v1/tasks:
    post:
      summary: Submit one unified task. Recommended local-register flow is channel=brazil_pp and task_type=protocol.
      description: Local clients must not pass raw payment URLs. If a split flow is required, pass source_task_id and let the remote platform read the extraction result.
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TaskSubmitRequest"
            examples:
              brazilProtocol:
                value:
                  channel_id: paypal.us-usd.br-proxy
                  channel_revision: channel-r1
                  task_type: protocol
                  payload:
                    account:
                      email: user@example.com
                      access_token: <ACCESS_TOKEN>
                      refresh_token: <REFRESH_TOKEN_OPTIONAL>
                    payment_mode: PAYPAL全球轮转
                    network_profile_id: br-payment-a
                    client_run_id: local-run-uuid
                    source_type: local_register
              splitPayment:
                value:
                  channel: paypal_protocol
                  task_type: protocol
                  payload:
                    source_task_id: task_xxx
                    account:
                      email: user@example.com
                      access_token: <ACCESS_TOKEN>
      responses:
        "200":
          description: Queued task or idempotent replay.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TaskSubmitResponse"
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
        "422": { $ref: "#/components/responses/Error" }
        "429": { $ref: "#/components/responses/Error" }
  /api/v1/link-tasks:
    post:
      summary: Backward-compatible payment-link extraction submit endpoint.
      description: This endpoint remains compatible with existing callers. New local register integrations should prefer POST /api/v1/tasks.
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                channel: { type: string, example: payment_link }
                payment_mode: { type: string, example: PAYPAL全球轮转 }
                session_pool: { type: string, description: Legacy session/AT pool input. Prefer payload.account in /api/v1/tasks. }
                payment_proxy_pool: { type: string }
                provider_proxy_pool: { type: string }
                max_attempts: { type: integer, example: 10 }
      responses:
        "200":
          description: Queued task or idempotent replay with legacy-compatible fields.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TaskSubmitResponse"
  /api/v1/tasks/{task_id}:
    get:
      summary: Query a task with stage-by-stage status.
      parameters:
        - $ref: "#/components/parameters/TaskId"
      responses:
        "200":
          description: Task status.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TaskQueryResponse"
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
  /api/v1/tasks/{task_id}/cancel:
    post:
      summary: Cancel a task idempotently. Reserved quota is released unless extraction already succeeded and was charged.
      parameters:
        - $ref: "#/components/parameters/TaskId"
      responses:
        "200":
          description: Cancelled task or existing terminal task.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TaskQueryResponse"
  /api/v1/admin/cdks:
    get:
      summary: List CDKs. super_admin only.
      parameters:
        - in: query
          name: role
          schema: { type: string, enum: [customer_cdk, admin_cdk] }
        - in: query
          name: status
          schema: { type: string }
        - in: query
          name: project_key
          schema: { type: string }
        - in: query
          name: q
          schema: { type: string }
        - in: query
          name: limit
          description: 每页返回数量，默认 100，最大 200。
          schema: { type: integer, minimum: 1, maximum: 200, default: 100 }
        - in: query
          name: offset
          description: 从第几条匹配结果开始读取，默认 0。
          schema: { type: integer, minimum: 0, default: 0 }
      responses:
        "200":
          description: 保留 cards 字段以兼容旧调用方，并返回分页信息。
          content:
            application/json:
              schema:
                type: object
                required: [ok, cards, total, limit, offset, has_more]
                properties:
                  ok: { type: boolean, example: true }
                  cards:
                    type: array
                    description: 当前页 CDK。仅含脱敏卡号和指纹，不含明文 CDK。
                    items: { type: object, additionalProperties: true }
                  total: { type: integer, example: 253 }
                  limit: { type: integer, example: 50 }
                  offset: { type: integer, example: 0 }
                  has_more: { type: boolean, example: true }
    post:
      summary: Create one customer/admin CDK by project. Plain CDK is returned once.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string }
                quota: { type: integer }
                max_concurrency: { type: integer }
                expires_at: { type: string, nullable: true }
                validity_seconds:
                  type: integer
                  description: Duration counted from the first successful CDK use. 0 means permanent or fixed expires_at mode.
                validity_label:
                  type: string
                  example: 7天
                project_key:
                  type: string
                  example: extract_platform
                  description: Active project id from GET /api/v1/admin/projects. A normal CDK is permanently scoped to this project.
                project_name:
                  type: string
                  readOnly: true
                  description: Server-resolved project name. The submitted value is ignored for normal CDKs.
                  example: 提炼总平台
                channels:
                  type: array
                  items: { type: string }
                  deprecated: true
                  description: Legacy compatibility field. New project cards use all channels belonging to the selected project.
                role: { type: string, enum: [customer_cdk, admin_cdk] }
                registration_scope:
                  type: string
                  enum: [project, global]
                  default: project
                  description: Only applies to admin_cdk registration access. global must be explicitly requested and permits binding in every active registration project; it does not grant super-admin APIs.
      responses:
        "200": { description: OK }
  /api/v1/admin/cdks/batch:
    post:
      summary: Create many customer CDKs. Plain CDKs are returned once.
      responses:
        "200": { description: OK }
  /api/v1/admin/cdks/topup:
    post:
      summary: Extend one customer CDK by internal card id or complete CDK value without creating a replacement card.
      description: The lookup value is only used in memory and is never written to the ledger, audit event, or response body.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CdkTopupLookupRequest" }
      responses:
        "200":
          description: Original card extended successfully.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CdkTopupResponse" }
        "403": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
        "422": { $ref: "#/components/responses/Error" }
  /api/v1/admin/projects:
    get:
      summary: List CDK projects. super_admin only.
      parameters:
        - in: query
          name: include_retired
          schema: { type: boolean, default: true }
      responses:
        "200":
          description: Project management list with CDK counts.
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean, example: true }
                  projects:
                    type: array
                    items: { $ref: "#/components/schemas/Project" }
    post:
      summary: Create a project before issuing project-scoped CDKs. super_admin only.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ProjectInput" }
      responses:
        "200": { description: Created project }
        "409": { $ref: "#/components/responses/Error" }
        "422": { $ref: "#/components/responses/Error" }
  /api/v1/admin/projects/{project_key}:
    post:
      summary: Rename, update, pause, restore or retire a project. super_admin only.
      parameters:
        - in: path
          name: project_key
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ProjectInput" }
      responses:
        "200": { description: Updated project }
        "404": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
  /api/v1/admin/projects/{project_key}/summary:
    get:
      summary: Get project-scoped card, task and registration summary. super_admin only.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
      responses:
        "200":
          description: Project summary for the card-library management view.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ProjectSummaryResponse" }
        "404": { $ref: "#/components/responses/Error" }
  /api/v1/admin/projects/{project_key}/integration:
    get:
      summary: Get administrator-facing project integration paths. super_admin only.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
      responses:
        "200":
          description: Project metadata and management paths.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AdminProjectIntegrationResponse" }
        "404": { $ref: "#/components/responses/Error" }
  /api/v1/admin/projects/{project_key}/registration/topup:
    post:
      summary: Extend the current CDK bound to one registration account without replacing its binding.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/RegistrationAccountTopupRequest" }
      responses:
        "200":
          description: Bound account and its original CDK were extended.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CdkTopupResponse" }
        "403": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
        "422": { $ref: "#/components/responses/Error" }
  /api/v1/admin/cdks/merge:
    post:
      summary: Atomically merge multiple customer CDKs into one new customer CDK.
      description: All source cards must belong to the same active project and have no active tasks or reserved quota. The target project is inherited from the source cards.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [source_ids]
              properties:
                source_ids:
                  type: array
                  items: { type: integer }
                name: { type: string }
      responses:
        "200": { description: OK }
        "409": { $ref: "#/components/responses/Error" }
  /api/v1/admin/live-events:
    get:
      summary: Stream real-time queue and masked task snapshots for the management console.
      description: Uses Server-Sent Events. super_admin and operator are allowed. The stream is read-only and emits one snapshot per second.
      responses:
        "200":
          description: text/event-stream snapshots.
          content:
            text/event-stream:
              schema:
                type: string
  /api/v1/admin/cdks/{id}/disable:
    post:
      summary: Disable a CDK.
      parameters: [{ $ref: "#/components/parameters/CdkId" }]
      responses: { "200": { description: OK } }
  /api/v1/admin/cdks/{id}/restore:
    post:
      summary: Restore a disabled CDK.
      parameters: [{ $ref: "#/components/parameters/CdkId" }]
      responses: { "200": { description: OK } }
  /api/v1/admin/cdks/{id}/revoke:
    post:
      summary: Revoke a CDK permanently.
      parameters: [{ $ref: "#/components/parameters/CdkId" }]
      responses: { "200": { description: OK } }
  /api/v1/admin/cdks/{id}/rotate:
    post:
      summary: Rotate a CDK. New plaintext is returned once.
      parameters: [{ $ref: "#/components/parameters/CdkId" }]
      responses: { "200": { description: OK } }
  /api/v1/admin/cdks/{id}/topup:
    post:
      summary: Extend one customer CDK by its internal id without creating a replacement card.
      parameters: [{ $ref: "#/components/parameters/CdkId" }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CdkTopupRequest" }
      responses:
        "200":
          description: Original card extended successfully.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CdkTopupResponse" }
        "403": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
        "422": { $ref: "#/components/responses/Error" }
  /api/v1/admin/cdks/{id}/ledger:
    get:
      summary: Get quota ledger for one CDK.
      parameters: [{ $ref: "#/components/parameters/CdkId" }]
      responses: { "200": { description: OK } }
  /api/v1/admin/tasks:
    get:
      summary: List recent tasks. super_admin/operator only.
      responses: { "200": { description: OK } }
  /api/v1/admin/audit-events:
    get:
      summary: Get append-only audit events.
      responses: { "200": { description: OK } }
  /api/v1/admin/queue-status:
    get:
      summary: Get queue and worker status.
      responses: { "200": { description: OK } }
  /api/v1/admin/queue-settings:
    post:
      summary: Update web/api/admin_api worker limits.
      responses: { "200": { description: OK } }
  /api/v1/admin/api-key/rotate:
    post:
      summary: Rotate super_admin API key. Plain key is returned once.
      responses: { "200": { description: OK } }
  /api/v1/errors:
    get:
      summary: Stable and legacy error code table.
      responses: { "200": { description: OK } }
  /api/v1/integration-guide:
    get:
      summary: Human-readable dynamic API integration guide.
      responses:
        "200":
          description: Markdown guide.
          content:
            text/markdown:
              schema: { type: string }
  /api/v1/integration-guide/latest:
    get:
      summary: Latest versioned API integration guide for new project clients (v1.5.0).
      responses:
        "200":
          description: Latest Markdown integration guide.
          content:
            text/markdown:
              schema: { type: string }
  /api/v1/integration-guide/v1.4.0:
    get:
      summary: CDK Quota and Task API v1.4.0 guide with registration execution leases.
      responses:
        "200":
          description: Versioned Markdown integration guide.
          content:
            text/markdown:
              schema: { type: string }
  /api/v1/integration-guide/v1.5.0:
    get:
      summary: CDK Quota and Task API v1.5.0 guide with unified project access sessions.
      responses:
        "200":
          description: Versioned Markdown integration guide.
          content:
            text/markdown:
              schema: { type: string }
  /api/v1/integration-guide/v1.3.0:
    get:
      summary: CDK Quota and Task API v1.3.0 guide with persistent registration accounts and replaceable CDK entitlements.
      responses:
        "200":
          description: Versioned Markdown integration guide.
          content:
            text/markdown:
              schema: { type: string }
  /api/v1/integration-guide/v1.2.4:
    get:
      summary: CDK Quota and Task API v1.2.4 integration guide with image-generation registration project isolation.
      responses:
        "200":
          description: Versioned Markdown integration guide.
          content:
            text/markdown:
              schema: { type: string }
  /api/v1/integration-guide/v1.2.3:
    get:
      summary: Frozen CDK Quota and Task API v1.2.3 integration guide with global registration admin CDK rules.
      responses:
        "200":
          description: Versioned Markdown integration guide.
          content:
            text/markdown:
              schema: { type: string }
  /api/v1/integration-guide/v1.2.1:
    get:
      summary: Frozen CDK Quota and Task API v1.2.1 integration guide.
      responses:
        "200":
          description: Versioned Markdown integration guide.
          content:
            text/markdown:
              schema: { type: string }
  /api/v1/integration-guide/v1.2.2:
    get:
      summary: Frozen CDK Quota and Task API v1.2.2 integration guide.
      responses:
        "200":
          description: Versioned Markdown integration guide.
          content:
            text/markdown:
              schema: { type: string }
  /api/v1/registration-core-guide:
    get:
      summary: Human-readable GPTFREE registration CDK compatibility guide.
      responses:
        "200":
          description: Markdown guide.
          content:
            text/markdown:
              schema: { type: string }
  /api/v1/registration-integration-guide:
    get:
      summary: Generic registration project integration guide for GPTFREE, image-generation and future projects.
      responses:
        "200":
          description: Persistent account and replaceable CDK integration guide.
          content:
            text/markdown:
              schema: { type: string }
  /api/v1/image-generation-registration-guide:
    get:
      summary: Human-readable image-generation registration CDK integration guide.
      description: Fixed project_key is image_generation_registration. Normal CDKs and project client keys are isolated from GPTFREE registration_core.
      responses:
        "200":
          description: Markdown guide for image-generation registration integration.
          content:
            text/markdown:
              schema: { type: string }
  /api/v1/project-integration-guide:
    get:
      summary: Human-readable multi-project onboarding guide and developer prompt.
      responses:
        "200":
          description: Markdown guide.
          content:
            text/markdown:
              schema: { type: string }
  /api/v1/registration-core/activate:
    post:
      security: []
      summary: Bind one local registration user to a registration-core CDK.
      description: The local client sends username and a client-generated reference. It never sends the local password or workspace contents. The CDK validity clock starts on the first successful activation.
      parameters:
        - $ref: "#/components/parameters/RegistrationClientKey"
        - $ref: "#/components/parameters/RegistrationTimestamp"
        - $ref: "#/components/parameters/RegistrationSignature"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/RegistrationActivateRequest" }
            example:
              cdk: REGISTRATION_CDK_FROM_ONE_TIME_RESULT
              username: local-user
              client_user_ref: local-user-001
              device_summary: optional-local-device-label
      responses:
        "200":
          description: Binding and short-lived signed authorization.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationAuthorizationResponse" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
        "422": { $ref: "#/components/responses/Error" }
  /api/v1/registration-core/authorize:
    post:
      security: []
      summary: Refresh authorization for an existing local registration binding.
      parameters:
        - $ref: "#/components/parameters/RegistrationClientKey"
        - $ref: "#/components/parameters/RegistrationTimestamp"
        - $ref: "#/components/parameters/RegistrationSignature"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/RegistrationAuthorizeRequest" }
      responses:
        "200":
          description: Refreshed signed authorization.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationAuthorizationResponse" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
  /api/v1/registration-core/workspace:
    post:
      security: []
      summary: Update remote workspace heartbeat metadata only.
      description: Only timestamps and a short device summary are accepted. Local passwords, workspace files and session data stay local.
      parameters:
        - $ref: "#/components/parameters/RegistrationClientKey"
        - $ref: "#/components/parameters/RegistrationTimestamp"
        - $ref: "#/components/parameters/RegistrationSignature"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/RegistrationWorkspaceRequest" }
      responses:
        "200":
          description: Updated binding metadata.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationWorkspaceResponse" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
  /api/v1/projects/{project_key}/registration/activate:
    post:
      security: []
      summary: Bind a local user to a registration-enabled project's CDK. Accepts the project's normal CDK, an explicit global registration admin_cdk, or the platform master CDK.
      description: Each registration project has an independent client key and HMAC scope. For image-generation registration use project_key=image_generation_registration and its own project client key; a GPTFREE ordinary CDK or client key is rejected rather than remapped.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
        - $ref: "#/components/parameters/RegistrationClientKey"
        - $ref: "#/components/parameters/RegistrationTimestamp"
        - $ref: "#/components/parameters/RegistrationSignature"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/RegistrationActivateRequest" }
            examples:
              imageGenerationRegistration:
                summary: First activation for the image-generation registration project.
                value:
                  cdk: IMAGE_GENERATION_REGISTRATION_CDK
                  username: local-user
                  client_user_ref: image-local-user-001
                  device_summary: optional-local-device-label
      responses:
        "200":
          description: Binding and short-lived authorization.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationAuthorizationResponse" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
        "422": { $ref: "#/components/responses/Error" }
  /api/v1/projects/{project_key}/registration/login:
    post:
      security: []
      summary: Unified project login using one project CDK input.
      description: The project client signs this request with its own project client key. A successful first login starts the CDK validity clock, creates or restores the durable account binding, and returns one opaque registration access token. The token is never stored in plaintext by the server and must not be shown in a browser.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
        - $ref: "#/components/parameters/RegistrationClientKey"
        - $ref: "#/components/parameters/RegistrationTimestamp"
        - $ref: "#/components/parameters/RegistrationSignature"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/RegistrationAccessLoginRequest" }
            example:
              cdk: PROJECT_CDK_FROM_USER
              username: local-user
              client_user_ref: stable-project-user-001
              device_summary: optional-safe-device-label
      responses:
        "200":
          description: Project account, binding and one-time opaque access session.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationAccessLoginResponse" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
        "422": { $ref: "#/components/responses/Error" }
  /api/v1/projects/{project_key}/registration/client-preflight:
    post:
      security: []
      summary: Validate a project client key and CDK without starting validity or creating a binding.
      description: The project backend must call this endpoint before registration/login. It requires the project-scoped HMAC headers and a Bearer CDK, returns only masked readiness data, and does not create an account, binding, session, quota reservation or lease. A platform master CDK still requires the client key belonging to this project.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
        - $ref: "#/components/parameters/RegistrationClientKey"
        - $ref: "#/components/parameters/RegistrationTimestamp"
        - $ref: "#/components/parameters/RegistrationSignature"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/RegistrationClientPreflightRequest" }
            example: {}
      responses:
        "200":
          description: Project client key and CDK are ready. No validity clock or binding was started.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationClientPreflightResponse" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
        "422": { $ref: "#/components/responses/Error" }
  /api/v1/projects/{project_key}/registration/session/status:
    post:
      security:
        - bearerAuth: []
      summary: Read the current project access session and durable account status.
      description: The Bearer value is the opaque registration_access_token returned by registration/login, not a CDK. The body is {} and is included in the HMAC signature. An expired session still returns its masked status so the client can show the rebind flow.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
        - $ref: "#/components/parameters/RegistrationClientKey"
        - $ref: "#/components/parameters/RegistrationTimestamp"
        - $ref: "#/components/parameters/RegistrationSignature"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/RegistrationSessionEmptyRequest" }
            example: {}
      responses:
        "200":
          description: Session and account state.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationSessionStatusResponse" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
  /api/v1/projects/{project_key}/registration/session/heartbeat:
    post:
      security:
        - bearerAuth: []
      summary: Validate and refresh the last-seen time of a project access session.
      description: Heartbeat does not extend CDK validity and does not reserve quota. It succeeds only while the bound CDK and account are active.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
        - $ref: "#/components/parameters/RegistrationClientKey"
        - $ref: "#/components/parameters/RegistrationTimestamp"
        - $ref: "#/components/parameters/RegistrationSignature"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/RegistrationSessionEmptyRequest" }
            example: {}
      responses:
        "200":
          description: Current active session and account state.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationSessionStatusResponse" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
  /api/v1/projects/{project_key}/registration/session/logout:
    post:
      security:
        - bearerAuth: []
      summary: Close one project access session without deleting the account.
      description: Logout is idempotent and never deletes the durable registration account or its configuration.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
        - $ref: "#/components/parameters/RegistrationClientKey"
        - $ref: "#/components/parameters/RegistrationTimestamp"
        - $ref: "#/components/parameters/RegistrationSignature"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/RegistrationSessionEmptyRequest" }
            example: {}
      responses:
        "200":
          description: Closed session state.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationSessionLogoutResponse" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
  /api/v1/projects/{project_key}/registration/authorize:
    post:
      security: []
      summary: Refresh a local authorization for a registration-enabled project. The binding remains tied to its original project and client key.
      description: Use the same project_key, binding_id, client_user_ref and project-scoped client key that were used for activation. A cross-project binding or client key is rejected.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
        - $ref: "#/components/parameters/RegistrationClientKey"
        - $ref: "#/components/parameters/RegistrationTimestamp"
        - $ref: "#/components/parameters/RegistrationSignature"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/RegistrationAuthorizeRequest" }
      responses:
        "200":
          description: Refreshed authorization.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationAuthorizationResponse" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
  /api/v1/projects/{project_key}/registration/workspace:
    post:
      security: []
      summary: Update one registration-enabled project's local heartbeat metadata. The binding remains tied to its original project and client key.
      description: This endpoint accepts heartbeat metadata only. It does not accept local workspace content, password, session, account token, cookie, proxy or verification code.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
        - $ref: "#/components/parameters/RegistrationClientKey"
        - $ref: "#/components/parameters/RegistrationTimestamp"
        - $ref: "#/components/parameters/RegistrationSignature"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/RegistrationWorkspaceRequest" }
      responses:
        "200":
          description: Updated binding metadata.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationWorkspaceResponse" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
  /api/v1/projects/{project_key}/registration/leases:
    post:
      security:
        - bearerAuth: []
      summary: Reserve quota and one or more execution units for a real registration job.
      description: Registration leases are the authoritative admission gate for local registration workers. Bearer may be the v1.4 project CDK or the v1.5 opaque registration_access_token. A reservation is released on failure, cancellation, timeout or restart, and is charged only when the client commits a successful registration.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
        - $ref: "#/components/parameters/IdempotencyKey"
        - $ref: "#/components/parameters/RegistrationClientKey"
        - $ref: "#/components/parameters/RegistrationTimestamp"
        - $ref: "#/components/parameters/RegistrationSignature"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/RegistrationLeaseRequest" }
            example:
              binding_id: regbind_xxx
              client_user_ref: image-admin-42
              task_source: auto_refill
              client_run_id: local-run-uuid
              units: 1
              lease_ttl_seconds: 120
              metadata:
                template_version: image-register-v3
      responses:
        "200":
          description: New or idempotently replayed registration lease.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationLeaseResponse" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
        "402": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
        "429": { $ref: "#/components/responses/Error" }
  /api/v1/projects/{project_key}/registration/leases/{lease_id}:
    get:
      security:
        - bearerAuth: []
      summary: Read one registration lease and the current quota snapshot. Bearer may be a CDK or an opaque project access session.
      description: The owner may read a closed lease after CDK expiry or revocation so the local worker can stop cleanly. No secret or workspace content is returned.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
        - $ref: "#/components/parameters/LeaseId"
        - $ref: "#/components/parameters/RegistrationClientKey"
        - $ref: "#/components/parameters/RegistrationTimestamp"
        - $ref: "#/components/parameters/RegistrationSignature"
      responses:
        "200":
          description: Lease state.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationLeaseResponse" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
  /api/v1/projects/{project_key}/registration/leases/{lease_id}/heartbeat:
    post:
      security:
        - bearerAuth: []
      summary: Extend an active registration lease.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
        - $ref: "#/components/parameters/LeaseId"
        - $ref: "#/components/parameters/RegistrationClientKey"
        - $ref: "#/components/parameters/RegistrationTimestamp"
        - $ref: "#/components/parameters/RegistrationSignature"
      responses:
        "200":
          description: Updated running lease.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationLeaseResponse" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
  /api/v1/projects/{project_key}/registration/leases/{lease_id}/commit:
    post:
      security:
        - bearerAuth: []
      summary: Commit a genuinely successful registration and charge reserved quota.
      description: This endpoint is idempotent. It must be called only after the local project has verified the account is usable. It never accepts raw account secrets or session data.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
        - $ref: "#/components/parameters/LeaseId"
        - $ref: "#/components/parameters/RegistrationClientKey"
        - $ref: "#/components/parameters/RegistrationTimestamp"
        - $ref: "#/components/parameters/RegistrationSignature"
      requestBody:
        required: false
        content:
          application/json:
            schema: { $ref: "#/components/schemas/RegistrationLeaseCommitRequest" }
      responses:
        "200":
          description: Committed lease and updated quota.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationLeaseResponse" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
  /api/v1/projects/{project_key}/registration/leases/{lease_id}/release:
    post:
      security:
        - bearerAuth: []
      summary: Release a failed, cancelled, timed-out or recovered registration lease.
      description: Release is idempotent and may be called for cleanup after CDK expiry or revocation. A committed lease is never refunded.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
        - $ref: "#/components/parameters/LeaseId"
        - $ref: "#/components/parameters/RegistrationClientKey"
        - $ref: "#/components/parameters/RegistrationTimestamp"
        - $ref: "#/components/parameters/RegistrationSignature"
      requestBody:
        required: false
        content:
          application/json:
            schema: { $ref: "#/components/schemas/RegistrationLeaseReleaseRequest" }
      responses:
        "200":
          description: Released lease and updated quota.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationLeaseResponse" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
  /api/v1/projects/{project_key}/registration/account/status:
    post:
      security: []
      summary: Read a persistent registration account even when its current CDK is expired.
      description: The project HMAC client key is required, but the expired task CDK is not. An expired entitlement does not delete or disable the account.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
        - $ref: "#/components/parameters/RegistrationClientKey"
        - $ref: "#/components/parameters/RegistrationTimestamp"
        - $ref: "#/components/parameters/RegistrationSignature"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/RegistrationAccountStatusRequest" }
      responses:
        "200":
          description: Account and current entitlement status.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationAccountStatusResponse" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
  /api/v1/projects/{project_key}/registration/rebind:
    post:
      security: []
      summary: Bind an existing account to a new project CDK.
      description: The account identity and configuration remain unchanged. The old binding is marked replaced. The new CDK plaintext is accepted only in this request and is never persisted.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
        - $ref: "#/components/parameters/RegistrationClientKey"
        - $ref: "#/components/parameters/RegistrationTimestamp"
        - $ref: "#/components/parameters/RegistrationSignature"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/RegistrationRebindRequest" }
      responses:
        "200":
          description: New binding and short-lived authorization.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationRebindResponse" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
  /api/v1/projects/{project_key}/registration/accounts/{client_user_ref}/config:
    get:
      security: []
      summary: Read one account's profile and client-encrypted configuration.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
        - in: path
          name: client_user_ref
          required: true
          schema: { type: string }
        - $ref: "#/components/parameters/RegistrationClientKey"
        - $ref: "#/components/parameters/RegistrationTimestamp"
        - $ref: "#/components/parameters/RegistrationSignature"
      responses:
        "200":
          description: Versioned account configuration.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationAccountConfigResponse" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
    put:
      security: []
      summary: Atomically update account configuration with optimistic version control.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
        - in: path
          name: client_user_ref
          required: true
          schema: { type: string }
        - $ref: "#/components/parameters/RegistrationClientKey"
        - $ref: "#/components/parameters/RegistrationTimestamp"
        - $ref: "#/components/parameters/RegistrationSignature"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/RegistrationAccountConfigUpdate" }
      responses:
        "200":
          description: New configuration version.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationAccountConfigResponse" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
        "422": { $ref: "#/components/responses/Error" }
  /api/v1/admin/projects/{project_key}/registration/overview:
    get:
      summary: Get project-scoped registration authorization counters. super_admin only.
      parameters: [{ $ref: "#/components/parameters/ProjectKey" }]
      responses:
        "200":
          description: Project registration counters.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationOverviewResponse" }
  /api/v1/admin/projects/{project_key}/registration/users:
    get:
      summary: List project-scoped registration bindings. super_admin only.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
        - in: query
          name: q
          schema: { type: string }
        - in: query
          name: status
          schema: { type: string, enum: [active, expiring, expired, disabled, revoked, replaced] }
        - in: query
          name: limit
          schema: { type: integer, minimum: 1, maximum: 200, default: 100 }
        - in: query
          name: offset
          schema: { type: integer, minimum: 0, default: 0 }
      responses:
        "200":
          description: Project-scoped masked registration bindings.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationUsersResponse" }
  /api/v1/admin/projects/{project_key}/registration/accounts:
    get:
      summary: List durable registration accounts without configuration secrets. super_admin only.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
        - in: query
          name: q
          schema: { type: string }
        - in: query
          name: status
          schema: { type: string, enum: [active, disabled, archived] }
        - in: query
          name: limit
          schema: { type: integer, minimum: 1, maximum: 200, default: 100 }
        - in: query
          name: offset
          schema: { type: integer, minimum: 0, default: 0 }
      responses:
        "200":
          description: Project-scoped persistent account list.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationAccountListResponse" }
  /api/v1/admin/projects/{project_key}/registration/accounts/{client_user_ref}/history:
    get:
      summary: List all masked CDK bindings for one durable account. super_admin only.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
        - in: path
          name: client_user_ref
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Account and binding history.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationAccountHistoryResponse" }
  /api/v1/admin/projects/{project_key}/registration/accounts/{client_user_ref}/disable:
    post:
      summary: Disable a durable account without deleting its data. super_admin only.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
        - in: path
          name: client_user_ref
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Account disabled.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationAccountStatusResponse" }
  /api/v1/admin/projects/{project_key}/registration/accounts/{client_user_ref}/restore:
    post:
      summary: Restore a disabled durable account. super_admin only.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
        - in: path
          name: client_user_ref
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Account restored.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationAccountStatusResponse" }
  /api/v1/admin/projects/{project_key}/registration/accounts/{client_user_ref}/issue-cdk:
    post:
      summary: Issue one replacement project CDK for a durable account. Plaintext is returned once. super_admin only.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
        - in: path
          name: client_user_ref
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/RegistrationAccountIssueCdkRequest" }
      responses:
        "200":
          description: New CDK for later rebind.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationAccountIssueCdkResponse" }
        "403": { $ref: "#/components/responses/Error" }
        "404": { $ref: "#/components/responses/Error" }
        "422": { $ref: "#/components/responses/Error" }
  /api/v1/admin/projects/{project_key}/registration/cdks/{id}/renew:
    post:
      summary: Issue a renewal CDK for one project-scoped registration binding. super_admin only.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
        - $ref: "#/components/parameters/CdkId"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/RegistrationRenewalInput" }
      responses:
        "200":
          description: One-time renewal CDK response.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationRenewalResponse" }
  /api/v1/admin/projects/{project_key}/registration/client-keys:
    get:
      summary: List masked project-scoped registration client keys. super_admin only.
      parameters: [{ $ref: "#/components/parameters/ProjectKey" }]
      responses:
        "200":
          description: Masked client key records.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationClientKeyListResponse" }
    post:
      summary: Create one project-scoped registration client key. super_admin only.
      parameters: [{ $ref: "#/components/parameters/ProjectKey" }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/RegistrationClientKeyInput" }
      responses:
        "200":
          description: One-time client key response.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationClientKeyResponse" }
  /api/v1/admin/projects/{project_key}/registration/client-keys/{id}/rotate:
    post:
      summary: Rotate one project-scoped registration client key. super_admin only.
      parameters:
        - $ref: "#/components/parameters/ProjectKey"
        - in: path
          name: id
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: One-time replacement client key response.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationClientKeyResponse" }
  /api/v1/admin/registration-core/overview:
    get:
      summary: Get registration-core activation counters. super_admin only.
      responses:
        "200":
          description: Activation counters.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationOverviewResponse" }
  /api/v1/admin/registration-core/users:
    get:
      summary: List registration-core user bindings with masked CDK information. super_admin only.
      parameters:
        - in: query
          name: q
          schema: { type: string }
        - in: query
          name: status
          schema: { type: string, enum: [active, expiring, expired, disabled, revoked, replaced] }
        - in: query
          name: limit
          schema: { type: integer, minimum: 1, maximum: 200, default: 100 }
        - in: query
          name: offset
          schema: { type: integer, minimum: 0, default: 0 }
      responses:
        "200":
          description: Masked user bindings.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationUsersResponse" }
  /api/v1/admin/registration-core/cdks/{id}/renew:
    post:
      summary: Issue a one-time plaintext renewal CDK for an existing registration binding. super_admin only.
      parameters: [{ $ref: "#/components/parameters/CdkId" }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                validity_seconds: { type: integer, minimum: 1, example: 2592000 }
                validity_label: { type: string, example: 1个月 }
                expires_at: { type: string, nullable: true }
                name: { type: string }
      responses:
        "200":
          description: New CDK plaintext is returned once.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationRenewalResponse" }
  /api/v1/admin/registration-core/client-keys:
    get:
      summary: List local registration client keys without plaintext. super_admin only.
      responses:
        "200":
          description: Masked client-key records.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationClientKeyListResponse" }
    post:
      summary: Create a local registration client key. Plaintext is returned once. super_admin only.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties: { name: { type: string, example: local-register-client } }
      responses:
        "200":
          description: One-time client key response.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationClientKeyResponse" }
  /api/v1/admin/registration-core/client-keys/{id}/rotate:
    post:
      summary: Revoke a local registration client key and issue a replacement. super_admin only.
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Replacement plaintext is returned once.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RegistrationClientKeyResponse" }
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
  parameters:
    IdempotencyKey:
      in: header
      name: Idempotency-Key
      required: true
      schema: { type: string, minLength: 8 }
      description: Caller-stable unique key. Same CDK and same key returns the same task and never repeats reservation.
    TaskId:
      in: path
      name: task_id
      required: true
      schema: { type: string, example: task_xxx }
    LeaseId:
      in: path
      name: lease_id
      required: true
      schema: { type: string, example: reglease_xxx }
      description: Opaque registration execution lease identifier.
    CdkId:
      in: path
      name: id
      required: true
      schema: { type: integer }
    ProjectKey:
      in: path
      name: project_key
      required: true
      schema:
        type: string
        pattern: '^[A-Za-z0-9_.-]+$'
      description: Stable project identifier. It is never changed or reused.
    RegistrationClientKey:
      in: header
      name: X-Registration-Client-Key
      required: true
      schema: { type: string }
      description: Plain client key returned once by the admin client-key endpoint. Never store it in source control or logs.
    RegistrationTimestamp:
      in: header
      name: X-Registration-Timestamp
      required: true
      schema: { type: string, pattern: '^[0-9]+$' }
      description: Unix seconds. Requests outside the five-minute clock-skew window are rejected.
    RegistrationSignature:
      in: header
      name: X-Registration-Signature
      required: true
      schema: { type: string }
      description: HMAC-SHA256 of timestamp.METHOD.path.sha256(canonical-json-body), keyed by the local registration client key.
  responses:
    Error:
      description: Unified error response.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
  schemas:
    RegistrationLeaseRequest:
      type: object
      required: [binding_id, client_user_ref]
      properties:
        binding_id: { type: string, example: regbind_xxx }
        client_user_ref: { type: string, example: image-admin-42 }
        task_source: { type: string, example: auto_refill, default: manual }
        client_run_id: { type: string, example: local-run-uuid }
        units: { type: integer, minimum: 1, maximum: 100, default: 1 }
        lease_ttl_seconds: { type: integer, minimum: 30, maximum: 900, default: 120 }
        metadata:
          type: object
          additionalProperties: true
          description: Safe version/source metadata only. Never include credentials, sessions, proxies, OTPs or payment URLs.
      description: Requires both Bearer CDK authentication and project HMAC headers. The binding must belong to this project and client_user_ref.
    RegistrationLeaseCommitRequest:
      type: object
      properties:
        success: { type: boolean, enum: [true], default: true }
        result:
          type: object
          additionalProperties: true
          description: Safe result summary only. Do not include account secrets or session material.
    RegistrationLeaseReleaseRequest:
      type: object
      properties:
        reason: { type: string, example: upstream_timeout, maxLength: 80 }
    RegistrationLease:
      type: object
      required: [lease_id, project_key, binding_id, client_user_ref, status, units, quota_reserved, quota_charged, expires_at]
      properties:
        lease_id: { type: string, example: reglease_xxx }
        project_key: { type: string, example: image_generation_registration }
        binding_id: { type: string, example: regbind_xxx }
        account_id: { type: string, example: regacct_xxx }
        client_user_ref: { type: string, example: image-admin-42 }
        task_source: { type: string, example: auto_refill }
        client_run_id: { type: string, example: local-run-uuid }
        units: { type: integer, example: 1 }
        status: { type: string, enum: [reserved, running, committed, released, expired] }
        quota_reserved: { type: integer, example: 1 }
        quota_charged: { type: integer, example: 0 }
        cdk_id: { type: integer, example: 12 }
        cdk_role: { type: string, enum: [customer_cdk, admin_cdk] }
        registration_scope: { type: string, enum: [project, global] }
        is_platform_master: { type: boolean, example: false }
        cdk_fingerprint: { type: string, example: 1a2b3c4d5e6f7890 }
        cdk_masked: { type: string, example: cdk_live...abc123 }
        cdk_config_version: { type: string, format: date-time, example: 2026-08-15T12:00:00Z }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        expires_at: { type: string, format: date-time }
        last_heartbeat_at: { type: string, format: date-time, nullable: true }
        committed_at: { type: string, format: date-time, nullable: true }
        released_at: { type: string, format: date-time, nullable: true }
    RegistrationLeaseResponse:
      type: object
      required: [ok, request_id, lease, quota]
      properties:
        ok: { type: boolean, example: true }
        request_id: { type: string, example: req_xxx }
        created: { type: boolean, example: true }
        idempotent_replay: { type: boolean, example: false }
        lease: { $ref: "#/components/schemas/RegistrationLease" }
        quota: { $ref: "#/components/schemas/Quota" }
    RegistrationAccountStatusRequest:
      type: object
      required: [client_user_ref]
      properties:
        account_id: { type: string, example: regacct_xxx }
        username: { type: string, example: local-user }
        client_user_ref: { type: string, example: local-user-001 }
      description: Project HMAC-authenticated lookup. No current CDK is required, so an expired entitlement can still be checked.
    RegistrationAccount:
      type: object
      required: [account_id, project_key, username, client_user_ref, status, login_allowed, entitlement_status, config_version]
      properties:
        account_id: { type: string, example: regacct_xxx }
        project_key: { type: string, example: registration_core }
        username: { type: string, example: local-user }
        client_user_ref: { type: string, example: local-user-001 }
        status: { type: string, enum: [active, disabled, archived] }
        login_allowed: { type: boolean, example: true }
        entitlement_status: { type: string, enum: [active, expiring, expired, disabled, revoked, replaced, none] }
        requires_rebind: { type: boolean, example: false }
        can_rebind: { type: boolean, example: false }
        current_binding_id: { type: string, nullable: true, example: regbind_xxx }
        cdk_id: { type: integer, nullable: true, example: 12 }
        cdk_masked: { type: string, example: regcdk_li...abc123 }
        cdk_fingerprint: { type: string, example: 1a2b3c4d5e6f7890 }
        cdk_role: { type: string, enum: [customer_cdk, admin_cdk, ""] }
        registration_scope: { type: string, enum: [project, global] }
        is_platform_master: { type: boolean, example: false }
        expires_at: { type: string, format: date-time, nullable: true }
        remaining_seconds: { type: integer, nullable: true }
        expiry_warning: { type: string, enum: [none, 7d, 3d, 1d, expired] }
        config_version: { type: integer, example: 3 }
        config_etag: { type: string, example: 6e4b... }
        config_updated_at: { type: string, format: date-time, nullable: true }
        last_login_at: { type: string, format: date-time, nullable: true }
        last_seen_at: { type: string, format: date-time, nullable: true }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
    RegistrationAccountStatusResponse:
      type: object
      required: [ok, request_id]
      properties:
        ok: { type: boolean, example: true }
        request_id: { type: string, example: req_xxx }
        exists: { type: boolean, example: true }
        can_activate: { type: boolean, example: false }
        account: { $ref: "#/components/schemas/RegistrationAccount" }
    RegistrationRebindRequest:
      type: object
      required: [cdk, client_user_ref]
      properties:
        cdk:
          type: string
          description: New project CDK accepted for this request only. Never log or persist plaintext.
        account_id: { type: string, example: regacct_xxx }
        username: { type: string, example: local-user }
        client_user_ref: { type: string, example: local-user-001 }
        device_summary: { type: string, maxLength: 160 }
    RegistrationRebindResponse:
      allOf:
        - $ref: "#/components/schemas/RegistrationAuthorizationResponse"
        - type: object
          properties:
            rebound: { type: boolean, example: true }
            account: { $ref: "#/components/schemas/RegistrationAccount" }
    RegistrationAccountConfigUpdate:
      type: object
      required: [client_user_ref, expected_version, profile, encrypted_config]
      properties:
        client_user_ref: { type: string, example: local-user-001 }
        expected_version: { type: integer, minimum: 0, example: 3 }
        profile:
          type: object
          additionalProperties: true
          description: Non-secret display/runtime metadata only. Passwords, tokens, cookies, proxies, codes and payment URLs are rejected.
        encrypted_config:
          type: string
          maxLength: 32768
          description: Opaque client-encrypted blob. The server stores it but does not decrypt it.
    RegistrationAccountConfigResponse:
      type: object
      required: [ok, request_id, account]
      properties:
        ok: { type: boolean, example: true }
        request_id: { type: string, example: req_xxx }
        account:
          allOf:
            - $ref: "#/components/schemas/RegistrationAccount"
            - type: object
              properties:
                profile: { type: object, additionalProperties: true }
                encrypted_config: { type: string, description: Returned only to the same project client key. }
    RegistrationAccountListResponse:
      type: object
      required: [ok, accounts, total, limit, offset, has_more]
      properties:
        ok: { type: boolean, example: true }
        accounts:
          type: array
          items: { $ref: "#/components/schemas/RegistrationAccount" }
        total: { type: integer, example: 120 }
        limit: { type: integer, example: 100 }
        offset: { type: integer, example: 0 }
        has_more: { type: boolean, example: true }
    RegistrationAccountHistoryResponse:
      type: object
      required: [ok, account, history]
      properties:
        ok: { type: boolean, example: true }
        account: { $ref: "#/components/schemas/RegistrationAccount" }
        history:
          type: array
          items: { $ref: "#/components/schemas/RegistrationBinding" }
    RegistrationAccountIssueCdkRequest:
      type: object
      properties:
        validity_seconds: { type: integer, minimum: 1, example: 2592000 }
        validity_label: { type: string, example: 1个月 }
        expires_at: { type: string, format: date-time, nullable: true }
        name: { type: string }
        note: { type: string }
    RegistrationAccountIssueCdkResponse:
      type: object
      required: [ok, account, card, rebind_required]
      properties:
        ok: { type: boolean, example: true }
        account: { $ref: "#/components/schemas/RegistrationAccount" }
        rebind_required: { type: boolean, example: true }
        card:
          type: object
          required: [id, plain_cdk, show_once, project_key]
          properties:
            id: { type: integer, example: 21 }
            plain_cdk: { type: string, description: Plaintext shown once only. }
            show_once: { type: boolean, example: true }
            masked_value: { type: string, example: regcdk_li...abc123 }
            fingerprint: { type: string, example: 1a2b3c4d5e6f7890 }
            project_key: { type: string, example: registration_core }
    RegistrationAccessLoginRequest:
      type: object
      required: [cdk, username, client_user_ref]
      properties:
        cdk:
          type: string
          description: Plain CDK supplied for this login request only. Never log or persist it in plaintext.
        username:
          type: string
          pattern: '^[A-Za-z0-9_.-]{3,80}$'
          description: Stable local username. The local password and account session are never sent.
        client_user_ref:
          type: string
          pattern: '^[A-Za-z0-9_.:-]{8,120}$'
          description: Stable local user reference. It must not change between restarts or CDK renewals.
        device_summary:
          type: string
          maxLength: 160
      description: Project HMAC-authenticated login body. The first successful login starts a timed CDK; status, contract and preflight do not.
    RegistrationAccessSession:
      type: object
      required: [status, active, project_key, account_id, binding_id, client_user_ref, cdk_role, registration_scope, is_platform_master]
      properties:
        status: { type: string, enum: [active, expired, closed, replaced, logged_out] }
        active: { type: boolean, example: true }
        session_fingerprint: { type: string, example: 6f9a... }
        session_masked: { type: string, example: regsess_...abc123 }
        project_key: { type: string, example: registration_core }
        account_id: { type: string, example: regacct_xxx }
        binding_id: { type: string, example: regbind_xxx }
        client_user_ref: { type: string, example: stable-project-user-001 }
        cdk_role: { type: string, enum: [customer_cdk, admin_cdk] }
        registration_scope: { type: string, enum: [project, global] }
        is_platform_master: { type: boolean, example: false }
        cdk_masked: { type: string, example: cdk_live...abc123 }
        cdk_fingerprint: { type: string, example: 1a2b3c4d5e6f7890 }
        expires_at: { type: string, format: date-time, nullable: true }
        remaining_seconds: { type: integer, nullable: true }
        created_at: { type: string, format: date-time }
        last_seen_at: { type: string, format: date-time, nullable: true }
        ended_at: { type: string, format: date-time, nullable: true }
        end_reason: { type: string, example: "" }
        account_status: { type: string, enum: [active, disabled, missing] }
      description: Masked session metadata only. The bearer token is never included here.
    RegistrationAccessLoginResponse:
      type: object
      required: [ok, request_id, account, binding, registration_access_token, show_once, session]
      properties:
        ok: { type: boolean, example: true }
        request_id: { type: string, example: req_xxx }
        created: { type: boolean, example: true }
        account: { $ref: "#/components/schemas/RegistrationAccount" }
        binding: { $ref: "#/components/schemas/RegistrationBinding" }
        registration_access_token:
          type: string
          writeOnly: true
          description: Opaque bearer token returned only once. Store encrypted in the project backend; never show it in a browser or log it.
        show_once: { type: boolean, example: true }
        session: { $ref: "#/components/schemas/RegistrationAccessSession" }
    RegistrationSessionEmptyRequest:
      type: object
      additionalProperties: false
      description: Send an empty JSON object so the request can be HMAC signed consistently.
    RegistrationClientPreflightRequest:
      type: object
      additionalProperties: false
      description: Send an empty JSON object. This request is read-only and is signed as {}.
      example: {}
    RegistrationClientPreflightResponse:
      type: object
      required: [ok, request_id, project, client_auth, cdk, lifecycle]
      properties:
        ok: { type: boolean, example: true }
        request_id: { type: string, example: req_xxx }
        project:
          type: object
          required: [project_key, integration_mode]
          properties:
            project_key: { type: string, example: image_generation_registration }
            integration_mode: { type: string, enum: [registration] }
        client_auth:
          type: object
          required: [status, scope, project_key, fingerprint]
          properties:
            status: { type: string, enum: [ok] }
            scope: { type: string, enum: [project] }
            project_key: { type: string, example: image_generation_registration }
            fingerprint: { type: string, example: 1a2b3c4d5e6f7890 }
        cdk:
          type: object
          required: [status, role, registration_scope, is_platform_master, project_access_allowed]
          properties:
            status: { type: string, enum: [active, expiring] }
            role: { type: string, enum: [customer_cdk, admin_cdk] }
            registration_scope: { type: string, enum: [project, global] }
            is_platform_master: { type: boolean, example: false }
            expires_at: { type: string, format: date-time, nullable: true }
            validity_seconds: { type: integer, nullable: true }
            activated_at: { type: string, format: date-time, nullable: true }
            activation_status: { type: string, example: not_started }
            remaining_quota: { type: integer, nullable: true }
            available_slots: { type: integer, nullable: true }
            project_key: { type: string, example: image_generation_registration }
            card_project_key: { type: string, example: image_generation_registration }
            project_access_allowed: { type: boolean, example: true }
        lifecycle:
          type: object
          required: [starts_validity, binding_created, quota_reserved]
          properties:
            starts_validity: { type: boolean, example: false }
            binding_created: { type: boolean, example: false }
            quota_reserved: { type: integer, example: 0 }
    RegistrationSessionStatusResponse:
      type: object
      required: [ok, request_id, session, account, binding]
      properties:
        ok: { type: boolean, example: true }
        request_id: { type: string, example: req_xxx }
        session: { $ref: "#/components/schemas/RegistrationAccessSession" }
        account: { $ref: "#/components/schemas/RegistrationAccount" }
        binding: { $ref: "#/components/schemas/RegistrationBinding" }
    RegistrationSessionLogoutResponse:
      type: object
      required: [ok, request_id, session]
      properties:
        ok: { type: boolean, example: true }
        request_id: { type: string, example: req_xxx }
        session: { $ref: "#/components/schemas/RegistrationAccessSession" }
    RegistrationActivateRequest:
      type: object
      required: [cdk, username, client_user_ref]
      properties:
        cdk:
          type: string
          description: Plain CDK entered only for this request. Accepts the project CDK, an explicit registration_scope=global admin_cdk, or the platform master CDK. It is never returned by normal queries.
        username:
          type: string
          pattern: '^[A-Za-z0-9_.-]{3,80}$'
          description: Local username only; local password is never sent.
        client_user_ref:
          type: string
          pattern: '^[A-Za-z0-9_.:-]{8,120}$'
          description: Stable local reference generated by the registration client.
        device_summary:
          type: string
          maxLength: 160
    RegistrationAuthorizeRequest:
      type: object
      required: [binding_id, client_user_ref]
      properties:
        binding_id: { type: string, example: regbind_xxx }
        client_user_ref: { type: string, example: local-user-001 }
    RegistrationWorkspaceRequest:
      type: object
      required: [binding_id, client_user_ref]
      properties:
        binding_id: { type: string, example: regbind_xxx }
        client_user_ref: { type: string, example: local-user-001 }
        workspace_updated_at: { type: string, format: date-time, nullable: true }
        device_summary: { type: string, maxLength: 160 }
    RegistrationBinding:
      type: object
      required: [binding_id, account_id, cdk_id, cdk_masked, cdk_role, registration_scope, is_platform_master, project_key, username, client_user_ref, status]
      properties:
        binding_id: { type: string, example: regbind_xxx }
        account_id: { type: string, example: regacct_xxx }
        cdk_id: { type: integer, example: 12 }
        cdk_masked: { type: string, example: regcdk_li...abc123 }
        cdk_fingerprint: { type: string, example: 1a2b3c4d5e6f7890 }
        cdk_role: { type: string, enum: [customer_cdk, admin_cdk] }
        registration_scope: { type: string, enum: [project, global] }
        is_platform_master: { type: boolean, example: false }
        project_key: { type: string, example: registration_core }
        username: { type: string, example: local-user }
        client_user_ref: { type: string, example: local-user-001 }
        status: { type: string, enum: [active, expiring, expired, disabled, revoked, replaced] }
        activated_at: { type: string, format: date-time }
        expires_at: { type: string, format: date-time, nullable: true }
        remaining_seconds: { type: integer, nullable: true }
        last_authorized_at: { type: string, format: date-time, nullable: true }
        last_login_at: { type: string, format: date-time, nullable: true }
        workspace_snapshot_at: { type: string, format: date-time, nullable: true }
        workspace_updated_at: { type: string, format: date-time, nullable: true }
        device_summary: { type: string }
        renewal_cdk_id: { type: integer, nullable: true }
        replaced_by_binding_id: { type: string, nullable: true }
        expiry_warning: { type: string, enum: [none, 7d, 3d, 1d, expired] }
        account_status: { type: string, enum: [active, disabled, archived] }
        config_version: { type: integer, example: 3 }
    RegistrationAuthorizationResponse:
      type: object
      required: [ok, request_id, binding, authorization]
      properties:
        ok: { type: boolean, example: true }
        request_id: { type: string, example: req_xxx }
        created: { type: boolean, description: False when the same CDK/user binding is replayed. }
        binding: { $ref: "#/components/schemas/RegistrationBinding" }
        authorization:
          type: object
          required: [signed_authorization, cache_expires_in_seconds]
          properties:
            signed_authorization:
              type: string
              description: Opaque short-lived authorization for the local client. Do not log or persist in shared project data.
            cache_expires_in_seconds: { type: integer, example: 900 }
    RegistrationWorkspaceResponse:
      type: object
      required: [ok, request_id, binding]
      properties:
        ok: { type: boolean, example: true }
        request_id: { type: string, example: req_xxx }
        binding: { $ref: "#/components/schemas/RegistrationBinding" }
    RegistrationClientKeyRecord:
      type: object
      required: [id, project_key, name, masked_value, fingerprint, status, created_at]
      properties:
        id: { type: integer, example: 3 }
        project_key: { type: string, example: registration_core }
        name: { type: string, example: local-register-client }
        masked_value: { type: string, example: regclient...abc123 }
        fingerprint: { type: string, example: 1a2b3c4d5e6f7890 }
        status: { type: string, enum: [active, revoked] }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        last_used_at: { type: string, format: date-time, nullable: true }
        revoked_at: { type: string, format: date-time, nullable: true }
    RegistrationClientKeySecret:
      allOf:
        - $ref: "#/components/schemas/RegistrationClientKeyRecord"
        - type: object
          required: [registration_client_key, show_once]
          properties:
            registration_client_key:
              type: string
              description: Plaintext shown only in this create/rotate response.
            show_once: { type: boolean, example: true }
    RegistrationClientKeyResponse:
      type: object
      required: [ok, client_key]
      properties:
        ok: { type: boolean, example: true }
        client_key: { $ref: "#/components/schemas/RegistrationClientKeySecret" }
    RegistrationClientKeyInput:
      type: object
      properties:
        name: { type: string, example: local-register-client }
    RegistrationClientKeyListResponse:
      type: object
      required: [ok, client_keys]
      properties:
        ok: { type: boolean, example: true }
        client_keys:
          type: array
          items: { $ref: "#/components/schemas/RegistrationClientKeyRecord" }
    RegistrationOverview:
      type: object
      properties:
        issued: { type: integer, example: 4 }
        today_activations: { type: integer, example: 2 }
        active: { type: integer, example: 8 }
        expiring: { type: integer, example: 1 }
        expired: { type: integer, example: 2 }
        disabled: { type: integer, example: 0 }
        revoked: { type: integer, example: 0 }
        replaced: { type: integer, example: 1 }
    RegistrationOverviewResponse:
      type: object
      required: [ok, overview]
      properties:
        ok: { type: boolean, example: true }
        overview: { $ref: "#/components/schemas/RegistrationOverview" }
    RegistrationUsersResponse:
      type: object
      required: [ok, users, total, limit, offset, has_more]
      properties:
        ok: { type: boolean, example: true }
        users:
          type: array
          items: { $ref: "#/components/schemas/RegistrationBinding" }
        total: { type: integer, example: 120 }
        limit: { type: integer, example: 100 }
        offset: { type: integer, example: 0 }
        has_more: { type: boolean, example: true }
    RegistrationRenewalResponse:
      type: object
      required: [ok, source_cdk_id, new_card]
      properties:
        ok: { type: boolean, example: true }
        source_cdk_id: { type: integer, example: 12 }
        new_card:
          type: object
          description: Full card metadata and plaintext only in this one-time response.
          properties:
            id: { type: integer, example: 19 }
            plain_cdk: { type: string, description: Plaintext shown once. }
            show_once: { type: boolean, example: true }
            masked_value: { type: string }
            fingerprint: { type: string }
            project_key: { type: string, example: registration_core }
    RegistrationRenewalInput:
      type: object
      properties:
        validity_seconds: { type: integer, minimum: 1, example: 2592000 }
        validity_label: { type: string, example: 1个月 }
        expires_at: { type: string, nullable: true }
        name: { type: string }
    PreflightRequest:
      type: object
      required: [task_type]
      properties:
        channel_id:
          type: string
          example: paypal.us-usd.br-proxy
          description: Preferred dynamic channel id from /api/v1/channel-catalog. IDs are never reused.
        channel_revision:
          type: string
          example: channel-r1
          description: Optional but recommended. If supplied and stale, the server returns channel_revision_mismatch.
        channel:
          type: string
          enum: [payment_link, brazil_pp, payment_console, paypal_protocol, checkout_console]
          description: Backward-compatible legacy channel. New integrations should use channel_id.
        task_type:
          type: string
          enum: [link, console, protocol]
        payload:
          type: object
          properties:
            payment_mode: { type: string, example: PAYPAL全球轮转 }
            network_profile_id:
              type: string
              example: br-payment-a
              description: Cloud-side proxy profile id. The API never returns proxy credentials.
        payment_mode:
          type: string
        network:
          $ref: "#/components/schemas/Network"
    TaskSubmitRequest:
      type: object
      required: [task_type, payload]
      properties:
        channel_id:
          type: string
          example: paypal.us-usd.br-proxy
        channel_revision:
          type: string
          example: channel-r1
        channel:
          type: string
          enum: [payment_link, brazil_pp, payment_console, paypal_protocol, checkout_console]
          description: Backward-compatible legacy channel. New integrations should use channel_id.
        task_type:
          type: string
          enum: [link, console, protocol]
        payload:
          type: object
          required: [account]
          properties:
            account:
              $ref: "#/components/schemas/Account"
            payment_mode:
              type: string
              example: PAYPAL全球轮转
            network:
              $ref: "#/components/schemas/Network"
            network_profile_id:
              type: string
              example: br-payment-a
              description: Cloud-side proxy profile id. Local clients store this id only.
            client_run_id:
              type: string
            source_type:
              type: string
              example: local_register
            source_task_id:
              type: string
              description: Optional split-flow source task. Use this instead of passing raw payment URLs.
            channel_id: { type: string }
            channel_revision: { type: string }
    Account:
      type: object
      required: [email]
      properties:
        email:
          type: string
          format: email
        access_token:
          type: string
          description: Sensitive. Masked in logs, audit and responses.
        refresh_token:
          type: string
          description: Sensitive. Optional when access_token or session_token is provided.
        session_token:
          type: string
          description: Sensitive. Optional alternative account session token.
      description: At least one of access_token, refresh_token or session_token is required for protocol tasks.
    Network:
      type: object
      properties:
        payment_proxy_pool:
          type: string
          description: First-stage/payment proxy pool identifier or pool content. Secrets are masked.
        provider_proxy_pool:
          type: string
          description: Provider/promo/protocol proxy pool identifier or pool content. Secrets are masked.
    PreflightResponse:
      type: object
      required: [ok, request_id, channel_id, channel_revision, capabilities, cdk, validation]
      properties:
        ok: { type: boolean, example: true }
        request_id: { type: string, example: req_xxx }
        channel_id: { type: string, example: paypal.us-usd.br-proxy }
        channel_revision: { type: string, example: channel-r1 }
        execution_mode:
          type: string
          enum: [full_remote, split_source, split_target]
        capabilities:
          type: object
          properties:
            channels:
              type: array
              items: { type: string }
            task_types:
              type: array
              items: { type: string }
            supports_stage_status: { type: boolean, example: true }
        cdk:
          $ref: "#/components/schemas/CdkQuotaStatus"
        validation:
          type: object
          properties:
            payment_proxy: { type: string, example: ok }
            provider_proxy: { type: string, example: ok }
            payment_mode: { type: string, example: ok }
            channel_permission: { type: string, example: ok }
            network_profile: { type: string, example: ok }
            network_profile_id: { type: string, example: br-payment-a }
            available_slots: { type: integer, example: 10 }
    TaskSubmitResponse:
      type: object
      required: [ok, request_id, idempotent_replay, task, quota]
      properties:
        ok: { type: boolean, example: true }
        request_id: { type: string, example: req_xxx }
        idempotent_replay: { type: boolean, example: false }
        task:
          $ref: "#/components/schemas/Task"
        quota:
          $ref: "#/components/schemas/Quota"
    TaskQueryResponse:
      type: object
      required: [ok, request_id, task, quota]
      properties:
        ok: { type: boolean, example: true }
        request_id: { type: string, example: req_xxx }
        task:
          $ref: "#/components/schemas/Task"
        quota:
          $ref: "#/components/schemas/Quota"
    Task:
      type: object
      required: [id, status, stage, channel, task_type]
      properties:
        id: { type: string, example: task_xxx }
        status:
          type: string
          enum: [queued, running, retry_scheduled, waiting_sms, succeeded, failed, cancelled]
        stage:
          type: string
          enum: [extract, payment, sms, plus]
        attempt: { type: integer, example: 1 }
        retryable: { type: boolean, example: false }
        retry_after_seconds: { type: integer, example: 0 }
        error:
          nullable: true
          $ref: "#/components/schemas/ErrorObject"
        channel: { type: string, example: brazil_pp }
        channel_id: { type: string, example: paypal.us-usd.br-proxy }
        channel_revision: { type: string, example: channel-r1 }
        execution_mode:
          type: string
          enum: [full_remote, split_source, split_target, ""]
        source_task_id:
          type: string
          example: task_xxx
        network_profile_id:
          type: string
          example: br-payment-a
        task_type: { type: string, example: protocol }
        result:
          type: object
          properties:
            extract_status: { type: string, example: succeeded }
            payment_status: { type: string, example: submitted }
            sms_status: { type: string, example: waiting }
            plus_enabled: { type: boolean, example: false }
            source_task_id: { type: string, example: task_xxx }
    Quota:
      type: object
      properties:
        remaining_quota:
          oneOf:
            - type: integer
            - type: string
              enum: [unlimited]
        reserved_quota: { type: integer }
        max_concurrency: { type: integer }
        running_count: { type: integer }
        available_slots: { type: integer }
    CdkTopupRequest:
      type: object
      required: [duration_seconds]
      properties:
        duration_seconds:
          type: integer
          minimum: 1
          example: 2592000
          description: Number of seconds to add to the original CDK.
        duration_label:
          type: string
          example: 1个月
          description: Display label for the current top-up.
    CdkTopupLookupRequest:
      allOf:
        - $ref: "#/components/schemas/CdkTopupRequest"
        - type: object
          required: [lookup]
          properties:
            lookup:
              type: string
              example: "123"
              description: Internal card id or complete CDK. A complete CDK is accepted only for this request and is never persisted.
    RegistrationAccountTopupRequest:
      allOf:
        - $ref: "#/components/schemas/CdkTopupRequest"
        - type: object
          required: [account]
          properties:
            account:
              type: string
              example: local-user
              description: Registration account username bound to the original CDK in the selected registration project.
    CdkTopupResponse:
      type: object
      required: [ok, card, topup]
      properties:
        ok: { type: boolean, example: true }
        account:
          type: string
          nullable: true
          description: Included only for account-based registration top-ups.
        card:
          type: object
          description: Updated original card, returned without plaintext CDK.
          properties:
            id: { type: integer, example: 123 }
            project_key: { type: string, example: registration_core }
            masked_value: { type: string, example: "regcdk_...abcd12" }
            expires_at: { type: string, nullable: true }
        topup:
          type: object
          properties:
            duration_seconds: { type: integer, example: 2592000 }
            duration_label: { type: string, example: 1个月 }
            mode:
              type: string
              enum: [pending_activation, extend_expiry]
            expires_at: { type: string, nullable: true }
    CdkQuotaStatus:
      allOf:
        - $ref: "#/components/schemas/Quota"
        - type: object
          properties:
            status: { type: string, enum: [active, disabled, expired, revoked, merged], example: active }
            role:
              type: string
              enum: [customer_cdk, admin_cdk]
              description: Task identity class. An admin_cdk is not a super-admin management credential.
            registration_scope:
              type: string
              enum: [project, global]
              description: Registration authorization range. global permits cross-project access only for active registration projects.
            quota_mode:
              type: string
              enum: [limited, unlimited]
              description: Quota accounting mode. Clients must not infer this from the CDK prefix.
            is_platform_master:
              type: boolean
              description: True only for the configured cross-project platform master task CDK.
            allowed_channels:
              type: array
              items: { type: string }
            expires_at:
              type: string
              nullable: true
            validity_seconds:
              type: integer
              description: Duration counted from first use. 0 means permanent or fixed expires_at mode.
            validity_label:
              type: string
              example: 7天
            activated_at:
              type: string
              nullable: true
              description: First successful CDK authentication/use time. Null means the duration clock has not started.
            activation_status:
              type: string
              example: 未启用
            project_key:
              type: string
              example: registration_core
              description: Effective project for this request. With no project_key query, this is the card's issuing project.
            card_project_key:
              type: string
              example: platform_admin
              description: Card issuing project retained for audit and display. It is not an authorization decision.
            requested_project_key:
              type: string
              example: registration_core
            effective_project_key:
              type: string
              example: registration_core
            project_access_allowed:
              type: boolean
              nullable: true
              example: true
              description: Authoritative result after role, registration scope, card state and active project checks. Null means no project_key was supplied, so no project access decision was made.
            project_name:
              type: string
              example: 提炼总平台
            card_project_name:
              type: string
              example: 平台管理员
    ProjectEndpoint:
      type: object
      required: [id, method, path, path_template, purpose, auth, required, starts_validity, idempotency_key]
      properties:
        id: { type: string, example: registration_login }
        method: { type: string, example: GET }
        path: { type: string, example: "/api/v1/cdk/status?project_key=example_register" }
        path_template: { type: string, example: "/api/v1/projects/{project_key}/registration/login" }
        purpose: { type: string, example: 校验 CDK 状态和额度 }
        auth: { type: string, example: bearer_cdk }
        required: { type: boolean, example: true }
        starts_validity: { type: boolean, example: false }
        idempotency_key:
          type: string
          enum: [not_used, recommended, required, idempotent]
          example: not_used
    ApiDiscovery:
      type: object
      required: [version, contract_revision, capabilities_revision, project_key_required_for_access_check, access_check_performed]
      properties:
        version: { type: string, example: 1.5.0 }
        contract_revision: { type: string, example: project-contract-v1.5 }
        capabilities_revision: { type: string, example: project-capabilities-v1 }
        project_key_required_for_access_check: { type: boolean, example: true }
        access_check_performed: { type: boolean, example: true }
    ProjectCapabilities:
      type: object
      required: [revision, contract_revision, project_key, integration_mode, project_access_allowed, project_access, feature_flags, required_endpoint_ids, path_rules, authentication, lifecycle, endpoints, cdk]
      properties:
        revision: { type: string, example: project-capabilities-v1 }
        contract_revision: { type: string, example: project-contract-v1.5 }
        project_key: { type: string, example: registration_core }
        integration_mode:
          type: string
          enum: [task_api, cdk_access, registration]
        project_access_allowed: { type: boolean, example: true }
        project_access: { $ref: "#/components/schemas/ProjectAccess" }
        feature_flags:
          type: object
          additionalProperties: { type: boolean }
          example:
            project_isolation: true
            persistent_identity: true
            replaceable_entitlement: true
            registration_login: true
            registration_sessions: true
            registration_leases: true
            registration_client_preflight: true
        required_endpoint_ids:
          type: array
          items: { type: string }
          example: [project_contract, cdk_status, project_capabilities, registration_login, registration_lease_reserve]
        path_rules:
          type: object
          properties:
            project_key_placeholder: { type: string, example: "{project_key}" }
            path_template_source: { type: string, example: "capabilities.endpoints[].path_template" }
            must_not_parse_openapi_paths: { type: boolean, example: true }
            unknown_revision: { type: string, enum: [fail_closed] }
        authentication:
          type: object
          properties:
            contract:
              type: object
              properties:
                scheme: { type: string, example: bearer_cdk }
                header: { type: string, example: "Authorization: Bearer <CDK>" }
                starts_validity: { type: boolean, example: false }
            registration:
              type: object
              properties:
                enabled: { type: boolean, example: true }
                client_key_scope: { type: string, enum: [project] }
                signature: { type: string, example: "timestamp.METHOD.path.sha256(canonical_json_body)" }
                headers:
                  type: array
                  items: { type: string }
                login_bearer: { type: string, example: cdk_in_body }
                runtime_bearer: { type: string, example: registration_access_token }
                session_token_show_once: { type: boolean, example: true }
                client_key: { $ref: "#/components/schemas/RegistrationClientKeyCapability" }
            task:
              type: object
              properties:
                enabled: { type: boolean, example: false }
                bearer: { type: string, example: cdk }
                idempotency_header: { type: string, example: Idempotency-Key }
        lifecycle:
          type: object
          additionalProperties: true
        endpoints:
          type: array
          items: { $ref: "#/components/schemas/ProjectEndpoint" }
        cdk:
          type: object
          properties:
            role: { type: string, enum: [customer_cdk, admin_cdk] }
            registration_scope: { type: string, enum: [project, global] }
            is_platform_master: { type: boolean }
            cross_project_access: { type: boolean }
    ProjectCapabilitiesResponse:
      type: object
      required: [ok, request_id, api_version, contract_revision, project, authentication, cdk, capabilities]
      properties:
        ok: { type: boolean, example: true }
        request_id: { type: string, example: req_xxx }
        api_version: { type: string, example: 1.5.0 }
        contract_revision: { type: string, example: project-contract-v1.5 }
        project: { $ref: "#/components/schemas/Project" }
        authentication:
          type: object
          additionalProperties: true
        cdk:
          $ref: "#/components/schemas/CdkQuotaStatus"
        capabilities:
          $ref: "#/components/schemas/ProjectCapabilities"
    ProjectAccess:
      type: object
      required: [allowed, requested_project_key, card_project_key, cross_project_access]
      properties:
        allowed: { type: boolean, example: true }
        requested_project_key: { type: string, example: registration_core }
        card_project_key: { type: string, example: platform_admin }
        cross_project_access:
          type: boolean
          example: true
          description: True when an admin_cdk has global registration access. It does not grant management APIs.
    RegistrationClientKeyCapability:
      type: object
      required: [required, scope, provisioning_auth, secret_delivery, runtime_storage, preflight_endpoint_id, missing_error_code, invalid_error_codes]
      properties:
        required: { type: boolean, example: true }
        scope: { type: string, enum: [project] }
        used_for: { type: string, example: HMAC proof for project registration requests }
        provisioning_auth: { type: string, enum: [super_admin_api_key] }
        create_path: { type: string, example: /api/v1/admin/projects/image_generation_registration/registration/client-keys }
        rotate_path_template: { type: string, example: "/api/v1/admin/projects/image_generation_registration/registration/client-keys/{key_id}/rotate" }
        secret_delivery: { type: string, enum: [show_once] }
        runtime_storage: { type: string, example: project_backend_encrypted_secret }
        preflight_endpoint_id: { type: string, example: registration_client_preflight }
        missing_error_code: { type: string, example: registration_client_auth_required }
        provisioning_status:
          type: string
          enum: [not_required, not_provisioned, provisioned]
          description: Server-side status only; never contains client-key material.
          example: provisioned
        server_ready:
          type: boolean
          description: True when the central service has an active client key for this project. The local backend must still inject its copy securely.
          example: true
        next_action:
          type: string
          enum: [not_required, create_project_client_key, inject_project_client_key]
          description: Single safe next action for integration diagnostics.
          example: inject_project_client_key
        invalid_error_codes:
          type: array
          items: { type: string }
    ProjectIntegrationContract:
      type: object
      required: [ok, request_id, api_version, contract_revision, project, authentication, capabilities, endpoints, card_library]
      properties:
        ok: { type: boolean, example: true }
        api_version: { type: string, example: 1.5.0 }
        request_id: { type: string, example: req_xxx }
        contract_revision: { type: string, example: project-contract-v1.5 }
        account_model: { type: string, example: persistent_identity }
        cdk_model: { type: string, example: replaceable_entitlement }
        project: { $ref: "#/components/schemas/Project" }
        authentication:
          type: object
          properties:
            type: { type: string, example: bearer_cdk }
            header: { type: string, example: "Authorization: Bearer <project cdk or global registration admin cdk>" }
            project_key: { type: string, example: example_register }
            project_isolated: { type: boolean, example: true }
            cross_project_access: { type: boolean, example: false }
            project_access_allowed: { type: boolean, example: true }
            requested_project_key: { type: string, example: example_register }
            effective_project_key: { type: string, example: example_register }
            card_project_key: { type: string, example: example_register }
            project_access: { $ref: "#/components/schemas/ProjectAccess" }
            cdk_role: { type: string, enum: [customer_cdk, admin_cdk] }
            registration_scope: { type: string, enum: [project, global] }
            is_platform_master: { type: boolean, example: false }
            registration_client_key: { $ref: "#/components/schemas/RegistrationClientKeyCapability" }
        cdk:
          $ref: "#/components/schemas/CdkQuotaStatus"
        capabilities:
          $ref: "#/components/schemas/ProjectCapabilities"
        endpoints:
          type: array
          items: { $ref: "#/components/schemas/ProjectEndpoint" }
        card_library:
          type: object
          properties:
            admin_filter: { type: string, example: "/api/v1/admin/cdks?project_key=example_register" }
            admin_summary: { type: string, example: /api/v1/admin/projects/example_register/summary }
            display_sections:
              type: array
              items: { type: string }
    ProjectSummaryResponse:
      type: object
      required: [ok, project, card_counts, task_counts, project_view]
      properties:
        ok: { type: boolean, example: true }
        project: { $ref: "#/components/schemas/Project" }
        card_counts:
          type: object
          additionalProperties: { type: integer }
        task_counts:
          type: object
          additionalProperties: { type: integer }
        project_view:
          type: object
          additionalProperties: true
    AdminProjectIntegrationResponse:
      type: object
      required: [ok, contract_revision, project, administration]
      properties:
        ok: { type: boolean, example: true }
        contract_revision: { type: string, example: project-contract-v1.5 }
        project: { $ref: "#/components/schemas/Project" }
        administration:
          type: object
          properties:
            cdk_list: { type: string, example: "/api/v1/admin/cdks?project_key=example_register" }
            project_summary: { type: string, example: /api/v1/admin/projects/example_register/summary }
    Project:
      type: object
      required: [project_key, name, status]
      properties:
        project_key:
          type: string
          example: extract_platform
          description: Stable project identifier. It cannot be changed after creation.
        name: { type: string, example: 提炼总平台 }
        description: { type: string, example: 提炼和支付链接任务 }
        entry_path: { type: string, example: / }
        integration_mode:
          type: string
          enum: [task_api, cdk_access, registration]
          description: Determines the project-specific API contract and card-library sections.
        integration_mode_name: { type: string, example: 云端任务接口, readOnly: true }
        integration_contract_path: { type: string, example: /api/v1/projects/extract_platform/integration, readOnly: true }
        status:
          type: string
          enum: [active, paused, retired]
        card_count: { type: integer, example: 12 }
        active_card_count: { type: integer, example: 9 }
        created_at: { type: string }
        updated_at: { type: string }
    ProjectInput:
      type: object
      properties:
        project_key:
          type: string
          example: local_register
          description: Required on create. Omit when updating. Use letters, numbers, dots, underscores and hyphens only.
        name: { type: string, example: 本地注册机 }
        description: { type: string, example: 本地注册任务 }
        entry_path: { type: string, example: https://register.example.test/ }
        integration_mode:
          type: string
          enum: [task_api, cdk_access, registration]
          default: cdk_access
          description: Select the project's API boundary when creating or updating it.
        status:
          type: string
          enum: [active, paused, retired]
    ChannelCatalogResponse:
      type: object
      required: [ok, request_id, revision, groups, channels]
      properties:
        ok: { type: boolean, example: true }
        request_id: { type: string, example: req_xxx }
        revision: { type: string, example: "20260814.2" }
        groups:
          type: array
          items:
            $ref: "#/components/schemas/ChannelGroup"
        channels:
          type: array
          items:
            $ref: "#/components/schemas/Channel"
    ChannelGroup:
      type: object
      required: [id, name, parent_id, order]
      properties:
        id: { type: string, example: paypal }
        name: { type: string, example: PayPal 卡链接 }
        parent_id: { type: string, example: "" }
        order: { type: integer, example: 10 }
    Channel:
      type: object
      required: [id, revision, name, group_path, status, task_types, payment_modes, execution_mode, requires]
      properties:
        id:
          type: string
          example: paypal.us-usd.br-proxy
          description: Stable id. Never reuse this id for a different channel.
        revision:
          type: string
          example: channel-r1
          description: Changes whenever executable behavior or required parameters change.
        name: { type: string, example: US/USD 直链（BR代理） }
        group_path:
          type: array
          items: { type: string }
          example: [PayPal 卡链接]
        status:
          type: string
          enum: [active, paused, maintenance, retired]
        task_types:
          type: array
          items:
            type: string
            enum: [link, console, protocol]
        payment_modes:
          type: array
          items: { type: string }
          example: [PAYPAL全球轮转]
        execution_mode:
          type: string
          enum: [full_remote, split_source, split_target]
        network_profile_id:
          type: string
          example: br-payment-a
        requires:
          type: object
          properties:
            qualification: { type: boolean }
            payment_proxy: { type: boolean }
            provider_proxy: { type: boolean }
            sms: { type: boolean }
            source_task_id: { type: boolean }
            account_fields:
              type: array
              items: { type: string }
    ErrorResponse:
      type: object
      required: [ok, request_id, error]
      properties:
        ok: { type: boolean, example: false }
        request_id: { type: string, example: req_xxx }
        error:
          $ref: "#/components/schemas/ErrorObject"
        error_code:
          type: string
          description: Legacy error code retained for older callers.
    ErrorObject:
      type: object
      required: [code, message, retryable, retry_after_seconds]
      properties:
        code:
          type: string
          enum: [invalid_token, cdk_revoked, quota_exhausted, concurrency_full, invalid_payload, unsupported_channel, channel_revision_mismatch, channel_unavailable, network_profile_missing, proxy_missing, upstream_timeout, upstream_unavailable, sms_timeout, cancelled, non_retryable_failure, registration_project_access_denied, registration_scope_denied, registration_client_key_scope_denied, registration_cdk_role_unsupported, registration_account_not_found, registration_account_disabled, registration_rebind_required, registration_rebind_conflict, registration_config_conflict, registration_config_invalid, registration_cdk_expired, registration_lease_required, registration_lease_not_found, registration_lease_closed, registration_lease_binding_required, registration_lease_expired, registration_lease_idempotency_conflict]
        message: { type: string }
        retryable: { type: boolean }
        retry_after_seconds: { type: integer }
