openapi: 3.1.0
info:
  title: AF Verify API
  version: 0.1.0
  summary: Registered AI agent credential verification
  description: >-
    AF Verify validates signed AFV2 challenges from registered agent credentials.
    It does not prove that no human initiated or supervised the request.
  contact:
    name: MAYOFLUX
    url: https://mayoflux.tv
servers:
  - url: https://af.mayoflux.tv
    description: Production and public test service
tags:
  - name: Discovery
  - name: Verification
  - name: Dashboard
  - name: Applications
paths:
  /api/v1/health:
    get:
      tags: [Discovery]
      summary: Check API health
      operationId: getHealth
      responses:
        "200":
          description: Healthy
          content:
            application/json:
              schema:
                type: object
                required: [ok, service, protocol, time]
                properties:
                  ok: { type: boolean, const: true }
                  service: { type: string, const: af-verify }
                  protocol: { type: string, const: AFV2 }
                  time: { type: string, format: date-time }
  /.well-known/jwks.json:
    get:
      tags: [Discovery]
      summary: Read AF Verify token signing keys
      operationId: getJwks
      responses:
        "200":
          description: JSON Web Key Set
          content:
            application/json:
              schema:
                type: object
                required: [keys]
                properties:
                  keys:
                    type: array
                    items: { type: object, additionalProperties: true }
  /.well-known/af-verify-configuration:
    get:
      tags: [Discovery]
      summary: Discover AF Verify endpoints
      operationId: getConfiguration
      responses:
        "200":
          description: Discovery metadata
          content:
            application/json:
              schema: { type: object, additionalProperties: true }
  /api/v1/challenges:
    post:
      tags: [Verification]
      summary: Create a short-lived verification challenge
      operationId: createChallenge
      security:
        - OrgApiKey: []
        - SiteKey: []
      parameters:
        - in: header
          name: Origin
          schema: { type: string, format: uri }
          description: Required with a public site key and matched exactly against the project allowlist.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateChallenge"
      responses:
        "201":
          description: Challenge created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ChallengeEnvelope" }
        "400": { $ref: "#/components/responses/Error" }
        "401": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
        "429": { $ref: "#/components/responses/Error" }
  /api/v1/challenges/{challengeId}:
    get:
      tags: [Verification]
      summary: Fetch a public challenge envelope
      operationId: getChallenge
      parameters:
        - $ref: "#/components/parameters/ChallengeId"
      responses:
        "200":
          description: Public signing envelope
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ChallengeEnvelope" }
        "404": { $ref: "#/components/responses/Error" }
        "410": { $ref: "#/components/responses/Error" }
  /api/v1/verifications:
    post:
      tags: [Verification]
      summary: Submit an agent signature
      operationId: submitVerification
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [challenge_id, agent_id, key_id, signature, algorithm]
              properties:
                challenge_id: { type: string, pattern: "^chl_" }
                agent_id: { type: string, pattern: "^agt_" }
                key_id: { type: string, pattern: "^agk_" }
                signature: { type: string, minLength: 80, maxLength: 100 }
                algorithm: { type: string, const: Ed25519 }
      responses:
        "200":
          description: Verification completed
          content:
            application/json:
              schema: { $ref: "#/components/schemas/VerificationResult" }
        "400": { $ref: "#/components/responses/Error" }
        "403": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
        "410": { $ref: "#/components/responses/Error" }
        "422": { $ref: "#/components/responses/Error" }
        "429": { $ref: "#/components/responses/Error" }
  /api/v1/verifications/{verificationId}:
    get:
      tags: [Verification]
      summary: Read a verification result visible to the authenticated tenant
      operationId: getVerification
      security:
        - OrgApiKey: []
        - SessionCookie: []
      parameters:
        - in: path
          name: verificationId
          required: true
          schema: { type: string, pattern: "^ver_" }
      responses:
        "200":
          description: Verification result
          content:
            application/json:
              schema: { $ref: "#/components/schemas/VerificationResult" }
        "404": { $ref: "#/components/responses/Error" }
  /api/v1/tokens/introspect:
    post:
      tags: [Verification]
      summary: Validate and atomically redeem a verification token
      operationId: introspectToken
      security:
        - OrgApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [token]
              properties:
                token: { type: string, maxLength: 8192 }
      responses:
        "200":
          description: Token state and objective claims
          content:
            application/json:
              schema:
                type: object
                required: [active, request_id]
                properties:
                  active: { type: boolean }
                  reason: { type: string }
                  request_id: { type: string }
                  claims: { type: object, additionalProperties: true }
        "401": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
  /api/auth/register:
    post:
      tags: [Dashboard]
      summary: Register a user and initial organization
      operationId: register
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, email, password, organization]
              properties:
                name: { type: string, minLength: 2, maxLength: 100 }
                email: { type: string, format: email }
                password: { type: string, minLength: 12, maxLength: 256 }
                organization: { type: string, minLength: 2, maxLength: 120 }
      responses:
        "201": { description: Registered and signed in }
        "400": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
        "429": { $ref: "#/components/responses/Error" }
  /api/auth/login:
    post:
      tags: [Dashboard]
      summary: Start an authenticated dashboard session
      operationId: login
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password]
              properties:
                email: { type: string, format: email }
                password: { type: string }
      responses:
        "200": { description: Signed in }
        "401": { $ref: "#/components/responses/Error" }
        "429": { $ref: "#/components/responses/Error" }
  /api/access-requests:
    post:
      tags: [Applications]
      summary: Submit an API access application
      operationId: submitAccessRequest
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AccessRequest" }
      responses:
        "201":
          description: Application stored
        "400": { $ref: "#/components/responses/Error" }
        "429": { $ref: "#/components/responses/Error" }
components:
  securitySchemes:
    OrgApiKey:
      type: http
      scheme: bearer
      bearerFormat: AF Verify secret key
      description: Server-only organization/project API secret. Never use in browser code.
    SiteKey:
      type: apiKey
      in: header
      name: X-AF-Site-Key
      description: Public widget identifier; exact Origin allowlisting still applies.
    SessionCookie:
      type: apiKey
      in: cookie
      name: __Host-af_session
  parameters:
    ChallengeId:
      in: path
      name: challengeId
      required: true
      schema: { type: string, pattern: "^chl_" }
  responses:
    Error:
      description: Stable safe API error
      headers:
        X-Request-ID:
          schema: { type: string }
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
  schemas:
    Error:
      type: object
      required: [error, request_id]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code: { type: string }
            message: { type: string }
        request_id: { type: string }
    CreateChallenge:
      type: object
      required: [action, resource, scopes]
      properties:
        agent_id: { type: string, pattern: "^agt_" }
        action: { type: string, minLength: 1, maxLength: 120 }
        resource: { type: string, minLength: 1, maxLength: 200 }
        scopes:
          type: array
          maxItems: 32
          uniqueItems: true
          items: { type: string, pattern: "^[a-z0-9._-]+:[a-z0-9._-]+$" }
        context:
          type: object
          additionalProperties: true
    ChallengeEnvelope:
      type: object
      required: [challenge_id, nonce, protocol, issued_at, expires_at, signing_payload]
      properties:
        challenge_id: { type: string }
        nonce: { type: string }
        protocol: { type: string, const: AFV2 }
        audience: { type: string }
        origin: { type: string, format: uri }
        action: { type: string }
        resource: { type: string }
        scopes: { type: array, items: { type: string } }
        issued_at: { type: string, format: date-time }
        expires_at: { type: string, format: date-time }
        signing_payload: { type: string }
        verify_url: { type: string, format: uri }
        status_url: { type: string, format: uri }
        poll_token: { type: string, writeOnly: true }
    VerificationResult:
      type: object
      required: [verified, verification_id, checks, request_id]
      properties:
        verified: { type: boolean }
        verification_id: { type: string }
        agent:
          type: object
          additionalProperties: true
        authorization:
          type: object
          properties:
            action: { type: string }
            resource: { type: string }
            scopes: { type: array, items: { type: string } }
        checks:
          type: object
          additionalProperties: { type: boolean }
        token: { type: string, writeOnly: true }
        issued_at: { type: string, format: date-time }
        expires_at: { type: string, format: date-time }
        request_id: { type: string }
        error:
          type: object
          properties:
            code: { type: string }
            message: { type: string }
    AccessRequest:
      type: object
      required: [name, email, use_case, agreed]
      properties:
        name: { type: string, minLength: 2, maxLength: 100 }
        email: { type: string, format: email }
        organization: { type: string, maxLength: 120 }
        website: { type: string, format: uri }
        job_title: { type: string, maxLength: 120 }
        use_case: { type: string, minLength: 20, maxLength: 4000 }
        monthly_verifications: { type: string }
        agent_types: { type: string }
        sensitive_actions: { type: array, items: { type: string } }
        stage: { type: string }
        launch_timeline: { type: string }
        technical_information: { type: string, maxLength: 4000 }
        agreed: { type: boolean, const: true }
