openapi: 3.1.0
info:
  title: Qlaas DMI line — agent-facing HTTP surface
  version: 0.2.0
  summary: Discovery, OAuth 2.1, AgentLine (MCP and A2A) and the public receipt verifier of one Qlaas DMI line.
  description: |
    Derived from the `UIE-founder/qlaas-dmi` repository (`src/server.ts`, `src/dmi/manifest.ts`,
    `src/agentline/service.ts`, `src/agentline/oauth.ts`, `src/agentline/receipts.ts`) on 1 October 2026.
    Where this document and the code disagree, the code is right.

    One line is one origin. Hosted lines will live at `https://dmi.qlaas.co.uk` (not provisioned yet);
    the handle page `/@handle` is for people, the documents below are for machines.
    Owner-only endpoints under `/api/*` are out of scope.

    Rate limits: public GETs 300/min per IP (429 + Retry-After); `/agentline/*` 60/min per IP
    (JSON-RPC error -32029); `/oauth/register` and `/oauth/authorize` share the same per-IP budget (429 `slow_down`).
  contact:
    name: qlaas
    url: https://qlaas.co.uk/dmi/operators
servers:
  - url: https://dmi.qlaas.co.uk
    description: Hosted lines (planned, not live).
  - url: http://localhost:8787
    description: A self-hosted line (`npx qlaas-dmi serve`).
tags:
  - name: discovery
  - name: oauth
  - name: agentline
  - name: receipts

paths:
  /.well-known/dmi.json:
    get:
      tags: [discovery]
      summary: Signed DMI manifest (worldauth.dmi.communications/0.1)
      description: Cached 30 s with an ETag; send If-None-Match to get 304.
      responses:
        "200":
          description: The manifest.
          headers:
            ETag: { schema: { type: string } }
            Cache-Control: { schema: { type: string, example: "public, max-age=30" } }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DmiManifest" }
        "304": { description: Not modified. }
        "429": { description: Too many public reads from this IP. }

  /.well-known/agent-card.json:
    get:
      tags: [discovery]
      summary: Signed A2A 1.0 agent card
      responses:
        "200":
          description: The card, with a detached Ed25519 signature over its canonical JSON.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AgentCard" }
        "304": { description: Not modified. }

  /.well-known/playbakk.json:
    get:
      tags: [discovery]
      summary: Line (federation) descriptor
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
                required: [v, handle, name, door, agentCard, identityKey]
                properties:
                  v: { const: "playbakk.line/0.1" }
                  handle: { type: string }
                  name: { type: string }
                  door: { type: string, format: uri }
                  agentCard: { type: string, format: uri }
                  identityKey: { $ref: "#/components/schemas/Ed25519Jwk" }
                  dmi: { type: string, format: uri }

  /.well-known/playbakk/receipt-key.json:
    get:
      tags: [discovery]
      summary: JWKS with the line's Ed25519 receipt/identity key
      description: The path keeps the repository's name; there is no Qlaas-named alias in the code.
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
                required: [keys]
                properties:
                  keys:
                    type: array
                    items: { $ref: "#/components/schemas/Ed25519Jwk" }

  /.well-known/oauth-authorization-server:
    get:
      tags: [oauth]
      summary: RFC 8414 authorization server metadata
      responses:
        "200":
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AuthorizationServerMetadata" }

  /.well-known/oauth-protected-resource:
    get:
      tags: [oauth]
      summary: RFC 9728 protected resource metadata (resource = /agentline/mcp)
      description: Also served at `/.well-known/oauth-protected-resource/agentline/mcp`. Pointed to by the `WWW-Authenticate` header on a 401.
      responses:
        "200":
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ProtectedResourceMetadata" }

  /oauth/register:
    post:
      tags: [oauth]
      summary: RFC 7591 dynamic client registration (public clients only)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [redirect_uris]
              properties:
                client_name: { type: string, maxLength: 80, default: Unnamed agent }
                redirect_uris:
                  type: array
                  minItems: 1
                  maxItems: 5
                  items: { type: string, format: uri }
                  description: https, or http on localhost / 127.0.0.1 / [::1]; no fragments.
                token_endpoint_auth_method: { const: none }
      responses:
        "201":
          content:
            application/json:
              schema:
                type: object
                properties:
                  client_id: { type: string, example: pbk_6k3hJ9sYq2Lw1x8a }
                  client_name: { type: string }
                  redirect_uris: { type: array, items: { type: string } }
                  created_at: { type: integer, description: Unix ms }
                  token_endpoint_auth_method: { const: none }
                  grant_types: { type: array, items: { enum: [authorization_code, refresh_token] } }
                  response_types: { type: array, items: { const: code } }
        "400": { $ref: "#/components/responses/OAuthError" }
        "429": { description: slow_down, or the client limit (200) was reached. }

  /oauth/authorize:
    get:
      tags: [oauth]
      summary: Authorization request (code + PKCE S256). Redirects to the owner's consent page.
      parameters:
        - { name: response_type, in: query, required: true, schema: { const: code } }
        - { name: client_id, in: query, required: true, schema: { type: string } }
        - { name: redirect_uri, in: query, required: true, schema: { type: string, format: uri }, description: Must be registered for the client. }
        - { name: code_challenge, in: query, required: true, schema: { type: string, pattern: "^[A-Za-z0-9_-]{43}$" } }
        - { name: code_challenge_method, in: query, required: true, schema: { const: S256 } }
        - { name: scope, in: query, required: false, schema: { const: agentline } }
        - { name: state, in: query, required: false, schema: { type: string } }
        - { name: resource, in: query, required: false, schema: { type: string, format: uri }, description: RFC 8707. Absolute URI without fragment. Bind to the line origin to use both MCP and A2A with one grant. }
      responses:
        "302":
          description: To `/consent?req=<id>`. After the owner decides, the browser is sent to `redirect_uri` with `code`, `state` and `iss`, or `error=access_denied`. The request expires after 10 minutes.
        "400": { $ref: "#/components/responses/OAuthError" }
        "429": { description: slow_down }

  /oauth/token:
    post:
      tags: [oauth]
      summary: Token endpoint (authorization_code, refresh_token)
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema: { $ref: "#/components/schemas/TokenRequest" }
          application/json:
            schema: { $ref: "#/components/schemas/TokenRequest" }
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
                required: [access_token, token_type, expires_in, refresh_token, scope]
                properties:
                  access_token: { type: string }
                  token_type: { const: Bearer }
                  expires_in: { const: 3600 }
                  refresh_token: { type: string, description: Valid 30 days; rotates on use. Replaying a spent refresh token revokes the whole grant family. }
                  scope: { const: agentline }
        "400": { $ref: "#/components/responses/OAuthError" }
        "401": { $ref: "#/components/responses/OAuthError" }

  /oauth/revoke:
    post:
      tags: [oauth]
      summary: Revoke one token (access or refresh)
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [token]
              properties:
                token: { type: string }
      responses:
        "200": { description: Always empty JSON, whether or not the token existed. }

  /agentline/mcp:
    post:
      tags: [agentline]
      summary: MCP over streamable HTTP (single JSON response), protocol 2025-06-18
      description: |
        Methods: `initialize`, `ping`, `tools/list`, `tools/call`. Notifications (no `id`) get 202 with no body.
        Declare the agent in `params._meta.agent` (`vendor`, `name`, `onBehalfOf`) and a voice upgrade code in `params._meta.upgradeCode`.
        Without a bearer token or a trusted signature the caller is tier `declared` unless the line requires auth.
      security:
        - {}
        - oauth2: [agentline]
        - signedRequest: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - $ref: "#/components/schemas/McpInitialize"
                - $ref: "#/components/schemas/McpSimple"
                - $ref: "#/components/schemas/McpToolsCall"
      responses:
        "200":
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/McpToolsCallResult"
                  - $ref: "#/components/schemas/McpToolsListResult"
                  - $ref: "#/components/schemas/JsonRpcError"
        "202": { description: Notification accepted. }
        "400": { description: JSON parse error (-32700). }
        "401": { $ref: "#/components/responses/AgentUnauthorized" }
        "429": { description: Rate limited (JSON-RPC -32029). }

  /agentline/a2a:
    post:
      tags: [agentline]
      summary: A2A 1.0 JSON-RPC (message/send, tasks/get)
      description: |
        `message/send` (alias `SendMessage`) takes a `data` part `{ skill, args }`; a message with only `text` parts becomes `leave_message`.
        Declare the agent in `message.metadata.agent`; the voice upgrade code goes in `message.metadata.upgradeCode`.
        `tasks/get` (alias `GetTask`) polls a task, which is how a STEP_UP resolves. No streaming, no push notifications.
      security:
        - {}
        - oauth2: [agentline]
        - signedRequest: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - $ref: "#/components/schemas/A2aMessageSend"
                - $ref: "#/components/schemas/A2aTasksGet"
      responses:
        "200":
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/A2aTaskResult"
                  - $ref: "#/components/schemas/JsonRpcError"
        "400": { description: JSON parse error (-32700). }
        "401": { $ref: "#/components/responses/AgentUnauthorized" }
        "429": { description: Rate limited (JSON-RPC -32029). }

  /receipts/{id}:
    get:
      tags: [receipts]
      summary: Public receipt verifier
      description: The id is an unguessable capability handed to the counterparty. Anyone holding it can confirm the line issued the receipt and that it is intact.
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, pattern: "^rcpt_[A-Za-z0-9_-]{12}$" } }
      responses:
        "200":
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ReceiptVerification" }
        "404": { description: "{ error: \"no such receipt\" }" }

components:
  securitySchemes:
    oauth2:
      type: oauth2
      description: OAuth 2.1 authorization code with PKCE (S256), public clients, dynamic registration. Tokens are opaque bearer tokens.
      flows:
        authorizationCode:
          authorizationUrl: https://dmi.qlaas.co.uk/oauth/authorize
          tokenUrl: https://dmi.qlaas.co.uk/oauth/token
          refreshUrl: https://dmi.qlaas.co.uk/oauth/token
          scopes:
            agentline: Reach the owner through AgentLine, under their mandate
    signedRequest:
      type: apiKey
      in: header
      name: Signature
      description: |
        Verified-operator signing (Web Bot Auth style, not RFC 9421). Three headers:
        `Signature-Agent` (key id the owner registered), `Signature-Created` (unix seconds, ±300 s),
        `Signature` (base64url Ed25519 over `${Signature-Created}.${rawBody}`). Each signature is accepted once.

  responses:
    OAuthError:
      description: RFC 6749 error body.
      content:
        application/json:
          schema:
            type: object
            required: [error, error_description]
            properties:
              error: { type: string, enum: [invalid_request, invalid_client, invalid_grant, invalid_scope, invalid_target, invalid_redirect_uri, invalid_client_metadata, unsupported_response_type, unsupported_grant_type] }
              error_description: { type: string }
    AgentUnauthorized:
      description: Bearer token invalid, or the line requires authentication (`QLAAS_AGENTLINE_REQUIRE_AUTH=1` / `QLAAS_MCP_REQUIRE_AUTH=1`).
      headers:
        WWW-Authenticate:
          schema: { type: string, example: 'Bearer resource_metadata="https://dmi.qlaas.co.uk/.well-known/oauth-protected-resource", error="invalid_token"' }
      content:
        application/json:
          schema: { $ref: "#/components/schemas/JsonRpcError" }

  schemas:
    Ed25519Jwk:
      type: object
      required: [kty, crv, x, kid, alg, use]
      properties:
        kty: { const: OKP }
        crv: { const: Ed25519 }
        x: { type: string, description: base64url public key }
        kid: { type: string, example: pbk-3f9a1c2b7e4d5a60, description: "`pbk-` + first 16 chars of sha256(SPKI DER), base64url" }
        alg: { const: EdDSA }
        use: { const: sig }

    DetachedSignature:
      type: object
      required: [keyId, alg, canonicalization, signature]
      properties:
        keyId: { type: string }
        alg: { const: EdDSA }
        canonicalization: { const: JCS }
        signature: { type: string, description: base64url Ed25519 over JCS(document without `signatures`) }

    Decision:
      type: string
      enum: [ALLOW, STEP_UP, DENY]

    Tier:
      type: string
      enum: [declared, verified, observed, inferred]
      description: Agent callers are `declared` (self-described) or `verified` (OAuth-approved by the owner, or a fresh signature from an owner-trusted key).

    SkillId:
      type: string
      enum: [leave_message, request_callback, check_availability, propose_meeting, urgent_patch_through]

    AgentDeclaration:
      type: object
      description: What the agent says about itself. Honoured as `declared` identity; for an OAuth-authorised caller only `onBehalfOf` is read.
      properties:
        vendor: { type: string, maxLength: 64 }
        name: { type: string, maxLength: 80 }
        onBehalfOf: { type: string, maxLength: 80, description: The principal, shown to the owner verbatim. }

    LeaveMessageArgs:
      type: object
      required: [text]
      additionalProperties: false
      properties:
        text: { type: string, minLength: 1, maxLength: 2000 }
        from: { type: string, maxLength: 80 }
        onBehalfOf: { type: string, maxLength: 80 }
        callback: { type: string, maxLength: 80 }
    RequestCallbackArgs:
      type: object
      required: [reason]
      additionalProperties: false
      properties:
        reason: { type: string, minLength: 1, maxLength: 500 }
        urgency: { type: string, enum: [low, normal, high] }
        callback: { type: string, maxLength: 80 }
        onBehalfOf: { type: string, maxLength: 80 }
    CheckAvailabilityArgs:
      type: object
      required: [durationMin]
      additionalProperties: false
      properties:
        durationMin: { type: integer, minimum: 5, maximum: 240 }
        days: { type: integer, minimum: 1, maximum: 14, default: 7 }
    ProposeMeetingArgs:
      type: object
      required: [start, durationMin, topic]
      additionalProperties: false
      properties:
        start: { type: string, format: date-time, maxLength: 40 }
        durationMin: { type: integer, minimum: 5, maximum: 240 }
        topic: { type: string, minLength: 1, maxLength: 200 }
        onBehalfOf: { type: string, maxLength: 80 }
    UrgentPatchThroughArgs:
      type: object
      required: [reason]
      additionalProperties: false
      properties:
        reason: { type: string, minLength: 1, maxLength: 500 }
        onBehalfOf: { type: string, maxLength: 80 }
        callback: { type: string, maxLength: 80 }

    SkillArgs:
      oneOf:
        - $ref: "#/components/schemas/LeaveMessageArgs"
        - $ref: "#/components/schemas/RequestCallbackArgs"
        - $ref: "#/components/schemas/CheckAvailabilityArgs"
        - $ref: "#/components/schemas/ProposeMeetingArgs"
        - $ref: "#/components/schemas/UrgentPatchThroughArgs"

    SkillResult:
      description: The executed result (ALLOW, or STEP_UP after the owner approved). Absent while working or when rejected.
      oneOf:
        - type: object
          title: leave_message / request_callback
          properties: { delivered: { const: true }, ref: { type: string } }
        - type: object
          title: check_availability
          properties:
            slots: { type: array, items: { type: object, properties: { start: { type: string, format: date-time }, end: { type: string, format: date-time } } } }
            tz: { const: UTC }
            disclosure: { const: free/busy only }
        - type: object
          title: propose_meeting
          properties: { confirmed: { const: true }, start: { type: string }, durationMin: { type: integer } }
        - type: object
          title: urgent_patch_through
          properties: { ringing: { const: true }, note: { type: string } }
        - type: object
          title: failed
          properties: { error: { type: string } }

    JsonRpcError:
      type: object
      required: [jsonrpc, id, error]
      properties:
        jsonrpc: { const: "2.0" }
        id: { type: [integer, string, "null"] }
        error:
          type: object
          properties:
            code: { type: integer, description: "-32700 parse, -32600 invalid request, -32601 method not found, -32602 invalid params, -32001 authorization required / task not found, -32029 rate limited" }
            message: { type: string }

    McpInitialize:
      type: object
      required: [jsonrpc, id, method]
      properties:
        jsonrpc: { const: "2.0" }
        id: { type: [integer, string] }
        method: { const: initialize }
        params: { type: object }
    McpSimple:
      type: object
      required: [jsonrpc, id, method]
      properties:
        jsonrpc: { const: "2.0" }
        id: { type: [integer, string] }
        method: { type: string, enum: [ping, tools/list] }
    McpToolsCall:
      type: object
      required: [jsonrpc, id, method, params]
      properties:
        jsonrpc: { const: "2.0" }
        id: { type: [integer, string] }
        method: { const: tools/call }
        params:
          type: object
          required: [name, arguments]
          properties:
            name: { $ref: "#/components/schemas/SkillId" }
            arguments: { $ref: "#/components/schemas/SkillArgs" }
            _meta:
              type: object
              properties:
                agent: { $ref: "#/components/schemas/AgentDeclaration" }
                upgradeCode: { type: string, pattern: "^[0-9]{6}$" }
    McpToolsListResult:
      type: object
      properties:
        jsonrpc: { const: "2.0" }
        id: {}
        result:
          type: object
          properties:
            tools:
              type: array
              items:
                type: object
                properties:
                  name: { $ref: "#/components/schemas/SkillId" }
                  title: { type: string }
                  description: { type: string }
                  inputSchema: { type: object }
                  annotations: { type: object, properties: { readOnlyHint: { type: boolean }, openWorldHint: { const: false } } }
    McpToolsCallResult:
      type: object
      properties:
        jsonrpc: { const: "2.0" }
        id: {}
        result:
          type: object
          required: [content, structuredContent, isError]
          properties:
            content:
              type: array
              items: { type: object, properties: { type: { const: text }, text: { type: string } } }
            structuredContent:
              type: object
              required: [taskId, state, decision, result, receipts]
              properties:
                taskId: { type: string, example: task_Q2x9LmP3vKd7 }
                state: { type: string, enum: [working, completed, rejected, failed], description: "`working` = STEP_UP pending with the owner; poll tasks/get on /agentline/a2a." }
                decision: { $ref: "#/components/schemas/Decision" }
                result: { oneOf: [{ $ref: "#/components/schemas/SkillResult" }, { type: "null" }] }
                receipts: { type: array, items: { type: string, pattern: "^rcpt_" } }
            isError: { type: boolean, description: true when rejected or failed }

    A2aMessageSend:
      type: object
      required: [jsonrpc, id, method, params]
      properties:
        jsonrpc: { const: "2.0" }
        id: { type: [integer, string] }
        method: { type: string, enum: [message/send, SendMessage] }
        params:
          type: object
          required: [message]
          properties:
            message:
              type: object
              required: [parts]
              properties:
                role: { const: user }
                messageId: { type: string }
                contextId: { type: string, maxLength: 80, description: Reused on the task; generated when absent. }
                parts:
                  type: array
                  items:
                    oneOf:
                      - type: object
                        title: data part
                        properties:
                          kind: { const: data }
                          data:
                            type: object
                            required: [skill, args]
                            properties:
                              skill: { $ref: "#/components/schemas/SkillId" }
                              args: { $ref: "#/components/schemas/SkillArgs" }
                      - type: object
                        title: text part (becomes leave_message)
                        properties:
                          kind: { const: text }
                          text: { type: string }
                metadata:
                  type: object
                  properties:
                    agent: { $ref: "#/components/schemas/AgentDeclaration" }
                    upgradeCode: { type: string, pattern: "^[0-9]{6}$" }
    A2aTasksGet:
      type: object
      required: [jsonrpc, id, method, params]
      properties:
        jsonrpc: { const: "2.0" }
        id: { type: [integer, string] }
        method: { type: string, enum: [tasks/get, GetTask] }
        params:
          type: object
          required: [id]
          properties:
            id: { type: string, pattern: "^task_" }
    A2aTaskResult:
      type: object
      properties:
        jsonrpc: { const: "2.0" }
        id: {}
        result:
          type: object
          required: [kind, id, contextId, status, artifacts, metadata]
          properties:
            kind: { const: task }
            id: { type: string, pattern: "^task_" }
            contextId: { type: string }
            status:
              type: object
              properties:
                state: { type: string, enum: [working, input-required, completed, rejected, failed], description: "`input-required` = STEP_UP waiting for the owner on their device." }
                message:
                  type: object
                  properties:
                    role: { const: agent }
                    parts: { type: array, items: { type: object, properties: { kind: { const: text }, text: { type: string } } } }
            artifacts:
              type: array
              items:
                type: object
                properties:
                  artifactId: { type: string }
                  parts: { type: array, items: { type: object, properties: { kind: { const: data }, data: { $ref: "#/components/schemas/SkillResult" } } } }
            metadata:
              type: object
              properties:
                decision: { $ref: "#/components/schemas/Decision" }
                receipts: { type: array, items: { type: string, pattern: "^rcpt_" } }
                callId: { type: string, description: Present when a voice upgrade code bound this task to a call. }

    ReceiptPayload:
      type: object
      required: [v, id, at, prev, actor, action, decision, reasons]
      properties:
        v: { const: playbakk.receipt/0.1 }
        id: { type: string, pattern: "^rcpt_[A-Za-z0-9_-]{12}$" }
        at: { type: string, format: date-time }
        prev: { type: [string, "null"], description: sha256(JCS(previous signed receipt)), base64url; null for the first receipt. }
        actor:
          type: object
          properties:
            kind: { type: string, enum: [agent, owner, system] }
            id: { type: string, example: playbakk-agent }
        counterparty:
          type: object
          properties:
            id: { type: string, description: "Your key id (`oauth:<client_id>` or the trusted key id), otherwise your declared label." }
            kind: { const: agent }
            tier: { $ref: "#/components/schemas/Tier" }
        action: { type: string, example: agentline.propose_meeting }
        decision: { $ref: "#/components/schemas/Decision" }
        subject:
          type: object
          properties:
            taskId: { type: string }
            callId: { type: string }
            args: { type: object }
        reasons: { type: array, items: { type: string } }
    SignedReceipt:
      type: object
      required: [payload, signature, keyId]
      properties:
        payload: { $ref: "#/components/schemas/ReceiptPayload" }
        signature: { type: string, description: base64url Ed25519 over JCS(payload) }
        keyId: { type: string }
    ReceiptVerification:
      type: object
      required: [receipt, hash, verified, keyId, jwks, line]
      properties:
        receipt: { $ref: "#/components/schemas/SignedReceipt" }
        hash: { type: string, description: sha256(JCS(receipt)), base64url — what the next receipt's `prev` points at. }
        verified: { type: boolean, description: The line re-checked the signature on this fetch. Verify independently against the JWKS. }
        keyId: { type: string }
        jwks: { type: string, format: uri }
        line: { type: string, format: uri }

    AuthorizationServerMetadata:
      type: object
      properties:
        issuer: { type: string, format: uri }
        authorization_endpoint: { type: string, format: uri }
        token_endpoint: { type: string, format: uri }
        registration_endpoint: { type: string, format: uri }
        revocation_endpoint: { type: string, format: uri }
        response_types_supported: { type: array, items: { const: code } }
        grant_types_supported: { type: array, items: { enum: [authorization_code, refresh_token] } }
        code_challenge_methods_supported: { type: array, items: { const: S256 } }
        token_endpoint_auth_methods_supported: { type: array, items: { const: none } }
        scopes_supported: { type: array, items: { const: agentline } }
    ProtectedResourceMetadata:
      type: object
      properties:
        resource: { type: string, format: uri, example: https://dmi.qlaas.co.uk/agentline/mcp }
        authorization_servers: { type: array, items: { type: string, format: uri } }
        bearer_methods_supported: { type: array, items: { const: header } }
        scopes_supported: { type: array, items: { const: agentline } }
        resource_name: { type: string, example: playbakk AgentLine }
    TokenRequest:
      type: object
      required: [grant_type, client_id]
      properties:
        grant_type: { type: string, enum: [authorization_code, refresh_token] }
        client_id: { type: string }
        code: { type: string, description: authorization_code only }
        redirect_uri: { type: string, format: uri, description: authorization_code only; must equal the one used on /oauth/authorize }
        code_verifier: { type: string, pattern: "^[A-Za-z0-9._~-]{43,128}$", description: authorization_code only }
        refresh_token: { type: string, description: refresh_token only }
        resource: { type: string, format: uri, description: RFC 8707; must match the authorization request when both are given }

    DmiManifest:
      type: object
      required: [dmi, generatedAt, resource, state, capabilities, executionPaths, upgrade, evidence, discovery, signatures]
      properties:
        dmi: { const: worldauth.dmi.communications/0.1 }
        generatedAt: { type: string, format: date-time }
        resource:
          type: object
          properties:
            kind: { const: person.line }
            principal: { type: object, properties: { name: { type: string }, handle: { type: string } } }
            line: { type: string, format: uri }
            identity: { type: string, format: uri }
            agentCard: { type: string, format: uri }
            addresses:
              type: object
              properties:
                door: { type: string, format: uri }
                federated: { type: string, example: demo@dmi.qlaas.co.uk }
                tel: { type: string, description: Only when a phone line is attached. }
        state:
          type: object
          description: "`evidence: declared` unless the owner opted in to presence (then `observed` with acceptingCalls, ownerReachableNow and optional quietUntil)."
          properties:
            asOf: { type: string, format: date-time }
            evidence: { type: string, enum: [declared, observed] }
            preferredForMachines: { const: agentline }
            acceptingCalls: { type: boolean }
            ownerReachableNow: { type: boolean }
            quietUntil: { type: string, format: date-time }
        capabilities:
          type: array
          items:
            type: object
            properties:
              id: { type: string, description: A SkillId, or `voice_call` for people. }
              name: { type: string }
              description: { type: string }
              contract: { type: object, properties: { input: { type: object, description: JSON Schema }, output: { type: object } } }
              constraints: { type: array, items: { type: string } }
              consequences: { type: string }
              authority:
                type: object
                properties:
                  default: { $ref: "#/components/schemas/Decision" }
                  rules:
                    type: array
                    items:
                      type: object
                      properties:
                        effect: { $ref: "#/components/schemas/Decision" }
                        when:
                          type: object
                          properties:
                            tiers: { type: array, items: { $ref: "#/components/schemas/Tier" } }
                            vendors: { type: array, items: { type: string } }
                            maxMinutes: { type: integer }
              paths: { type: array, items: { type: string, enum: [mcp, a2a, webrtc, websocket-media, pstn] } }
              outcome: { type: string }
              evidence: { type: string }
        executionPaths:
          type: array
          items:
            type: object
            properties:
              rank: { type: integer }
              id: { type: string, enum: [mcp, a2a, webrtc, websocket-media, pstn] }
              protocol: { type: string }
              url: { type: string }
              for: { type: string, enum: [machines, people] }
              auth:
                type: array
                description: On the machine paths only.
                items:
                  type: object
                  properties:
                    scheme: { type: string, enum: [oauth2, http-message-signature, none] }
                    tier: { type: string }
                    metadata: { type: string, format: uri }
                    headers: { type: array, items: { type: string } }
                    alg: { const: Ed25519 }
                    note: { type: string }
              cdo: { type: object, description: Declared design estimates, not measurements. }
              evidence: { const: declared }
        upgrade:
          type: object
          properties:
            description: { type: string }
            field: { const: metadata.upgradeCode }
        evidence:
          type: object
          properties:
            receipts:
              type: object
              properties:
                alg: { const: EdDSA }
                canonicalization: { const: JCS }
                chained: { const: true }
                keyId: { type: string }
                key: { $ref: "#/components/schemas/Ed25519Jwk" }
                jwks: { type: string, format: uri }
            disclosure: { type: string }
        discovery:
          type: object
          properties:
            wellKnown: { type: array, items: { type: string, format: uri } }
            dns: { type: string, example: '_playbakk.<your-domain> TXT "v=pbk1; line=https://dmi.qlaas.co.uk; handle=demo"' }
        signatures:
          type: array
          items: { $ref: "#/components/schemas/DetachedSignature" }

    AgentCard:
      type: object
      properties:
        protocolVersion: { const: "1.0" }
        name: { type: string }
        description: { type: string }
        version: { type: string }
        provider: { type: object, properties: { organization: { type: string }, url: { type: string } } }
        supportedInterfaces:
          type: array
          items: { type: object, properties: { url: { type: string, format: uri }, protocolBinding: { const: JSONRPC }, protocolVersion: { const: "1.0" } } }
        capabilities: { type: object, properties: { streaming: { const: false }, pushNotifications: { const: false } } }
        securitySchemes: { type: object }
        security: { type: array }
        defaultInputModes: { type: array, items: { type: string } }
        defaultOutputModes: { type: array, items: { type: string } }
        skills:
          type: array
          items: { type: object, properties: { id: { $ref: "#/components/schemas/SkillId" }, name: { type: string }, description: { type: string }, tags: { type: array, items: { type: string } }, inputModes: { type: array, items: { type: string } } } }
        extensions:
          type: object
          properties:
            https://playbakk.com/ext/agentline/v0:
              type: object
              properties:
                mcp: { type: string, format: uri }
                dmi: { type: string, format: uri }
                upgrade: { type: string }
                receiptKey: { $ref: "#/components/schemas/Ed25519Jwk" }
                disclosure: { type: string }
                identity: { type: string }
        signatures:
          type: array
          items: { $ref: "#/components/schemas/DetachedSignature" }
