> ## Documentation Index
> Fetch the complete documentation index at: https://docs.phoneuse.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Start provisioning this organisation

> Begins a purchase and returns somewhere to send the browser.

WHAT IS BEING BOUGHT is an optional `{sku, billing}` body. Send it to buy a named thing at the price `GET /v1/skus` published for that pair. Send no body at all, as a caller with nothing to choose between does, and the deployment's single default price applies, which is what this route did for every caller before it read a body. A body naming a pair this deployment holds no price for answers 503 rather than quietly charging the default: taking the wrong amount is worse than taking none.

WHO MAY CALL IT. A signed-in org ADMIN: a member cannot commit the organisation to a subscription and gets 403. A deployment may additionally require the admin's recent identity check, refusing with `reverification_required`; whether it does is configuration. OR an API key holding `orders:place`, for PREPAID plans only (pay as you go answers 403). Only an admin's session holds that scope and a key never carries more than its minter, so such a key was created or approved by an admin; a key has no sign-in to check, so no identity check is asked of it. A key without the scope gets 403.

An organisation that is ALREADY provisioned gets 409, whatever the deployment's configuration gaps are: telling a customer with a working subscription that provisioning is unavailable would be a worse answer than the truth. The state of the organisation is therefore checked before the state of the deployment.

SEND AN `Idempotency-Key` AND A RETRY COSTS NOTHING. With one, a repeat within 24 hours is answered with the FIRST attempt's answer, the same checkout url and the same order id with no new session, and the response carries `Idempotent-Replayed: true` so you can tell. That is what makes a lost response safe to retry: the 2xx you never received is handed to you rather than reproduced.

WITHOUT ONE, calling it again while an attempt is open RESTARTS that attempt where the earlier one can still be made unpayable, and refuses with 409 `checkout_in_progress` where it cannot, which is the case that matters: an order the customer has already paid for is never abandoned out from under an inbound payment. Restarting is safe for your money and costs you the session already open in the browser, which is the whole reason to send a key.

Every reason this deployment cannot provision, one that is not finished being set up and one with no payment provider configured alike, answers one 503 code. From a caller's side they are one situation and splitting them would only make the console branch on a distinction it cannot act on.

WHAT A KEY CAN AND CANNOT DO HERE. It opens a Checkout page and returns its url; a PERSON still has to pay on that page, so a program cannot spend money while nobody is watching. Cancelling or returning an order, the billing portal and pay as you go stay with a signed-in person, and no scope grants them.

Hosted deployments only. A local checkout does not mount this route, so calling it there is a 404.



## OpenAPI

````yaml /api/openapi.json post /v1/checkout-sessions
openapi: 3.1.0
info:
  title: Phonebase HTTP API
  version: 0.1.0
  summary: >-
    Every HTTP operation the control plane mounts, generated from the app's own
    route table.
  description: >-
    The control plane exposes two HTTP surfaces, and reading the boundary
    between them matters more than reading any single endpoint.


    The CONTROL surface is what your backend and your operators call. It takes a
    bearer token in the `Authorization` header, and two credential channels
    resolve to the same authorization context: an OAuth 2.1 access token for
    interactive clients, or an org scoped API key for headless ones.


    The DEVICE surface, under `/vrx/{runToken}/v1`, is what a worker calls while
    executing one run. Its credential is the run token in the url path, and it
    reads no `Authorization` header at all.


    Device control itself is not REST. An agent taps, swipes and reads the
    screen over the Model Context Protocol at `POST /mcp`, one tool call at a
    time. There is no `POST /v1/devices/{id}/tap`, and if you are looking for
    one, start with the tool reference instead.


    Operations marked `"x-phonebase-modes": ["hosted"]` exist only on a hosted
    deployment; a local checkout does not mount them.
servers:
  - url: '{origin}'
    description: Your control plane origin.
    variables:
      origin:
        default: https://mcp.phonebase.co
        enum:
          - https://mcp.phonebase.co
          - http://127.0.0.1:8788
        description: >-
          The hosted deployment serves the public origin its metadata document
          advertises, and that is the default here. Select the loopback entry
          instead when you are running a local checkout, which serves the port
          `PORT` names.
security: []
tags:
  - name: Apps
    description: Save immutable application builds and manage explicit device operations.
  - name: Managed Proxy
    description: >-
      Organization-owned fixed egress resources, independent one-time orders and
      verified network relationships.
  - name: Health
    description: Liveness, unauthenticated.
  - name: Gateway enrollment
    description: >-
      How a new gateway computer trades a one-time enrollment code for its
      tunnel credential. Unauthenticated: the code is the credential.
  - name: Gateway releases
    description: >-
      What a gateway computer installs: the current edge release of a channel.
      Called with the gateway's own credential.
  - name: MCP
    description: The JSON-RPC endpoint an agent drives devices through.
  - name: Copilot
    description: >-
      Drive a device by chatting. A signed in person's own conversations,
      streamed. Hosted deployments only.
  - name: Discovery
    description: Where to authenticate. Served on hosted deployments only.
  - name: Runs
    description: Start, watch and steer a run.
  - name: API keys
    description: Mint and revoke headless credentials. Hosted deployments only.
  - name: Device surface
    description: What a worker calls with its run token to observe and act.
  - name: Usage
    description: Read your org's own receipts and consumption. Hosted deployments only.
  - name: Devices
    description: Read the fleet your credential can see. Hosted deployments only.
  - name: Notifications
    description: >-
      What a person needs to know when they are not watching, and which emails
      they receive. Hosted deployments only.
  - name: Webhooks
    description: >-
      Subscribe your own system to what happens here, and read what was
      delivered. Hosted deployments only.
  - name: Provisioning
    description: >-
      Whether an organisation is provisioned, and how it becomes so. Hosted
      deployments only, and both operations answer a declared 503 on a
      deployment that cannot provision yet.
paths:
  /v1/checkout-sessions:
    post:
      tags:
        - Provisioning
      summary: Start provisioning this organisation
      description: >-
        Begins a purchase and returns somewhere to send the browser.


        WHAT IS BEING BOUGHT is an optional `{sku, billing}` body. Send it to
        buy a named thing at the price `GET /v1/skus` published for that pair.
        Send no body at all, as a caller with nothing to choose between does,
        and the deployment's single default price applies, which is what this
        route did for every caller before it read a body. A body naming a pair
        this deployment holds no price for answers 503 rather than quietly
        charging the default: taking the wrong amount is worse than taking none.


        WHO MAY CALL IT. A signed-in org ADMIN: a member cannot commit the
        organisation to a subscription and gets 403. A deployment may
        additionally require the admin's recent identity check, refusing with
        `reverification_required`; whether it does is configuration. OR an API
        key holding `orders:place`, for PREPAID plans only (pay as you go
        answers 403). Only an admin's session holds that scope and a key never
        carries more than its minter, so such a key was created or approved by
        an admin; a key has no sign-in to check, so no identity check is asked
        of it. A key without the scope gets 403.


        An organisation that is ALREADY provisioned gets 409, whatever the
        deployment's configuration gaps are: telling a customer with a working
        subscription that provisioning is unavailable would be a worse answer
        than the truth. The state of the organisation is therefore checked
        before the state of the deployment.


        SEND AN `Idempotency-Key` AND A RETRY COSTS NOTHING. With one, a repeat
        within 24 hours is answered with the FIRST attempt's answer, the same
        checkout url and the same order id with no new session, and the response
        carries `Idempotent-Replayed: true` so you can tell. That is what makes
        a lost response safe to retry: the 2xx you never received is handed to
        you rather than reproduced.


        WITHOUT ONE, calling it again while an attempt is open RESTARTS that
        attempt where the earlier one can still be made unpayable, and refuses
        with 409 `checkout_in_progress` where it cannot, which is the case that
        matters: an order the customer has already paid for is never abandoned
        out from under an inbound payment. Restarting is safe for your money and
        costs you the session already open in the browser, which is the whole
        reason to send a key.


        Every reason this deployment cannot provision, one that is not finished
        being set up and one with no payment provider configured alike, answers
        one 503 code. From a caller's side they are one situation and splitting
        them would only make the console branch on a distinction it cannot act
        on.


        WHAT A KEY CAN AND CANNOT DO HERE. It opens a Checkout page and returns
        its url; a PERSON still has to pay on that page, so a program cannot
        spend money while nobody is watching. Cancelling or returning an order,
        the billing portal and pay as you go stay with a signed-in person, and
        no scope grants them.


        Hosted deployments only. A local checkout does not mount this route, so
        calling it there is a 404.
      operationId: postV1CheckoutSessions
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: >-
            A key of your own, unique to this attempt, so a retry cannot buy
            twice. Send the SAME value on every retry of one purchase; up to 255
            printable ASCII characters, and a random uuid is the usual choice.


            With a key, a retry within 24 hours is answered with the ORIGINAL
            answer, the same checkout url and the same order id, and nothing new
            is created. Without one, a retry supersedes: the session you may
            already have open is expired and a different one is minted, which is
            safe for your money and inconvenient for whoever is looking at the
            old tab.


            Reusing a key you already sent with a DIFFERENT purchase is refused
            rather than answered, because the alternative is handing you a link
            to the wrong thing.
          schema:
            type: string
            maxLength: 255
      requestBody:
        description: >-
          What to buy, or nothing at all. See the description for what an absent
          body means.
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PurchaseChoice'
      responses:
        '200':
          description: >-
            A started purchase. A named sku is fulfilled from a unit of THAT
            kind and no other, so a customer is never handed something other
            than what they paid for; if none is free the purchase still succeeds
            and the device is promised (see `shipsNow` on `GET /v1/skus`).
          headers:
            Idempotent-Replayed:
              description: >-
                `true` when this answer was recorded by an earlier attempt and
                handed back unchanged rather than produced now. Absent
                otherwise.
              schema:
                type: string
                enum:
                  - 'true'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CheckoutSession'
        '400':
          description: >-
            The body is there and is not a valid `{sku, billing}` pair:
            unreadable JSON, an unknown sku, an unknown billing mode, or one of
            the two without the other. Both fields or neither; half a choice is
            refused rather than completed, because completing it would be this
            service picking what you pay.


            Also a `model` that the matching `GET /v1/skus` row does not list,
            or a `model` sent without a pair. The check is against the list this
            deployment declared, so a client holding an older list is told here
            rather than after an order exists.


            Also a `region` sent without a `model`, sent for a model that lists
            no `regions`, or not among the codes the model lists; the message
            names the codes it does list.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: No credential, or one that was refused.
          headers:
            WWW-Authenticate:
              description: >-
                A bearer challenge. On a hosted deployment it names the metadata
                document to discover the authorization server from, and it adds
                `error="invalid_token"` when a credential was presented and
                rejected.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedError'
        '403':
          description: >-
            Either the caller may not start provisioning (a member; an API key
            without `orders:place`; an ordering key asking for pay as you go),
            or, as `reverification_required`, that admin's sign-in has not
            verified a factor within the last 10 minutes, because a purchase
            opens a recurring charge that outlives the sign-in that started it.
            Verify again and retry the same call unchanged.


            WHETHER this route refuses on freshness at all is a DEPLOYMENT
            posture, not a contract, and the default is not to. A client must
            therefore handle this 403 without assuming it will ever see it, and
            must not assume it will not. Branch on `error.code`, never on the
            status.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/Error'
                  - $ref: '#/components/schemas/ReverificationRequiredError'
        '409':
          description: >-
            Another attempt is open and cannot be superseded because it may
            still be paid (`checkout_in_progress`); the previous attempt was
            paid and that payment is still being confirmed
            (`payment_in_progress`); the selected model's reviewed quote is
            stale (`quote_changed`); or there is no device available to
            provision (`out_of_stock`).


            `out_of_stock` for the SHELF appears ONLY on a deployment configured
            to refuse a purchase it cannot fulfil immediately. The default is
            the opposite: the purchase succeeds and the device is promised
            within about 24 hours, which `GET /v1/fulfilments` then reports as
            `awaiting_provisioning`. A client should handle both, because which
            one a deployment does is a configuration choice and not a contract
            change. `out_of_stock` for a chosen Cloud model means the supplier
            goods listing could not resolve its goodId. A listed model or region
            that is only temporarily sold out may instead be paid for as a
            backorder.


            `idempotency_conflict` is the fourth, and only when you sent an
            `Idempotency-Key`. `idempotency_conflict`. Either an attempt under
            this key is still running, in which case `retryable` is true and
            sending the same request again shortly gets the answer, or this key
            was already used for a DIFFERENT request, which is `retryable:
            false` and means the client has a bug: use a new key, or send the
            original request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '413':
          description: >-
            The request body exceeds the server's limit (1 MiB). Refused before
            the body is parsed, so nothing was partially applied. Retrying the
            same body will fail the same way: send less. Per-field limits are
            tighter than this and are documented on the fields themselves; this
            is the backstop underneath them. The shape is the same on ordinary
            JSON surfaces, /vrx included. The explicitly configured APK content
            stream documents its own byte ceiling.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PayloadTooLargeError'
        '429':
          description: >-
            Over the request budget for this caller. Hosted deployments only.
            Which budget applied depends on whether this request carried an
            `Authorization` header, not on which endpoint it was aimed at. Wait
            `Retry-After` seconds and retry the same request: the refusal
            happens before any work, so nothing was partially applied. See
            /api/rate-limits.
          headers:
            Retry-After:
              description: >-
                Whole seconds until one request is available again. Computed
                from the caller's own remaining allowance, not a fixed constant.
              schema:
                type: string
            RateLimit-Limit:
              description: >-
                The budget in effect for this caller class, in requests per
                minute.
              schema:
                type: string
            RateLimit-Remaining:
              description: >-
                Whole requests that were still available when this request was
                admitted. Zero on a 429.
              schema:
                type: string
            RateLimit-Reset:
              description: >-
                Seconds until the allowance is back to full. Longer than
                `Retry-After`, which is the wait for a single request.
              schema:
                type: string
            RateLimit-Policy:
              description: The budget and its window, as `<limit>;w=60`.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RateLimitedError'
        '503':
          description: >-
            This deployment cannot provision yet: no provider configured, the
            provisioning tables not applied, no way to attribute this
            organisation to an authentication instance, or no price configured
            for the sku and billing pair the body named.


            For a monthly Cloud model, also when no supplier goods listing has
            succeeded yet, the configured default region is not listed, or its
            exact monthly Price is unavailable. No payment page is created.


            Also, on a deployment configured to refuse a purchase it cannot
            fulfil immediately, when the inventory it would have to read cannot
            be read at all. That is deliberately NOT `409 out_of_stock`: nothing
            was learned about the shelf, so the honest answer is that we cannot
            say yet. Retry it.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CheckoutUnavailableError'
      security:
        - bearerAuth: []
components:
  schemas:
    PurchaseChoice:
      type: object
      properties:
        sku:
          type: string
          enum:
            - emulator
            - cloud_phone
            - phone_robot_android
            - phone_robot_ios
          description: What to buy. Must be one this deployment prices; see `GET /v1/skus`.
        billing:
          type: string
          enum:
            - subscription
            - payg
            - day
          description: How to pay for it.
        model:
          type: string
          description: >-
            Which model of that sku, by the `id` of one of the `models` the
            matching `GET /v1/skus` row listed. Required for monthly Cloud phone
            checkout; other pairs may omit it. Send an id the deployment did not
            declare and the answer is 400 `invalid_argument`. A supplier-listed
            monthly Cloud model with `backorderable: true` may be paid for while
            sold out. An unresolved supplier goodId is refused before payment.
            Validated against the current server catalog, never the copy a
            client holds.
          minLength: 1
          maxLength: 64
        quoteId:
          type: string
          description: >-
            The `quoteId` published beside the selected model in `GET /v1/skus`.
            Required for a priced model; the server checks it against its
            current Price before creating an order or a payment session. Never
            an amount and never trusted as the charge source.
        addOns:
          type: object
          additionalProperties: false
          properties:
            usFixedIp:
              type: boolean
              description: See `CheckoutAddOns.usFixedIp`. Absent means false.
            preinstallApps:
              type: boolean
              description: See `CheckoutAddOns.preinstallApps`. Absent means false.
          description: >-
            Add-on intents, Cloud phone with a `model` only. STRICT, unlike the
            body: an unknown key or a non-boolean value is 400
            `invalid_argument`. Recorded on the order; nothing here changes the
            charge.
        quantity:
          type: integer
          minimum: 1
          maximum: 20
          description: >-
            How many phones of the chosen model, 1 to 20. Optional, legal only
            beside a `model`; absent means one. Respect the matching catalog
            entry's maxQuantity: modern Cloud first-month payments support up to
            three; all other plans support one. A larger value is refused before
            an order or payment exists. One Stripe payment creates separately
            tracked units, each with its own delivery, term and bounded refund.
        region:
          type: string
          pattern: ^[A-Z]{2}$
          description: >-
            Where to place that model, by the `code` of one of the `regions` the
            chosen model listed on `GET /v1/skus`: an upper-case ISO 3166-1
            alpha-2 code. Optional, and legal only beside a `model` that lists
            regions: send it for a model with no `regions` and the answer is 400
            `invalid_argument` ("No region can be chosen for this model; omit
            region."); send a code the model did not list and the answer is 400
            naming the codes it does. A listed sold-out region may be purchased
            as a Cloud backorder. Omit it and the deployment's default region
            for that model applies; an unlisted default is refused before
            payment.
      required:
        - sku
        - billing
      additionalProperties: true
      description: >-
        What to buy. Fields this service does not know are IGNORED, not refused:
        the route reads `sku`, `billing`, `model`, `quoteId`, `region`,
        `quantity` and `addOns` and drops everything else.
    CheckoutSession:
      type: object
      properties:
        checkoutUrl:
          type: string
          description: >-
            Where to send the browser to complete the purchase. Single use, and
            it expires.
        orderId:
          type: string
          description: This attempt's order id, echoed so a caller can correlate.
        quantity:
          type: integer
          minimum: 1
          maximum: 3
        fulfilmentIds:
          type: array
          minItems: 1
          maxItems: 3
          uniqueItems: true
          items:
            type: string
          description: >-
            Stable opaque per-phone identifiers, committed as fulfillments only
            after verified payment. Quantity-one keeps its existing order
            identifier.
        expiresAt:
          type: string
          format: date-time
          description: >-
            After this the URL stops being payable and a fresh attempt is
            needed.
      required:
        - checkoutUrl
        - orderId
        - expiresAt
      additionalProperties: false
      description: A started purchase.
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              enum:
                - backend_resolution_failed
                - backend_not_implemented
                - device_unavailable
                - device_disconnected
                - lease_mismatch
                - lease_required
                - capability_unsupported
                - invalid_argument
                - backend_command_failed
                - idempotency_conflict
                - action_outcome_unknown
                - stale_frame
                - internal_error
                - unauthorized
                - permission_denied
                - run_not_found
                - frame_not_found
                - device_busy
                - admission_unavailable
                - checkout_unavailable
                - checkout_customer_balance
                - checkout_in_progress
                - quote_changed
                - payment_in_progress
                - out_of_stock
                - wrong_state
                - instance_not_served
                - reverification_required
                - not_found
                - receipt_not_found
                - rate_limited
                - payload_too_large
                - origin_not_allowed
                - api_key_from_browser_origin
                - key_predates_instance_attribution
                - org_not_served_by_instance
                - tenant_binding_unavailable
                - tenant_binding_unsettled
                - fleet_at_capacity
                - forbidden
                - service_unavailable
                - invalid_signature
                - invalid_event
                - not_settled
              description: >-
                The closed set of codes this service answers with on purpose, on
                MCP tools and REST alike (D152). What each one means and when
                you get it is on the errors page.
            message:
              type: string
              description: A human readable summary. Do not branch on it, branch on `code`.
            retryable:
              type: boolean
              description: >-
                Whether re-issuing the same call unchanged is meaningful. It is
                a property of the occurrence, not of the code, so read the field
                rather than inferring it.
          required:
            - code
            - message
          additionalProperties: true
          description: >-
            Specific errors add their own fields, such as `deviceId`, `reason`
            or `capability`. Read the ones you branch on and ignore the rest.
      required:
        - error
      additionalProperties: false
      description: >-
        The control surface error envelope. Every non 2xx JSON body on this
        surface has this shape.
    UnauthorizedError:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              const: unauthorized
              description: Always `unauthorized` on this response.
            reason:
              type: string
              enum:
                - missing_credentials
                - malformed_credentials
                - invalid_token
                - token_expired
                - wrong_audience
                - missing_org
                - missing_org_role
                - unknown_key
                - revoked_key
                - expired_key
              description: The closed set of ways authentication can fail.
            hint:
              type: string
              description: >-
                A next step in prose, on the reasons where there is one to give:
                `revoked_key`, `expired_key`, `missing_org_role`, and
                `invalid_token` when the account is not a member of the named
                organization. Absent otherwise, so branch on `reason` and show
                this if it is there.
          required:
            - code
            - reason
          additionalProperties: false
      required:
        - error
      additionalProperties: false
      description: >-
        A refused credential. `unknown_key` covers both a keyId nobody has and a
        secret that does not match, because telling those apart would enumerate
        keys for you. Revocation and expiry are distinguishable, since you owned
        that key.
    ReverificationRequiredError:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              const: reverification_required
              description: Always `reverification_required` on this response.
            message:
              type: string
              description: A human readable summary naming the remedy and the window.
            retryable:
              type: boolean
              const: true
              description: >-
                Always true: the same call, unchanged, succeeds once a factor
                has been verified.
            maxAgeMinutes:
              type: integer
              const: 10
              description: >-
                How recent the identity check has to be. The caller's own
                measured age is deliberately not reported.
          required:
            - code
            - message
            - retryable
            - maxAgeMinutes
          additionalProperties: false
      required:
        - error
      additionalProperties: false
      description: >-
        Returned by `POST /v1/checkout-sessions` and Managed Proxy financial
        actions, where the deployment requires it, when the signed-in admin has
        not verified a factor recently. It is not a statement about permissions.
        Creating, rotating and renewing API keys never answer it.
    PayloadTooLargeError:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              const: payload_too_large
              description: Always `payload_too_large` on this response.
            message:
              type: string
              description: A human readable summary naming the limit in bytes.
          required:
            - code
            - message
          additionalProperties: false
      required:
        - error
      additionalProperties: false
      description: >-
        Returned when the request body exceeds the server limit. Identical on
        every surface, including /vrx.
    RateLimitedError:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              const: rate_limited
              description: Always `rate_limited` on this response.
            message:
              type: string
              description: >-
                A human readable summary. The numbers are in the headers, not in
                here.
          required:
            - code
            - message
          additionalProperties: false
      required:
        - error
      additionalProperties: false
      description: >-
        Over the request budget. Raised by the rate limiter before any handler
        runs, which is why its code is not one of the action codes: nothing was
        attempted, so no action failed.
    CheckoutUnavailableError:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              enum:
                - admission_unavailable
                - checkout_unavailable
                - checkout_customer_balance
              description: >-
                `admission_unavailable`: the provisioning store cannot be read
                right now; retry after `Retry-After`. `checkout_unavailable`:
                checkout could not be opened or the earlier session could not be
                closed; retry shortly. `checkout_customer_balance`: the
                organization's billing account holds a credit or balance that
                blocks checkout; retrying does not help, contact support.
            message:
              type: string
              description: A human readable summary. Do not branch on it, branch on `code`.
          required:
            - code
            - message
          additionalProperties: false
      required:
        - error
      additionalProperties: false
      description: Checkout cannot start for this organization right now.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        The control surface credential. Send `Authorization: Bearer <token>`.


        Two kinds of token are accepted and they are told apart by shape, not by
        a separate header. A token beginning `pbk_` is an org scoped API key,
        whose public half and secret half are generated together and of which
        only a hash of the secret is ever stored; anything else is treated as an
        OAuth 2.1 access token and verified against the authorization server's
        keys.


        Both resolve to the same context: an org, a principal and a set of
        scopes. Nothing downstream branches on which channel you used, with one
        deliberate exception, key management, which requires a signed-in person
        so that a key can never mint another key.


        Scopes are enforced when MCP tools are REGISTERED rather than when they
        are called, so a tool your credential cannot use is absent from
        `tools/list` rather than refused mid gesture.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.