{
  "openapi": "3.1.0",
  "info": {
    "title": "CAIRL Public Integration API",
    "version": "0.8.0",
    "description": "Public contract for CAIRL hosted verification, OAuth token exchange, userinfo claims, token revocation, Shopify verification exchange, and outbound webhook events."
  },
  "servers": [
    {
      "url": "https://cairl.app",
      "description": "Production"
    },
    {
      "url": "https://staging.cairl.app",
      "description": "Staging"
    }
  ],
  "paths": {
    "/verify/start": {
      "get": {
        "summary": "Start hosted verification",
        "description": "Redirect a user to CAIRL's hosted verification flow using OAuth 2.0 Authorization Code with PKCE.",
        "operationId": "startHostedVerification",
        "parameters": [
          {
            "name": "client_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "redirect_uri",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uri"
            }
          },
          {
            "name": "state",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 16
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "examples": ["age_18_plus identity_verified"]
            }
          },
          {
            "name": "code_challenge",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "code_challenge_method",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "const": "S256"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Redirects the user into the hosted CAIRL verification flow."
          },
          "400": {
            "description": "Missing, malformed, or unauthorized authorization parameter."
          }
        }
      }
    },
    "/api/oauth/token": {
      "post": {
        "summary": "Exchange authorization code for access token",
        "operationId": "exchangeOAuthCode",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/OAuthTokenRequest"
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OAuthTokenRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Access token issued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthTokenResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, grant, redirect URI, or scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid client credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient wallet balance; no token is issued."
          },
          "403": {
            "description": "Client inactive or enrollment blocked."
          },
          "429": {
            "description": "Token or partner spend rate limit exceeded."
          },
          "503": {
            "description": "Spend rate-limit service unavailable; request fails closed."
          }
        }
      }
    },
    "/api/oauth/userinfo": {
      "get": {
        "summary": "Read issuance-time verified claims",
        "operationId": "getOAuthUserinfo",
        "security": [
          {
            "BearerAccessToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Verified claims snapshot evaluated when the access token was issued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserinfoResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired, revoked, or unknown bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Userinfo rate limit exceeded."
          }
        }
      }
    },
    "/api/oauth/revoke": {
      "post": {
        "summary": "Revoke an OAuth access token",
        "description": "Implements RFC 7009 token revocation. Unknown, expired, or already revoked tokens still return 200.",
        "operationId": "revokeOAuthToken",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/OAuthRevokeRequest"
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OAuthRevokeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Revocation accepted. Response body is empty."
          },
          "400": {
            "description": "Malformed request body or missing required parameter."
          },
          "401": {
            "description": "Invalid client credentials."
          },
          "429": {
            "description": "Token endpoint rate limit exceeded."
          }
        }
      }
    },
    "/api/verify/hvf-session/{session_id}": {
      "get": {
        "summary": "Query hosted verification session status",
        "operationId": "getHostedVerificationSession",
        "security": [
          {
            "PartnerApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "examples": ["hvf_abc123"]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Session status and metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HvfSessionResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key."
          },
          "403": {
            "description": "API key does not own this session."
          },
          "404": {
            "description": "Session not found or expired."
          },
          "500": {
            "description": "Internal server error."
          }
        }
      }
    },
    "/api/integrations/shopify/verify/exchange": {
      "post": {
        "summary": "Exchange Shopify hosted verification code",
        "operationId": "exchangeShopifyVerificationCode",
        "parameters": [
          {
            "name": "X-CAIRL-Integration-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "examples": ["shopify"]
            }
          },
          {
            "name": "X-CAIRL-Timestamp",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "description": "Unix seconds used in the HMAC canonical message."
            }
          },
          {
            "name": "X-CAIRL-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^sha256=[0-9a-f]{64}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ShopifyExchangeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verified Shopify buyer claims.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShopifyExchangeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body."
          },
          "401": {
            "description": "Invalid HMAC signature or missing signing headers."
          },
          "404": {
            "description": "Shop or authorization code not found."
          },
          "422": {
            "description": "Verification did not produce a positive claim or enrollment was blocked."
          },
          "429": {
            "description": "Integration or partner spend rate limit exceeded."
          },
          "503": {
            "description": "Spend rate-limit service unavailable; request fails closed."
          }
        }
      }
    }
  },
  "webhooks": {
    "cairlEvents": {
      "post": {
        "summary": "Receive CAIRL outbound webhook events",
        "description": "CAIRL sends signed HTTP POST requests to the webhook URL configured on a partner API key.",
        "parameters": [
          {
            "name": "X-CAIRL-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^sha256=[0-9a-f]{64}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/VerificationSessionCompletedEvent"
                  },
                  {
                    "$ref": "#/components/schemas/VerificationSessionFailedEvent"
                  },
                  {
                    "$ref": "#/components/schemas/EnrollmentCreatedEvent"
                  },
                  {
                    "$ref": "#/components/schemas/VaeResolvedEvent"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Partner accepted the event. Any 2xx response acknowledges delivery."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAccessToken": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT"
      },
      "PartnerApiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Partner API key in the Authorization header."
      }
    },
    "schemas": {
      "ClaimsMap": {
        "type": "object",
        "description": "Verified claims keyed by requested claim identifier. CAIRL returns claims, not raw identity documents or default raw PII.",
        "additionalProperties": {
          "oneOf": [
            {
              "type": "boolean"
            },
            {
              "type": "string"
            },
            {
              "type": "number"
            },
            {
              "type": "null"
            }
          ]
        },
        "examples": [
          {
            "age_18_plus": true,
            "identity_verified": true
          }
        ]
      },
      "OAuthTokenRequest": {
        "type": "object",
        "required": [
          "grant_type",
          "code",
          "client_id",
          "client_secret",
          "redirect_uri",
          "code_verifier"
        ],
        "properties": {
          "grant_type": {
            "type": "string",
            "const": "authorization_code"
          },
          "code": {
            "type": "string"
          },
          "client_id": {
            "type": "string"
          },
          "client_secret": {
            "type": "string"
          },
          "redirect_uri": {
            "type": "string",
            "format": "uri"
          },
          "code_verifier": {
            "type": "string"
          }
        }
      },
      "OAuthTokenResponse": {
        "type": "object",
        "required": ["access_token", "token_type", "expires_in", "scope"],
        "properties": {
          "access_token": {
            "type": "string"
          },
          "token_type": {
            "type": "string",
            "const": "Bearer"
          },
          "expires_in": {
            "type": "integer",
            "const": 3600
          },
          "scope": {
            "type": "string"
          }
        }
      },
      "UserinfoResponse": {
        "type": "object",
        "required": ["sub", "evaluated_at", "claims", "meta"],
        "properties": {
          "sub": {
            "type": "string",
            "description": "Stable, per-partner pseudonymous subject."
          },
          "evaluated_at": {
            "type": "string",
            "format": "date-time",
            "description": "Token issuance timestamp. Stable for the lifetime of the token."
          },
          "claims": {
            "$ref": "#/components/schemas/ClaimsMap"
          },
          "meta": {
            "type": "object",
            "required": [
              "claims_requested",
              "claims_resolved",
              "claims_null",
              "claims_ignored"
            ],
            "properties": {
              "claims_requested": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "claims_resolved": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "claims_null": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "claims_ignored": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "OAuthRevokeRequest": {
        "type": "object",
        "required": ["token", "client_id", "client_secret"],
        "properties": {
          "token": {
            "type": "string"
          },
          "token_type_hint": {
            "type": "string",
            "enum": ["access_token"]
          },
          "client_id": {
            "type": "string"
          },
          "client_secret": {
            "type": "string"
          }
        }
      },
      "OAuthErrorResponse": {
        "type": "object",
        "required": ["error", "error_description"],
        "properties": {
          "error": {
            "type": "string"
          },
          "error_description": {
            "type": "string"
          }
        }
      },
      "HvfSessionResponse": {
        "type": "object",
        "required": ["success", "session"],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "session": {
            "type": "object",
            "required": [
              "sessionId",
              "status",
              "scopes",
              "isTestMode",
              "createdAt",
              "expiresAt"
            ],
            "properties": {
              "sessionId": {
                "type": "string"
              },
              "status": {
                "type": "string",
                "enum": ["pending", "authenticated", "verified", "complete"]
              },
              "scopes": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "isTestMode": {
                "type": "boolean"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              },
              "expiresAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        }
      },
      "ShopifyExchangeRequest": {
        "type": "object",
        "required": ["code", "redirect_uri", "shop_domain", "customer_id"],
        "properties": {
          "code": {
            "type": "string"
          },
          "redirect_uri": {
            "type": "string",
            "format": "uri"
          },
          "shop_domain": {
            "type": "string",
            "examples": ["merchant.myshopify.com"]
          },
          "customer_id": {
            "type": "string"
          }
        }
      },
      "ShopifyExchangeResponse": {
        "type": "object",
        "required": [
          "verified",
          "claims",
          "verification_level",
          "enrollment_id"
        ],
        "properties": {
          "verified": {
            "type": "boolean",
            "const": true
          },
          "claims": {
            "$ref": "#/components/schemas/ClaimsMap"
          },
          "verification_level": {
            "type": "string"
          },
          "enrollment_id": {
            "type": "string"
          }
        }
      },
      "VerificationSessionCompletedEvent": {
        "type": "object",
        "required": [
          "event",
          "event_id",
          "session_id",
          "partner_id",
          "user_id",
          "status",
          "scopes",
          "completed_at"
        ],
        "properties": {
          "event": {
            "type": "string",
            "const": "verification.session.completed"
          },
          "event_id": {
            "type": "string"
          },
          "session_id": {
            "type": "string"
          },
          "partner_id": {
            "type": "string"
          },
          "user_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "const": "complete"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "completed_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "VerificationSessionFailedEvent": {
        "type": "object",
        "required": [
          "event",
          "event_id",
          "session_id",
          "partner_id",
          "failure_reason",
          "failed_at"
        ],
        "properties": {
          "event": {
            "type": "string",
            "const": "verification.session.failed"
          },
          "event_id": {
            "type": "string"
          },
          "session_id": {
            "type": "string"
          },
          "partner_id": {
            "type": "string"
          },
          "failure_reason": {
            "type": "string"
          },
          "failed_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "EnrollmentCreatedEvent": {
        "type": "object",
        "required": [
          "event",
          "event_id",
          "enrollment_id",
          "partner_id",
          "user_id",
          "created_at"
        ],
        "properties": {
          "event": {
            "type": "string",
            "const": "enrollment.created"
          },
          "event_id": {
            "type": "string"
          },
          "enrollment_id": {
            "type": "string"
          },
          "partner_id": {
            "type": "string"
          },
          "user_id": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "VaeResolvedEvent": {
        "type": "object",
        "required": [
          "event",
          "event_id",
          "partner_id",
          "user_id",
          "claims",
          "resolved_at"
        ],
        "properties": {
          "event": {
            "type": "string",
            "const": "vae.resolved"
          },
          "event_id": {
            "type": "string"
          },
          "partner_id": {
            "type": "string"
          },
          "user_id": {
            "type": "string"
          },
          "claims": {
            "$ref": "#/components/schemas/ClaimsMap"
          },
          "resolved_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      }
    }
  }
}
