> ## 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.

# Generate destination plan from Channel Map + intent



## OpenAPI

````yaml /api-reference/openapi.json post /plan
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:
  /plan:
    post:
      tags:
        - Routing
      summary: Generate destination plan from Channel Map + intent
      operationId: plan
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PlanGenerateRequest'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlanGenerateResponse'
        '400':
          description: Malformed body or missing required field
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing, invalid or expired bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: 'insufficient_scope: the token lacks `intents:write`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Unknown intent or destination
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
        - oauth2:
            - intents:write
        - bearerAuth: []
components:
  schemas:
    PlanGenerateRequest:
      type: object
      required:
        - intent_id
      additionalProperties: false
      properties:
        intent_id:
          $ref: '#/components/schemas/IntentId'
        channel:
          type: string
          description: Work channel to route. Defaults to payload.channel on the intent.
          examples:
            - contingent_hire
            - services_sow
            - permanent_hire
            - signature
            - contract
            - purchase
        platform:
          type: string
          description: Explicit destination override, bypassing the Channel Map.
    PlanGenerateResponse:
      type: object
      required:
        - intent_id
        - intent_version
        - channel
        - destinations
        - rationale
      additionalProperties: false
      properties:
        intent_id:
          $ref: '#/components/schemas/IntentId'
        intent_version:
          type: integer
          minimum: 1
        channel:
          type: string
        destinations:
          type: array
          items:
            type: object
            required:
              - platform
              - object_type
              - carrier_field
              - mode
            properties:
              platform:
                type: string
              category:
                type: string
              object_type:
                type: string
              carrier_field:
                type: string
                description: Where the Intent ID is written in the destination record.
              mode:
                enum:
                  - stamp
                  - link
              events_posture:
                enum:
                  - push
                  - poll
                  - mixed
        rationale:
          type: string
          description: Why this destination was chosen. Deterministic and explainable.
    Error:
      type: object
      required:
        - error
      additionalProperties: true
      properties:
        error:
          type: string
    IntentId:
      type: string
      pattern: ^[A-Za-z0-9_-]{10,64}$
      description: >-
        Minted at the decision and carried unchanged into every destination
        record. Fits a 64-character client-supplied id field, which is how the
        same id finds the record in either direction.
      examples:
        - itn_7f3aK2mQ9xLp
  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.

````