{
  "openapi": "3.1.1",
  "info": {
    "title": "AnyOAuth sign-in API",
    "version": "0.1.0",
    "description": "Confidential-backend sign-in. Only Google/GitHub are implemented. JSON code exchange, profile-only tokens, no refresh flow. Not a generic OAuth or OIDC authorization server."
  },
  "servers": [
    {
      "url": "https://api.anyoauth.com"
    }
  ],
  "x-docs-groups": {
    "createLoginSession": "Clean callback login",
    "loginResult": "Login result",
    "authorizationUrl": "Legacy browser authorization",
    "exchangeCode": "Code exchange",
    "profile": "Profile",
    "revoke": "Revocation",
    "providers": "Provider availability"
  },
  "paths": {
    "/v1/login/session": {
      "post": {
        "operationId": "createLoginSession",
        "x-sdk-auth": "client",
        "x-sdk-role": "server",
        "description": "Recommended clean-callback flow. Register a fresh 32-byte base64url session key from your backend and retain it in a browser-bound server session. Navigate the browser to authorization_url. The key and client secret must never appear in URLs. AnyOAuth returns to the exact registered callback without query parameters or fragments on success and failure. No automatic retries.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoginSessionRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Single-use browser launch URL; session expires in ten minutes",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LoginSessionResponse"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/login/result": {
      "post": {
        "operationId": "loginResult",
        "x-sdk-auth": "client",
        "x-sdk-role": "server",
        "description": "Fetch the clean-callback result from your backend using the original session key, client credentials and callback. Pending reads do not consume the session. A succeeded result carries profile; a failed result carries error. Terminal results are consumed atomically once; expiry, replay and invalid sessions return 400. Verify the initiating browser cookie before calling. No automatic retries after ambiguous failure.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoginResultRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Pending status or one-time verified profile/failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LoginResult"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/authorize": {
      "get": {
        "operationId": "authorizationUrl",
        "x-sdk-local": true,
        "description": "Construct this URL locally and navigate the browser. Persist state/verifier in the initiating application session; validate and consume the transaction on callback. Never include a client secret.",
        "parameters": [
          {
            "name": "client_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          },
          {
            "name": "provider",
            "in": "query",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/Provider"
            }
          },
          {
            "name": "redirect_uri",
            "in": "query",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/RedirectUri"
            }
          },
          {
            "name": "state",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 16,
              "maxLength": 512
            }
          },
          {
            "name": "code_challenge",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{43}$"
            }
          },
          {
            "name": "code_challenge_method",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "const": "S256"
            }
          },
          {
            "name": "response_type",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "const": "code"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Browser redirect to configured provider",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/token": {
      "post": {
        "operationId": "exchangeCode",
        "x-sdk-auth": "client",
        "x-sdk-role": "server",
        "description": "Client credentials are in the JSON body, not Basic auth. One-time redemption; no automatic retries after ambiguous failure. Token expires in 900 seconds.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TokenRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "AnyOAuth profile token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/profile": {
      "get": {
        "operationId": "profile",
        "x-sdk-auth": "bearer",
        "x-sdk-role": "server",
        "security": [
          {
            "ProfileToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Normalized identity; null fields are legitimate. Use subject, not email, as account key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Profile"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/revoke": {
      "post": {
        "operationId": "revoke",
        "x-sdk-auth": "bearer",
        "x-sdk-role": "server",
        "security": [
          {
            "ProfileToken": []
          }
        ],
        "description": "No body. Syntactically valid bearer tokens are revoked idempotently. Does not terminate the customer app session.",
        "responses": {
          "204": {
            "description": "Revoked; empty response, never parse as JSON"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/providers": {
      "get": {
        "operationId": "providers",
        "x-sdk-auth": "none",
        "x-sdk-role": "server",
        "description": "Deployment-wide status, not project-specific authorization. Browser clients use their backend for this API.",
        "responses": {
          "200": {
            "description": "Provider availability",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProviderStatuses"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    }
  },
  "components": {
    "examples": {
      "LoginSessionRequest": {
        "value": {
          "client_id": "YOUR_CLIENT_ID",
          "client_secret": "YOUR_CLIENT_SECRET",
          "session_key": "sssssssssssssssssssssssssssssssssssssssssss",
          "provider": "google",
          "redirect_uri": "https://your-app.example/auth/callback"
        }
      },
      "LoginSessionResponse": {
        "value": {
          "authorization_url": "https://api.anyoauth.com/auth/login/aol_lllllllllllllllllllllllllllllllllllllllllll",
          "expires_in": 600
        }
      },
      "LoginResultRequest": {
        "value": {
          "client_id": "YOUR_CLIENT_ID",
          "client_secret": "YOUR_CLIENT_SECRET",
          "session_key": "sssssssssssssssssssssssssssssssssssssssssss",
          "redirect_uri": "https://your-app.example/auth/callback"
        }
      },
      "LoginResult": {
        "value": {
          "status": "succeeded",
          "profile": {
            "subject": "usr_example",
            "provider": "google",
            "providerSubject": "provider-subject",
            "name": null,
            "username": null,
            "email": null,
            "emailVerified": false,
            "avatarUrl": null
          }
        }
      },
      "TokenRequest": {
        "value": {
          "grant_type": "authorization_code",
          "client_id": "YOUR_CLIENT_ID",
          "client_secret": "YOUR_CLIENT_SECRET",
          "code": "CALLBACK_CODE",
          "redirect_uri": "https://your-app.example/auth/callback",
          "code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
        }
      },
      "TokenResponse": {
        "value": {
          "access_token": "aop_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
          "token_type": "Bearer",
          "expires_in": 900,
          "scope": "profile"
        }
      },
      "Profile": {
        "value": {
          "subject": "usr_example",
          "provider": "google",
          "providerSubject": "provider-subject",
          "name": null,
          "username": null,
          "email": null,
          "emailVerified": false,
          "avatarUrl": null
        }
      },
      "ProviderStatuses": {
        "value": [
          {
            "id": "google",
            "configured": true,
            "enabled": true,
            "available": true
          },
          {
            "id": "github",
            "configured": true,
            "enabled": true,
            "available": true
          }
        ]
      },
      "ApiError": {
        "value": {
          "error": "request_failed",
          "message": "The request could not be completed."
        }
      }
    },
    "securitySchemes": {
      "ProfileToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "AnyOAuth opaque profile token, not an upstream provider token"
      }
    },
    "responses": {
      "Error": {
        "description": "JSON application error (400,401,404,413,429,500,503); infrastructure may return a non-JSON error",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        }
      }
    },
    "schemas": {
      "LoginSessionRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "client_id",
          "client_secret",
          "session_key",
          "provider",
          "redirect_uri"
        ],
        "properties": {
          "client_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "client_secret": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "session_key": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{43}$"
          },
          "provider": {
            "$ref": "#/components/schemas/Provider"
          },
          "redirect_uri": {
            "$ref": "#/components/schemas/RedirectUri"
          }
        }
      },
      "LoginSessionResponse": {
        "type": "object",
        "required": [
          "authorization_url",
          "expires_in"
        ],
        "properties": {
          "authorization_url": {
            "$ref": "#/components/schemas/RedirectUri"
          },
          "expires_in": {
            "type": "integer",
            "const": 600
          }
        }
      },
      "LoginResultRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "client_id",
          "client_secret",
          "session_key",
          "redirect_uri"
        ],
        "properties": {
          "client_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "client_secret": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "session_key": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{43}$"
          },
          "redirect_uri": {
            "$ref": "#/components/schemas/RedirectUri"
          }
        }
      },
      "LoginResult": {
        "type": "object",
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "succeeded",
              "failed"
            ]
          },
          "profile": {
            "$ref": "#/components/schemas/Profile"
          },
          "error": {
            "type": "string",
            "enum": [
              "access_denied",
              "invalid_provider_response",
              "provider_unavailable"
            ]
          }
        }
      },
      "Provider": {
        "type": "string",
        "enum": [
          "google",
          "github"
        ]
      },
      "RedirectUri": {
        "type": "string",
        "format": "uri",
        "maxLength": 2048,
        "x-validation": "redirect",
        "description": "Exact registered HTTPS URL or HTTP loopback; no credentials, query or fragment."
      },
      "TokenRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "grant_type",
          "client_id",
          "client_secret",
          "code",
          "redirect_uri",
          "code_verifier"
        ],
        "properties": {
          "grant_type": {
            "type": "string",
            "const": "authorization_code"
          },
          "client_id": {
            "type": "string",
            "maxLength": 100
          },
          "client_secret": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "redirect_uri": {
            "$ref": "#/components/schemas/RedirectUri"
          },
          "code_verifier": {
            "type": "string",
            "pattern": "^[A-Za-z0-9._~-]{43,128}$"
          }
        }
      },
      "TokenResponse": {
        "type": "object",
        "required": [
          "access_token",
          "token_type",
          "expires_in",
          "scope"
        ],
        "properties": {
          "access_token": {
            "type": "string",
            "pattern": "^aop_[A-Za-z0-9_-]{43}$"
          },
          "token_type": {
            "type": "string",
            "const": "Bearer"
          },
          "expires_in": {
            "type": "integer",
            "const": 900
          },
          "scope": {
            "type": "string",
            "const": "profile"
          }
        }
      },
      "Profile": {
        "type": "object",
        "required": [
          "subject",
          "provider",
          "providerSubject",
          "name",
          "username",
          "email",
          "emailVerified",
          "avatarUrl"
        ],
        "properties": {
          "subject": {
            "type": "string"
          },
          "provider": {
            "$ref": "#/components/schemas/Provider"
          },
          "providerSubject": {
            "type": "string"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "username": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "emailVerified": {
            "type": "boolean"
          },
          "avatarUrl": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "ProviderStatus": {
        "type": "object",
        "required": [
          "id",
          "configured",
          "enabled",
          "available"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/Provider"
          },
          "configured": {
            "type": "boolean"
          },
          "enabled": {
            "type": "boolean"
          },
          "available": {
            "type": "boolean"
          }
        }
      },
      "ProviderStatuses": {
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/ProviderStatus"
        }
      },
      "ApiError": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "Transaction": {
        "type": "object",
        "required": [
          "state",
          "codeVerifier",
          "codeChallenge",
          "createdAt",
          "expiresAt"
        ],
        "properties": {
          "state": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{43}$"
          },
          "codeVerifier": {
            "type": "string",
            "pattern": "^[A-Za-z0-9._~-]{43,128}$"
          },
          "codeChallenge": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{43}$"
          },
          "createdAt": {
            "type": "integer"
          },
          "expiresAt": {
            "type": "integer"
          }
        }
      }
    }
  },
  "x-sdk-helpers": {
    "createTransaction": {
      "lifetimeSeconds": 600,
      "entropyBytes": 32,
      "description": "Create random state and a PKCE verifier/challenge. Persist this transaction in the initiating browser or app session.",
      "input": {
        "type": "object",
        "properties": {},
        "additionalProperties": false
      }
    },
    "pkceChallenge": {
      "algorithm": "SHA-256",
      "encoding": "base64url",
      "description": "Compute the RFC 7636 S256 challenge for the original verifier.",
      "input": {
        "type": "object",
        "required": [
          "codeVerifier"
        ],
        "properties": {
          "codeVerifier": {
            "$ref": "#/components/schemas/TokenRequest/properties/code_verifier"
          }
        },
        "additionalProperties": false
      }
    },
    "authorizationUrl": {
      "operation": "authorizationUrl",
      "description": "Construct the URL locally and navigate the initiating browser; never include a client secret."
    },
    "validateCallback": {
      "description": "Exact saved callback, state and expiry. Reject duplicate state/code/error and simultaneous code/error. Caller owns browser binding and atomic consumption.",
      "input": {
        "type": "object",
        "required": [
          "callbackUrl",
          "redirectUri",
          "state",
          "expiresAt"
        ],
        "properties": {
          "callbackUrl": {
            "type": "string"
          },
          "redirectUri": {
            "$ref": "#/components/schemas/RedirectUri"
          },
          "state": {
            "type": "string",
            "minLength": 1
          },
          "expiresAt": {
            "type": "integer"
          }
        },
        "additionalProperties": false
      }
    }
  }
}
