openapi: 3.1.0
info:
  title: EU AI Act Provenance & Label API (C2PA verification)
  version: 1.0.0
  description: >
    Read, verify and report C2PA Content Credentials on any uploaded file, so a platform can
    prove it checked provenance and flag what arrived unlabelled. Deterministic, no PKI of your
    own required. Returns a verdict plus a signed audit record you can show a regulator.


    Not legal advice. Not an official EU or C2PA service. A passing verdict is not a compliance
    guarantee. Absence of a credential is not proof content is not AI-generated. All
    AI-generation signals derive from signed C2PA assertions — this is not a deepfake detector.
  license:
    name: MIT
servers:
  - url: /c2pa
    description: Behind the shared Caddy → APISIX gateway (path prefix /c2pa).
  - url: /
    description: Local dev (no prefix).

tags:
  - name: compute
    description: Public/free, rate limited. Optional API key unlocks a higher budget + retained audit history.
  - name: static
    description: Public reference data. Not rate limited.

security:
  - {}                    # anonymous allowed
  - bearerAuth: []        # optional API key

paths:
  /v1/verify:
    post:
      tags: [compute]
      summary: Verify one asset
      description: >
        Accepts a multipart file upload (`file` part), a JSON body `{ "url": "..." }`, or raw
        bytes. Returns the verdict, manifest summary, signer + trust-list status, AI-generation
        signal, and a signed audit record.
      parameters:
        - name: recover
          in: query
          description: Enable soft-binding recovery for stripped manifests (adds a network call).
          schema: { type: boolean, default: false }
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file: { type: string, format: binary }
          application/json:
            schema:
              type: object
              properties:
                url: { type: string, format: uri }
          application/octet-stream:
            schema: { type: string, format: binary }
      responses:
        "200":
          description: Verification result.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/VerifyResponse" }
        "400": { description: Bad request (e.g. image dimension exceeds 8000px, invalid body). }
        "413": { description: Request body too large. }
        "429": { $ref: "#/components/responses/RateLimited" }

  /v1/verify/batch:
    post:
      tags: [compute]
      summary: Verify many assets
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/BatchRequest" }
      responses:
        "200":
          description: Per-asset results + aggregate summary.
          content:
            application/json:
              schema:
                type: object
                properties:
                  summary:
                    type: object
                    properties:
                      total: { type: integer }
                      labelled: { type: integer }
                      unlabelled: { type: integer }
                      invalid: { type: integer }
                      indeterminate: { type: integer }
                  results:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string }
                        url: { type: string }
                        verdict: { type: string }
                        validationState: { type: [string, "null"] }
                        aiInferred: { type: string }
                        assetHash: { type: string }
                        error: { type: string }
        "400": { description: Bad request. }
        "429": { $ref: "#/components/responses/RateLimited" }

  /v1/report:
    post:
      tags: [compute]
      summary: Full manifest report
      description: Returns the complete parsed manifest store + raw C2PA validation_results, without a verdict reduction.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema: { type: object, properties: { file: { type: string, format: binary } } }
          application/json:
            schema: { type: object, properties: { url: { type: string, format: uri } } }
          application/octet-stream:
            schema: { type: string, format: binary }
      responses:
        "200":
          description: Manifest report.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Report" }
        "400": { description: Bad request. }
        "413": { description: Request body too large. }

  /v1/audit/{recordId}:
    get:
      tags: [compute]
      summary: Fetch a retained audit record
      parameters:
        - name: recordId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Signed audit record envelope.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AuditRecord" }
        "404": { description: Not found. }

  /v1/usage:
    get:
      tags: [compute]
      summary: API key usage and quota
      responses:
        "200":
          description: Usage counters for the caller.
          content:
            application/json:
              schema:
                type: object
                properties:
                  tier: { type: string }
                  quota: { type: [integer, "null"] }
                  verifications: { type: integer }
                  reports: { type: integer }
                  total: { type: integer }

  /api/v1/index.json:
    get: { tags: [static], summary: Endpoint catalogue, responses: { "200": { description: OK } } }
  /api/v1/meta.json:
    get: { tags: [static], summary: Regulation + engine + trust-list metadata, responses: { "200": { description: OK } } }
  /api/v1/formats.json:
    get: { tags: [static], summary: Supported media types + image cap, responses: { "200": { description: OK } } }
  /api/v1/status-codes.json:
    get: { tags: [static], summary: Verdict / state / aiInferred vocabulary + C2PA failure codes, responses: { "200": { description: OK } } }
  /api/v1/audit-key.json:
    get: { tags: [static], summary: Public key for offline audit-record verification, responses: { "200": { description: OK } } }

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: Optional API key. Anonymous access is allowed at a lower rate-limit budget.
  responses:
    RateLimited:
      description: Rate limit exceeded.
      headers:
        Retry-After: { schema: { type: integer } }
        RateLimit-Limit: { schema: { type: integer } }
        RateLimit-Remaining: { schema: { type: integer } }
        RateLimit-Reset: { schema: { type: integer } }
      content:
        application/json:
          schema:
            type: object
            properties:
              error: { type: string }
              retryAfterSeconds: { type: integer }
  schemas:
    VerifyResponse:
      $ref: ./schemas/verify-response.schema.json
    AuditRecord:
      $ref: ./schemas/audit-record.schema.json
    Report:
      $ref: ./schemas/report.schema.json
    BatchRequest:
      $ref: ./schemas/batch-request.schema.json
