openapi: 3.0.3
info:
  title: Akamai Cloud Queues + Notifications (nats-mq)
  description: |
    Globally distributed queue + notification service on the shared 27-region
    NATS JetStream substrate. SQS+SNS-shape HTTP API with subject-pattern
    subscribe, replay, in-browser direct subscribe, function-as-deliverer
    recipes, and cross-product KV→MQ bridge via `change_stream_topic`.

    Auth: paste your tenant bearer below ("Authorize" → "tenantBearer").
    Admin operations use a separate admin bearer (see /v1/admin/* paths).
  version: 0.1.0
servers:
  - url: https://mq.connected-cloud.io
    description: Production data plane (single hostname, per-path origin per ADR-006)
  - url: https://cp.mq.connected-cloud.io
    description: Production control plane (admin + onboarding)
security:
  - tenantBearer: []
tags:
  - name: queues
    description: SQS-shape — send/receive/ack/extend/nak; per-queue DLQ
  - name: topics
    description: SNS-shape — publish with subject-pattern routing; replay
  - name: subscriptions
    description: HTTP push or SSE delivery; per-sub circuit breaker (ADR-017)
  - name: admin
    description: Tenant CRUD, key issue/revoke, invite mint, audit
  - name: gate
    description: Open endpoints — gate-page submit + claim

paths:
  /v1/health:
    get:
      summary: Health check
      security: []
      tags: [queues]
      responses:
        '200':
          description: OK

  /v1/queues:
    get:
      summary: List your queues
      tags: [queues]
      responses:
        '200':
          description: tenant-scoped queue list
    post:
      summary: Create a queue
      tags: [queues]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string, example: "orders" }
                replicas: { type: integer, enum: [1,3,5], default: 3 }
                max_age: { type: string, example: "48h" }
      responses:
        '201':
          description: created

  /v1/queues/{queue}:
    get:
      summary: Queue info
      tags: [queues]
      parameters: [{ name: queue, in: path, required: true, schema: { type: string } }]
      responses:
        '200': { description: queue state }
    delete:
      summary: Delete queue
      tags: [queues]
      parameters: [{ name: queue, in: path, required: true, schema: { type: string } }]
      responses:
        '204': { description: deleted }

  /v1/queues/{queue}/messages:
    post:
      summary: Send a message
      tags: [queues]
      parameters: [{ name: queue, in: path, required: true, schema: { type: string } }]
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema: { type: string, format: binary }
      responses:
        '200': { description: '{queue, msg_id, stream, duplicate}' }
    get:
      summary: Long-poll receive (up to 20s)
      tags: [queues]
      parameters:
        - { name: queue, in: path, required: true, schema: { type: string } }
        - { name: wait, in: query, required: false, schema: { type: integer, default: 0, maximum: 20 } }
      responses:
        '200': { description: '{count, messages:[{ack_token, body_b64, msg_id, ...}]}' }

  /v1/queues/{queue}/messages/{ackToken}:
    delete:
      summary: Ack a message
      tags: [queues]
      parameters:
        - { name: queue, in: path, required: true, schema: { type: string } }
        - { name: ackToken, in: path, required: true, schema: { type: string } }
      responses:
        '204': { description: acked }

  /v1/queues/{queue}/messages/{ackToken}/extend:
    post:
      summary: Extend visibility (more processing time)
      tags: [queues]
      parameters:
        - { name: queue, in: path, required: true, schema: { type: string } }
        - { name: ackToken, in: path, required: true, schema: { type: string } }
      responses:
        '200': { description: extended }

  /v1/queues/{queue}/messages/{ackToken}/nak:
    post:
      summary: Nak (return to queue with backoff)
      tags: [queues]
      parameters:
        - { name: queue, in: path, required: true, schema: { type: string } }
        - { name: ackToken, in: path, required: true, schema: { type: string } }
        - { name: delay, in: query, required: false, schema: { type: string, example: "10s" } }
      responses:
        '200': { description: nak'd }

  /v1/queues/{queue}/dlq:
    get:
      summary: DLQ peek (non-destructive)
      tags: [queues]
      parameters:
        - { name: queue, in: path, required: true, schema: { type: string } }
        - { name: count, in: query, required: false, schema: { type: integer, default: 10 } }
      responses:
        '200': { description: dlq frames }

  /v1/topics:
    get:
      summary: List your topics
      tags: [topics]
      responses:
        '200': { description: topic list }
    post:
      summary: Create a topic
      tags: [topics]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string, example: "events" }
                replicas: { type: integer, enum: [1,3,5], default: 3 }
      responses:
        '201': { description: created }

  /v1/topics/{topic}:
    get:
      summary: Topic info
      tags: [topics]
      parameters: [{ name: topic, in: path, required: true, schema: { type: string } }]
      responses:
        '200': { description: topic state }
    delete:
      summary: Delete topic
      tags: [topics]
      parameters: [{ name: topic, in: path, required: true, schema: { type: string } }]
      responses:
        '204': { description: deleted }

  /v1/topics/{topic}/publish:
    post:
      summary: Publish to topic (subject from X-MQ-Subject header)
      tags: [topics]
      parameters:
        - { name: topic, in: path, required: true, schema: { type: string } }
        - { name: X-MQ-Subject, in: header, required: false, schema: { type: string, example: "orders.created" } }
        - { name: X-MQ-Dedup-Id, in: header, required: false, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object }
      responses:
        '200': { description: '{topic, subject, msg_id, stream, duplicate}' }

  /v1/topics/{topic}/publish/{subjectSuffix}:
    post:
      summary: Publish to topic (subject suffix in URL path)
      tags: [topics]
      parameters:
        - { name: topic, in: path, required: true, schema: { type: string } }
        - { name: subjectSuffix, in: path, required: true, schema: { type: string, example: "orders.created" } }
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object }
      responses:
        '200': { description: published }

  /v1/topics/{topic}/dlq:
    get:
      summary: Topic DLQ peek
      tags: [topics]
      parameters:
        - { name: topic, in: path, required: true, schema: { type: string } }
        - { name: count, in: query, required: false, schema: { type: integer, default: 10 } }
      responses:
        '200': { description: dlq frames }

  /v1/subscriptions:
    get:
      summary: List your subscriptions
      tags: [subscriptions]
      responses:
        '200': { description: subscriptions list }
    post:
      summary: Create a subscription (HTTP push or SSE)
      tags: [subscriptions]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [topic, delivery]
              properties:
                topic: { type: string, example: "events" }
                filter: { type: string, example: "orders.*.created" }
                delivery:
                  type: object
                  required: [type]
                  properties:
                    type: { type: string, enum: ["http_push","sse"] }
                    url: { type: string, format: uri }
                    secret: { type: string, description: "HMAC-SHA256 signing secret" }
      responses:
        '201':
          description: 'created subscription (returns id, topic, filter, consumer)'

  /v1/subscriptions/{id}:
    get:
      summary: Subscription state
      tags: [subscriptions]
      parameters: [{ name: id, in: path, required: true, schema: { type: string } }]
      responses:
        '200': { description: '{id, topic, filter, consumer, delivered, pending, in_flight, ...}' }
    delete:
      summary: Delete subscription
      tags: [subscriptions]
      parameters: [{ name: id, in: path, required: true, schema: { type: string } }]
      responses:
        '204': { description: tombstoned }

  /v1/subscriptions/{id}/stream:
    get:
      summary: SSE stream (long-lived, routed direct to LKE per ADR-006)
      tags: [subscriptions]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string } }
        - { name: session, in: query, required: false, schema: { type: string }, description: "Signed one-shot token (when EventSource can't pass auth header)" }
      responses:
        '200':
          description: 'text/event-stream; each frame is {subject, body_b64, msg_id, timestamp}'
          content:
            text/event-stream:
              schema: { type: string }

  /v1/metrics:
    get:
      summary: Prometheus exposition (per-tenant labels, curated set)
      tags: [queues]
      security: []
      responses:
        '200': { description: metrics }

  /v1/admin/tenants:
    get:
      summary: List tenants (admin)
      tags: [admin]
      security: [{ adminBearer: [] }]
      responses:
        '200': { description: tenant list }
    post:
      summary: Create tenant + initial bearer (returned once)
      tags: [admin]
      security: [{ adminBearer: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, properties: { tag: { type: string } } }
      responses:
        '201': { description: '{tenant, bearer, note}' }

  /v1/admin/tenants/{id}:
    get:
      summary: Tenant info
      tags: [admin]
      security: [{ adminBearer: [] }]
      parameters: [{ name: id, in: path, required: true, schema: { type: string } }]
      responses:
        '200': { description: tenant record }
    delete:
      summary: Delete tenant
      tags: [admin]
      security: [{ adminBearer: [] }]
      parameters: [{ name: id, in: path, required: true, schema: { type: string } }]
      responses:
        '204': { description: deleted }

  /v1/admin/tenants/{id}/keys:
    get:
      summary: List tenant's keys
      tags: [admin]
      security: [{ adminBearer: [] }]
      parameters: [{ name: id, in: path, required: true, schema: { type: string } }]
      responses:
        '200': { description: keys list }
    post:
      summary: Issue a new bearer for tenant (returned once)
      tags: [admin]
      security: [{ adminBearer: [] }]
      parameters: [{ name: id, in: path, required: true, schema: { type: string } }]
      responses:
        '201': { description: '{key, bearer, note}' }

  /v1/admin/keys/{hash}:
    delete:
      summary: Revoke a key by sha256 hash
      tags: [admin]
      security: [{ adminBearer: [] }]
      parameters: [{ name: hash, in: path, required: true, schema: { type: string } }]
      responses:
        '204': { description: revoked }

  /v1/admin/invites:
    get:
      summary: List invites
      tags: [admin]
      security: [{ adminBearer: [] }]
      parameters:
        - { name: include_claimed, in: query, required: false, schema: { type: boolean, default: false } }
      responses:
        '200': { description: invites list }
    post:
      summary: Mint an invite
      tags: [admin]
      security: [{ adminBearer: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                tag: { type: string }
                ttl_hours: { type: integer, default: 168 }
      responses:
        '201': { description: '{token, tag, expires_at}' }

  /v1/admin/invite-requests:
    get:
      summary: List gate-page invite requests
      tags: [admin]
      security: [{ adminBearer: [] }]
      responses:
        '200': { description: pending/approved/declined requests }

  /v1/admin/invite-requests/{id}/approve:
    post:
      summary: Approve gate request (mints invite for the requestor's email)
      tags: [admin]
      security: [{ adminBearer: [] }]
      parameters: [{ name: id, in: path, required: true, schema: { type: string } }]
      responses:
        '200': { description: '{request, invite}' }

  /v1/admin/invite-requests/{id}/decline:
    post:
      summary: Decline gate request
      tags: [admin]
      security: [{ adminBearer: [] }]
      parameters: [{ name: id, in: path, required: true, schema: { type: string } }]
      responses:
        '200': { description: declined }

  /v1/admin/audit:
    get:
      summary: Admin action audit log
      tags: [admin]
      security: [{ adminBearer: [] }]
      parameters:
        - { name: limit, in: query, required: false, schema: { type: integer, default: 200, maximum: 1000 } }
      responses:
        '200': { description: '{audit:[...], count}' }

  /api/request-invite:
    post:
      summary: Gate-page submission (open)
      tags: [gate]
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, email]
              properties:
                name: { type: string }
                email: { type: string, format: email }
                note: { type: string }
      responses:
        '200': { description: '{id, status:"pending"}' }

  /api/claim/{token}:
    get:
      summary: Preview invite (open)
      tags: [gate]
      security: []
      parameters: [{ name: token, in: path, required: true, schema: { type: string } }]
      responses:
        '200': { description: '{tag, expires_at, claimed}' }
    post:
      summary: Claim invite (open; mints tenant+bearer)
      tags: [gate]
      security: []
      parameters: [{ name: token, in: path, required: true, schema: { type: string } }]
      responses:
        '200': { description: '{tenant, bearer, note}' }

components:
  securitySchemes:
    tenantBearer:
      type: http
      scheme: bearer
      description: Per-tenant bearer issued via the control plane (claim flow or admin issue).
    adminBearer:
      type: http
      scheme: bearer
      description: Single admin bearer for cross-tenant ops + admin endpoints.
