openapi: 3.0.3
info:
  title: RunProof - Execution Proof API by Chamia
  version: "1.0.0"
servers:
  - url: https://runproof.chamia-20260215.workers.dev
security:
  - bearerAuth: []
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
  schemas:
    Tags:
      type: object
      nullable: true
      additionalProperties:
        type: string
    Receipt:
      type: object
      description: Signed receipt payload plus minimal operational metadata.
      required: [receipt_id, status]
      properties:
        receipt_id: { type: string }
        project_id: { type: string }
        actor_id: { type: string }
        created_at: { type: string, format: date-time }
        input_hash: { type: string, description: "sha256:<hex>" }
        output_hash: { type: string, nullable: true, description: "sha256:<hex>" }
        params_hash: { type: string, nullable: true, description: "sha256:<hex>" }
        env_hash: { type: string, nullable: true, description: "sha256:<hex>" }
        code_ref: { type: string, nullable: true }
        run_id: { type: string, nullable: true }
        tags: { $ref: '#/components/schemas/Tags' }
        receipt_kind:
          type: string
          nullable: true
          description: "Optional public receipt classification. MVP known values: release, document, audit_pack. Unknown values are out of scope for MVP."
          enum: [release, document, audit_pack]
        status:
          type: string
          description: "MVP currently returns issued. pending is reserved for future use."
          enum: [pending, issued]
        prev_receipt_hash: { type: string, nullable: true, description: "sha256:<hex>" }
        receipt_hash: { type: string, description: "sha256:<hex>" }
        signature: { type: string }
        sig_kid: { type: string }
        chain_status: { type: string, nullable: true, description: main|branch }
        expected_prev_receipt_hash: { type: string, nullable: true, description: "sha256:<hex>" }
    Idempotency:
      type: object
      required: [hit, key]
      properties:
        hit: { type: boolean }
        key:
          type: string
          description: "Opaque server-generated replay key. Treat as an opaque string."
    ReceiptEnvelope:
      type: object
      required: [ok, receipt]
      properties:
        ok: { type: boolean }
        receipt: { $ref: '#/components/schemas/Receipt' }
    ReceiptEnvelopeWithIdempotency:
      allOf:
        - $ref: '#/components/schemas/ReceiptEnvelope'
        - type: object
          required: [idempotency]
          properties:
            idempotency: { $ref: '#/components/schemas/Idempotency' }
            chain:
              type: object
              nullable: true
              properties:
                status: { type: string, nullable: true, description: main|branch }
                expected_prev_receipt_hash: { type: string, nullable: true, description: "sha256:<hex>" }
                prev_receipt_hash: { type: string, nullable: true, description: "sha256:<hex>" }
            quota:
              type: object
              nullable: true
              properties:
                plan: { type: string }
                monthly_limit: { type: integer }
                monthly_used: { type: integer }
                monthly_remaining: { type: integer }
                monthly_reset_at: { type: string, format: date-time }
paths:
  /health:
    get:
      summary: Health check / entrypoint
      security: []
      responses:
        "200":
          description: OK

  /v1/public-key:
    get:
      summary: Get public keys for signature verification
      security: []
      responses:
        "200":
          description: OK

  /v1/receipts:
    post:
      summary: Create a receipt (hash-only, low-output)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                input_hash:
                  type: string
                  description: "sha256:<hex>"
                receipt_kind:
                  type: string
                  nullable: true
                  description: "Optional public receipt classification. MVP known values: release, document, audit_pack. Unknown values are out of scope for MVP."
                  enum: [release, document, audit_pack]
                output_hash:
                  type: string
                  nullable: true
                  description: "sha256:<hex>"
                params_hash:
                  type: string
                  nullable: true
                  description: "sha256:<hex>"
                env_hash:
                  type: string
                  nullable: true
                  description: "sha256:<hex>"
                code_ref:
                  type: string
                  nullable: true
                run_id:
                  type: string
                  nullable: true
                prev_receipt_hash:
                  type: string
                  nullable: true
                  description: "sha256:<hex>"
                tags: { $ref: '#/components/schemas/Tags' }
              required: [input_hash]
      responses:
        "201":
          description: Created (new logical input)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReceiptEnvelopeWithIdempotency'
        "200":
          description: Idempotent replay (same logical input)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReceiptEnvelopeWithIdempotency'
        "400": { description: Bad request }
        "401": { description: Unauthorized }
        "403": { description: Forbidden }
        "429": { description: Rate limit / quota exceeded }

  /v1/receipts/{receipt_id}:
    get:
      summary: Get a receipt by id (public read; rate-limited)
      security: []
      parameters:
        - name: receipt_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReceiptEnvelope'
        "404": { description: Not found }
        "429": { description: Rate limit }

  /v1/verify:
    post:
      summary: Verify receipt signature and linkage (hash-only)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                receipt_id:
                  type: string
                input_hash:
                  type: string
                  description: "sha256:<hex>"
                output_hash:
                  type: string
                  nullable: true
                  description: "sha256:<hex>"
              required: [receipt_id, input_hash]
      responses:
        "200": { description: OK }
        "400": { description: Bad request }
        "401": { description: Unauthorized }
        "403": { description: Forbidden }
        "404": { description: Not found }

  /v1/claim:
    post:
      summary: Claim API key for a Stripe Checkout purchase
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                session_id:
                  type: string
              required: [session_id]
      responses:
        "200": { description: OK }
        "400": { description: Bad request }
        "404": { description: claim_pending (checkout indexed later) }
        "409": { description: claim_pending or already_claimed }
        "410": { description: claim_expired }
        "503": { description: claim_not_ready }

  /v1/claim/free:
    post:
      summary: Claim a free API key after Turnstile verification
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                turnstile_token:
                  type: string
              required: [turnstile_token]
      responses:
        "200": { description: OK }
        "400": { description: bad_request }
        "403": { description: forbidden }
        "429": { description: rate_limited }
        "500": { description: internal_error }
        "503": { description: not_configured }
