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

# Validate intent against platform requirements



## OpenAPI

````yaml /api-reference/openapi.json post /preflight
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:
  /preflight:
    post:
      tags:
        - Routing
      summary: Validate intent against platform requirements
      operationId: preflight
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PreflightRequest'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PreflightResponse'
        '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'
        '422':
          description: >-
            The destination tenant requires fields the intent does not carry
            yet. The body's enrichment_request is the question to ask back
            upstream.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EnrichmentGap'
      security:
        - oauth2:
            - intents:write
        - bearerAuth: []
components:
  schemas:
    PreflightRequest:
      type: object
      required:
        - intent_id
        - platform
      additionalProperties: false
      properties:
        intent_id:
          $ref: '#/components/schemas/IntentId'
        platform:
          type: string
    PreflightResponse:
      type: object
      required:
        - intent_id
        - platform
        - profile
        - ok
        - missing_fields
      additionalProperties: false
      properties:
        intent_id:
          $ref: '#/components/schemas/IntentId'
        platform:
          type: string
        profile:
          type: string
          description: The MappingProfile validated against.
        ok:
          type: boolean
        missing_fields:
          type: array
          items:
            type: string
    Error:
      type: object
      required:
        - error
      additionalProperties: true
      properties:
        error:
          type: string
    EnrichmentGap:
      type: object
      required:
        - error
        - enrichment_request
      additionalProperties: false
      description: >-
        Preflight found fields the tenant requires that the intent does not
        carry yet.
      properties:
        error:
          type: string
        enrichment_request:
          $ref: '#/components/schemas/EnrichmentRequest'
    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
    EnrichmentRequest:
      title: EnrichmentRequest
      description: >-
        Emitted back to Triage when preflight finds missing platform-required
        fields. The publish is withheld until all gaps are filled — the intent
        is never half-published.
      type: object
      required:
        - type
        - intent_id
        - intent_version
        - target_system
        - profile
        - missing_fields
      properties:
        type:
          type: string
          const: enrichment_request
        intent_id:
          type: string
          pattern: ^[A-Za-z0-9_-]{10,64}$
        intent_version:
          type: integer
          minimum: 1
        target_system:
          type: string
          description: Provider key.
        profile:
          type: string
          description: MappingProfile name that flagged the gaps.
        missing_fields:
          type: array
          items: bf4997c4-066c-4256-a212-1220888667df
          minItems: 1
          description: Canonical source paths that are required but absent or empty.
      additionalProperties: false
      $defs:
        missing_field:
          oneOf:
            - type: string
              description: >-
                Canonical dot-path of the missing field (e.g.
                'location.country').
            - type: object
              required:
                - source_path
              properties:
                source_path:
                  type: string
                  description: Canonical dot-path.
                target_field:
                  type: string
                  description: Destination-dialect field name for context.
                hint:
                  type: string
                  description: Human-readable hint about what's needed.
              additionalProperties: false
      x-canonical-url: https://composer.id/schemas/enrichment-request.json
  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.

````