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

# Mint self-serve sandbox credentials: a personal key, an OAuth client and a webhook signing key for a new tenant

> The sandbox issues its own credentials: no account, no form. One call creates a tenant with a personal API key, an OAuth client (id and secret for `POST /oauth/token`) and a webhook signing key, all carrying every scope and all expiring together (see `expires_at`). The secrets are shown once. Rate-limited per caller address; a 429 carries `Retry-After`. Sandbox only: destinations are mock tenants, and the production service will not expose this route.



## OpenAPI

````yaml /api-reference/openapi.json post /sandbox/keys
openapi: 3.1.0
info:
  title: composerID Intent API
  version: 1.0.0-draft
  summary: >-
    Publish a decision into any destination system of record, under one Intent
    ID.
  description: >
    The composerID Intent API: mint an intent at the decision, plan its

    destinations, preflight against a tenant's own requirements, publish
    idempotently,

    and reconcile drift, with every step on an append-only timeline under one
    Intent ID.


    **Status.** This is the documented contract, served today by the reference
    sandbox

    (`service/` in the composerID repository) over mock destination tenants. The

    production service behind `https://api.composer.id` is in build; nothing
    here is a

    claim that it is live. The sandbox implements this document exactly, so a
    client

    generated from it works against the sandbox now and against production
    unchanged.


    **Authentication.** Bearer token on every request (`Authorization: Bearer

    <token>`). Production clients use OAuth 2.0 client credentials: exchange a

    `client_id` and `client_secret` at `POST /v1/oauth/token` for a token that

    expires in an hour and carries only the scopes the client was granted

    (`intents:read`, `intents:write`, `publish`, `reconcile`, `passthrough`,

    `destinations:read`); a token without the scope a route needs gets 403

    `insufficient_scope`. Personal sandbox API keys are also accepted as bearer

    tokens and carry every scope. All comparisons are constant-time. Destination

    credentials are never supplied by the caller: they are held per tenant and

    injected by the adapter.


    **Sandbox credentials.** The hosted sandbox issues its own: `POST

    /v1/sandbox/keys` (no authentication, rate-limited per caller address)

    creates a tenant with a personal API key, an OAuth client and a webhook

    signing key, shown once and expiring together. Each tenant sees only the

    intents it minted. The local run prints a key at startup instead. The

    production service will not expose this route.

    The one route that carries no bearer token is `POST
    /v1/webhooks/{platform}`:

    the caller is a destination platform, so the delivery authenticates by its

    `X-Signature-HMAC-SHA256-{n}` header (Base64 HMAC-SHA256 over the raw body,

    any active signing key, timing-safe), the same convention composerID's own

    outbound webhooks use.


    **Idempotency.** Publishing is idempotent on

    `{intent_id}-{intent_version}-{target_system}`. A replayed publish returns
    the

    stored receipt with `replayed: true` and never creates a second record in
    the

    destination system. Where a destination documents no idempotency header, the

    adapter dedupes by querying the destination's own client-supplied id before

    creating.


    **What crosses the boundary.** A destination receives a valid request plus
    the

    Intent ID. Diagnostic answers, scores and the Compliance File never leave

    composerID.
  contact:
    name: composerID developer access
    url: https://www.composer.id/authentication
servers:
  - url: https://sandbox.composer.id/v1
    description: >-
      Hosted reference sandbox: mock destination tenants, self-serve credentials
      at POST /sandbox/keys.
  - url: https://api.composer.id/v1
    description: 'Production. In build: see the status note above.'
  - url: http://127.0.0.1:8787/v1
    description: >-
      Local reference sandbox (python3 -m service.server), mock destination
      tenants.
security:
  - oauth2:
      - destinations:read
      - intents:read
      - intents:write
      - passthrough
      - publish
      - reconcile
  - bearerAuth: []
tags:
  - name: Auth
    description: >-
      OAuth 2.0 client credentials. Exchange a client id and secret for a
      short-lived bearer token carrying only the scopes the client was granted.
  - name: Intent
    description: Mint, read, amend and audit an intent. The timeline is append-only.
  - name: Routing
    description: >-
      Apply the Channel Map, then validate against the destination tenant's own
      requirements before publishing.
  - name: Publishing
    description: >-
      Idempotent publish, drift reconciliation, inbound destination events, and
      a raw escape hatch that stays anchored to the Intent ID.
  - name: Discovery
    description: What composerID can reach, and what each destination requires.
externalDocs:
  description: >-
    The composerID documentation: API reference, schemas and destination
    coverage
  url: https://www.composer.id/api-reference/overview
paths:
  /sandbox/keys:
    post:
      tags:
        - Auth
      summary: >-
        Mint self-serve sandbox credentials: a personal key, an OAuth client and
        a webhook signing key for a new tenant
      description: >-
        The sandbox issues its own credentials: no account, no form. One call
        creates a tenant with a personal API key, an OAuth client (id and secret
        for `POST /oauth/token`) and a webhook signing key, all carrying every
        scope and all expiring together (see `expires_at`). The secrets are
        shown once. Rate-limited per caller address; a 429 carries
        `Retry-After`. Sandbox only: destinations are mock tenants, and the
        production service will not expose this route.
      operationId: mintSandboxCredentials
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SandboxKeyRequest'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxCredentials'
        '400':
          description: Body is not a JSON object, or label is not a string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: >-
            rate_limited: the per-address allowance for this hour is spent;
            retry after `Retry-After` seconds
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security: []
components:
  schemas:
    SandboxKeyRequest:
      type: object
      additionalProperties: false
      description: Optional. A label helps you tell credential sets apart.
      properties:
        label:
          type: string
          maxLength: 80
          description: Free text, stored with the tenant.
    SandboxCredentials:
      type: object
      required:
        - tenant
        - api_key
        - client_id
        - client_secret
        - webhook_signing_key
        - scope
        - expires_at
        - token_endpoint
      additionalProperties: false
      description: >-
        A new sandbox tenant and every credential it needs. Shown once: the
        server keeps only hashes.
      properties:
        tenant:
          type: string
          description: >-
            Tenant id (`tn_...`). Intents are visible only to this tenant's
            credentials.
        label:
          type: string
        api_key:
          type: string
          description: >-
            Personal sandbox key (`sk_sandbox_...`), every scope. Send as
            `Authorization: Bearer <api_key>`.
        client_id:
          type: string
          description: OAuth 2.0 client id for `POST /oauth/token`.
        client_secret:
          type: string
          format: password
        webhook_signing_key:
          type: string
          description: >-
            Signing key for `POST /webhooks/{platform}` deliveries that
            reference this tenant's intents (`X-Signature-HMAC-SHA256-{n}`).
        scope:
          type: string
          description: Space-separated scopes the credentials carry (all of them).
        expires_at:
          type: string
          format: date-time
          description: When every credential in this set stops working; mint again after.
        token_endpoint:
          type: string
          const: /v1/oauth/token
        note:
          type: string
    Error:
      type: object
      required:
        - error
      additionalProperties: true
      properties:
        error:
          type: string
  securitySchemes:
    oauth2:
      type: oauth2
      description: >-
        Client credentials, the production model: server-to-server, short-lived
        tokens, least-privilege scopes, rotation by issuing a new client secret.
      flows:
        clientCredentials:
          tokenUrl: https://api.composer.id/v1/oauth/token
          scopes:
            intents:read: Read intent records and their timelines.
            intents:write: Create, patch, plan, preflight and enrich intents.
            publish: Publish an intent into a destination system.
            reconcile: Run reconciliation against a destination.
            passthrough: Raw platform calls anchored to an Intent ID.
            destinations:read: List platforms, destinations and capabilities.
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        A personal sandbox API key, minted by POST /sandbox/keys and carrying
        every scope; or a token from the oauth2 flow. Compared in constant time.
        Destination credentials are never passed by the caller.

````