{
  "openapi": "3.1.1",
  "info": {
    "title": "Axowl Public API",
    "description": "The endpoints Axowl exposes to external developers. Everything here is reachable without a dashboard session.\n\n**Authentication** — end-user sign-in for your app (passkey, social, magic link, MFA). Identified by your application key in the path; no API key.\n\n**Management API** — organizations, app groups and billing for your own org. Send your org key as `X-Api-Key: ah_live_…`. A revoked key answers 401 — treat that as an outage, never as \"this user has no organization\".\n\n**OIDC Provider / SAML** — standard protocol endpoints. They follow their own specifications and are not versioned by us; only `/api/public/v1/*` carries our stability promise.",
    "version": "v1"
  },
  "servers": [
    {
      "url": "https://testapi.axowl.com/"
    }
  ],
  "paths": {
    "/api/public/legal/business-info": {
      "get": {
        "tags": [
          "Legal"
        ],
        "summary": "Korean business address required by the E-Commerce Act §13. Returned only to KR clients (gated on CF-IPCountry, fail-closed); 204 otherwise. No authentication.",
        "operationId": "LegalBusinessInfo",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/orgs/{slug}/partner-assertion": {
      "post": {
        "tags": [
          "Partner Assertion"
        ],
        "summary": "Exchange an org API key for a short-lived RS256 partner assertion (two-way key)",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AssertionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/sms/send": {
      "post": {
        "tags": [
          "SMS"
        ],
        "summary": "Send an SMS — metered against the org's prepaid credit (402 when empty)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendSmsRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/custom-domains/resolve": {
      "get": {
        "tags": [
          "Authentication"
        ],
        "parameters": [
          {
            "name": "host",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/invitations/{code}": {
      "get": {
        "tags": [
          "Authentication"
        ],
        "summary": "Get public invitation info (no auth required)",
        "operationId": "GetInvitationPreview",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/apps/{applicationKey}/login-methods": {
      "get": {
        "tags": [
          "Authentication"
        ],
        "summary": "Get available login methods and branding for an application (no auth required)",
        "operationId": "GetLoginMethods",
        "parameters": [
          {
            "name": "applicationKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/apps/{applicationKey}/auth/social": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "parameters": [
          {
            "name": "applicationKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppSocialLoginRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/apps/{applicationKey}/auth/social/{provider}/start": {
      "get": {
        "tags": [
          "Authentication"
        ],
        "parameters": [
          {
            "name": "applicationKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "redirect_url",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "portal_origin",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/auth/social/callback": {
      "get": {
        "tags": [
          "Authentication"
        ],
        "parameters": [
          {
            "name": "code",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "state",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "error",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "tags": [
          "Authentication"
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/apps/{applicationKey}/auth/social/exchange": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "parameters": [
          {
            "name": "applicationKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppSocialExchangeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/apps/{applicationKey}/auth/session/handshake": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "parameters": [
          {
            "name": "applicationKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/apps/{applicationKey}/auth/session/exchange": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "parameters": [
          {
            "name": "applicationKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SessionExchangeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/apps/{applicationKey}/auth/magic-link/send": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Send magic link email for end-user login (no auth required)",
        "operationId": "EndUserMagicLinkSend",
        "parameters": [
          {
            "name": "applicationKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppMagicLinkSendRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/apps/{applicationKey}/auth/magic-link/verify": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Verify magic link token and issue JWT for end-user (no auth required)",
        "operationId": "EndUserMagicLinkVerify",
        "parameters": [
          {
            "name": "applicationKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppMagicLinkVerifyRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/apps/{applicationKey}/auth/mfa/enroll/send": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "EndUserMfaEnrollSend",
        "parameters": [
          {
            "name": "applicationKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MfaEnrollSendRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/apps/{applicationKey}/auth/mfa/verify": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "EndUserMfaVerify",
        "parameters": [
          {
            "name": "applicationKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MfaVerifyRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/apps/{applicationKey}/auth/passkey/register-options": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Get passkey registration options for end-user (no auth required)",
        "operationId": "EndUserPasskeyRegisterOptions",
        "parameters": [
          {
            "name": "applicationKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonObject"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/apps/{applicationKey}/auth/passkey/register": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Complete passkey registration for end-user (no auth required)",
        "operationId": "EndUserPasskeyRegister",
        "parameters": [
          {
            "name": "applicationKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonObject"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/apps/{applicationKey}/auth/passkey/login-options": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Get passkey login options for end-user (no auth required)",
        "operationId": "EndUserPasskeyLoginOptions",
        "parameters": [
          {
            "name": "applicationKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonObject"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/apps/{applicationKey}/auth/passkey/login": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Login with passkey for end-user (no auth required)",
        "operationId": "EndUserPasskeyLogin",
        "parameters": [
          {
            "name": "applicationKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonObject"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/apps/{applicationKey}/auth/federated": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Login via OIDC federation for end-user (no auth required)",
        "operationId": "EndUserFederatedLogin",
        "parameters": [
          {
            "name": "applicationKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppFederatedLoginRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/apps/{applicationKey}/auth/logout": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Revoke EndUser JWT and publish logout event (no auth required)",
        "operationId": "EndUserLogout",
        "parameters": [
          {
            "name": "applicationKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppLogoutRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/apps/{applicationKey}/identity/korean/initiate": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Start Korean identity verification (EndUser SDK, requires end-user JWT)",
        "operationId": "AppKoreanIdentityInitiate",
        "parameters": [
          {
            "name": "applicationKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppKoreanIdentityInitiateRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/apps/{applicationKey}/identity/korean/finalize": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Finalize Korean identity verification (EndUser SDK, requires end-user JWT)",
        "operationId": "AppKoreanIdentityFinalize",
        "parameters": [
          {
            "name": "applicationKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppKoreanIdentityFinalizeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/orgs/{slug}/.well-known/jwks.json": {
      "get": {
        "tags": [
          "Authentication"
        ],
        "summary": "JWKS endpoint — RS256 public key for JWT verification (no auth required)",
        "operationId": "PublicJwks",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/orgs/{slug}/.well-known/openid-configuration": {
      "get": {
        "tags": [
          "Authentication"
        ],
        "summary": "OIDC discovery document — consumers verify end-user tokens by Authority alone (no auth required)",
        "operationId": "PublicOidcDiscovery",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/community/events/subscribe": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Subscribe to community event notifications (public, no auth)",
        "operationId": "SubscribeCommunityEvents",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EventSubscribeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/apps/{applicationKey}/analytics/events": {
      "post": {
        "tags": [
          "Behavior Analytics"
        ],
        "summary": "Collect behavior analytics events (page views, dwell time, referrers) from the Axowl SDK beacon",
        "operationId": "IngestBehaviorEvents",
        "parameters": [
          {
            "name": "applicationKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/v1/me": {
      "get": {
        "tags": [
          "Management API"
        ],
        "summary": "Get my org",
        "description": "Returns the org that issued the ah_live_ API key. Auth: header X-Api-Key: ah_live_…",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/v1/app-groups": {
      "get": {
        "tags": [
          "Management API"
        ],
        "summary": "List App Groups",
        "description": "Returns the App Groups (login surfaces) owned by the org that issued the ah_live_ API key. Auth: header X-Api-Key: ah_live_…",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/v1/end-users/import": {
      "post": {
        "tags": [
          "Management API"
        ],
        "summary": "Import end users (migration)",
        "description": "Bulk-imports end users exported from another auth provider (Clerk, Kinde, Supabase, Firebase, Auth0, NextAuth …) into an App Group. Password hashes, when supplied, are stored dormant for provenance — Axowl end-user login is passwordless (magic link, social, passkey), so imported users sign in with their existing email and are prompted to register a passkey. Max 500 rows per request. Auth: header X-Api-Key: ah_live_…",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImportEndUsersRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/v1/invoices": {
      "get": {
        "tags": [
          "Management API"
        ],
        "summary": "List invoices",
        "description": "Monthly billing statements for the org that issued the ah_live_ API key, newest first. Each line carries the ledger reference it was charged under, so the amounts can be verified independently. Auth: header X-Api-Key: ah_live_…",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/v1/invoices/{id}": {
      "get": {
        "tags": [
          "Management API"
        ],
        "summary": "Get an invoice",
        "description": "One invoice with its lines and seal evidence. Auth: header X-Api-Key: ah_live_…",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/v1/invoices/{id}/verify": {
      "get": {
        "tags": [
          "Management API"
        ],
        "summary": "Verify a sealed invoice",
        "description": "Anonymous verification of a sealed statement: recomputes the statement hash from its stored lines and compares it to the sealed value. No API key required — share the invoice id with an auditor or counterparty. Available when the issuing org is on the Business plan or higher.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/v1/monetization": {
      "get": {
        "tags": [
          "Management API"
        ],
        "summary": "Get monetization policy",
        "description": "The billing policy for the org that issued the ah_live_ key: take rate on billed volume, monthly volume cap (Free only), which app groups may collect, and whether consolidated statements are enabled. Payment engines read this before charging on the org's behalf — Axowl publishes the policy; the engine executes it. Auth: header X-Api-Key: ah_live_…",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/v1/orgs/monetization": {
      "post": {
        "tags": [
          "Management API"
        ],
        "summary": "Get another org's monetization policy",
        "description": "The billing policy for the org given in the body — for payment engines that hold a brand→org binding rather than that org's own key. Rates and limits only; app groups come back as ids without names. Body: orgId. Auth: header X-Api-Key: ah_live_…",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OrgMonetizationRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/v1/orgs": {
      "post": {
        "tags": [
          "Management API"
        ],
        "summary": "Create an org",
        "description": "Creates a new organization owned by ownerEmail (user auto-provisioned if new) and returns a fresh ah_live_ key for it. Body: ownerEmail, name, slug, type (individual|company, default individual), countryIso. Auth: header X-Api-Key: ah_live_…",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateOrgRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/v1/orgs/match": {
      "post": {
        "tags": [
          "Management API"
        ],
        "summary": "Match an org by owner",
        "description": "Finds the org owned by ownerEmail whose name equals orgName (case-insensitive). Body: ownerEmail, orgName. Auth: header X-Api-Key: ah_live_…",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MatchOrgRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/v1/orgs/app-groups": {
      "post": {
        "tags": [
          "Management API"
        ],
        "summary": "List an org's app groups (brands)",
        "description": "Every App Group of the org owned by ownerEmail whose name equals orgName — the org's brands, one product/service line each. Empty list is a normal answer (an org may not use App Groups yet). Body: ownerEmail, orgName. Auth: header X-Api-Key: ah_live_…",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MatchOrgRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/v1/app-groups/by-id": {
      "post": {
        "tags": [
          "Management API"
        ],
        "summary": "Read an app group (brand) by id",
        "description": "Returns {id, orgId, name, description, iconUrl, brandColor} for an App Group id you already hold. An App Group is the brand — read its name and branding from here rather than storing a copy that drifts when the customer edits it on Axowl. Body: appGroupId. Auth: header X-Api-Key: ah_live_… (platform partners only). 404 = no such app group (it may have been deleted).",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppGroupByIdRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/v1/orgs/by-owner": {
      "post": {
        "tags": [
          "Management API"
        ],
        "summary": "List my orgs",
        "description": "Returns all orgs owned by ownerEmail. Body: ownerEmail. Auth: header X-Api-Key: ah_live_…",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ByOwnerRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/v1/orgs/by-id": {
      "post": {
        "tags": [
          "Management API"
        ],
        "summary": "Read an org by id",
        "description": "Returns {id, name, slug, ready} for an org id you already hold. Use this to keep a stored org name current: the name can be changed on Axowl at any time, and every other lookup here is keyed by the name itself. Body: orgId. Auth: header X-Api-Key: ah_live_… (platform partners only). 404 = no such org.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ByIdRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/v1/orgs/owner-status": {
      "post": {
        "tags": [
          "Management API"
        ],
        "summary": "Owner-gate status for the signed-in end user",
        "description": "Says whether the end user holding the Bearer token has walked through the account-owner gate yet. State: no_owner_account (send to owner sign-up) | owner_account_exists (send to owner sign-in with email as login_hint). Auth: header X-Api-Key: ah_live_… AND Authorization: Bearer <end-user JWT>.",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/v1/files": {
      "get": {
        "tags": [
          "Management API"
        ],
        "summary": "List files",
        "description": "Lists files in the org's file box. Query: trashed (default false), q (name contains), page, pageSize (max 200). Trashed files do not count toward the storage quota. Auth: header X-Api-Key: ah_live_… (org-wide key; App-Group-scoped keys get 401).",
        "parameters": [
          {
            "name": "trashed",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32"
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": [
                "integer",
                "string"
              ],
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "tags": [
          "Management API"
        ],
        "summary": "Upload a file",
        "description": "Uploads one file (multipart/form-data, part name 'file', max 10MB) into the org's file box. The server computes sha256 itself — the returned digest is ours, not yours. 403 storage_quota_exceeded when the plan limit would be exceeded (body carries usedBytes/limitBytes). Auth: header X-Api-Key: ah_live_… (org-wide key).",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/v1/files/{id}/download": {
      "get": {
        "tags": [
          "Management API"
        ],
        "summary": "Get a download link",
        "description": "Issues a short-lived presigned GET URL (10 minutes) for one file. The URL always responds with Content-Disposition: attachment — it is never rendered inline. Anyone holding the URL can fetch it until it expires, so treat it as a bearer credential and do not log it. Issuing is recorded in the audit trail. Auth: header X-Api-Key: ah_live_… (org-wide key).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/v1/files/{id}": {
      "delete": {
        "tags": [
          "Management API"
        ],
        "summary": "Move a file to trash",
        "description": "Soft-deletes one file. The object is NOT removed from storage yet — it stays restorable for the plan's retention window (Free 7 days, paid 30) and stops counting toward the quota. Permanent removal happens after that window. Auth: header X-Api-Key: ah_live_… (org-wide key).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/v1/files/{id}/restore": {
      "post": {
        "tags": [
          "Management API"
        ],
        "summary": "Restore a file from trash",
        "description": "Brings a trashed file back. Because restoring adds its bytes back to the quota, this can return 403 storage_quota_exceeded even though deleting always succeeds. Auth: header X-Api-Key: ah_live_… (org-wide key).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/v1/storage/usage": {
      "get": {
        "tags": [
          "Management API"
        ],
        "summary": "Get storage usage",
        "description": "Returns the org's file-box usage against its plan limit. limitBytes is null on unlimited plans. Trashed bytes are reported separately and are NOT included in usedBytes. This is the same number the upload gate enforces. Auth: header X-Api-Key: ah_live_… (org-wide key).",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/orgs/{slug}/authorize": {
      "get": {
        "tags": [
          "OIDC Provider"
        ],
        "summary": "OIDC authorization endpoint (Authorization Code + PKCE) — bridges to hosted login. No auth.",
        "operationId": "OidcAuthorize",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/orgs/{slug}/authorize/continue": {
      "get": {
        "tags": [
          "OIDC Provider"
        ],
        "summary": "Internal OIDC bridge — turns a completed portal login into an authorization code. No auth.",
        "operationId": "OidcAuthorizeContinue",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/orgs/{slug}/token": {
      "post": {
        "tags": [
          "OIDC Provider"
        ],
        "summary": "OIDC token endpoint — authorization_code (PKCE) + refresh_token. No auth (PKCE/refresh authenticates).",
        "operationId": "OidcToken",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/orgs/{slug}/userinfo": {
      "get": {
        "tags": [
          "OIDC Provider"
        ],
        "summary": "OIDC UserInfo endpoint — identity claims for a valid Bearer access_token.",
        "operationId": "OidcUserInfo",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/orgs/{slug}/end-session": {
      "get": {
        "tags": [
          "OIDC Provider"
        ],
        "summary": "OIDC RP-initiated logout — validates post_logout_redirect_uri and redirects.",
        "operationId": "OidcEndSession",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/orgs/{slug}/auth/saml/login": {
      "get": {
        "tags": [
          "SAML Login"
        ],
        "summary": "SAML SP-initiated SSO start — builds an AuthnRequest and redirects to the org IdP. No auth.",
        "operationId": "SamlLogin",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/orgs/{slug}/auth/saml/metadata": {
      "get": {
        "tags": [
          "SAML Login"
        ],
        "summary": "SAML SP metadata — register this at the enterprise IdP. No auth.",
        "operationId": "SamlMetadata",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/orgs/{slug}/auth/saml/acs": {
      "post": {
        "tags": [
          "SAML Login"
        ],
        "summary": "SAML Assertion Consumer Service — validates the IdP SAMLResponse signature, then JIT + session. No auth (signature authenticates).",
        "operationId": "SamlAcs",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/orgs/{slug}/saml/idp/metadata": {
      "get": {
        "tags": [
          "SAML Identity Provider"
        ],
        "summary": "SAML IdP metadata — register Axowl as your IdP at the relying SP. No auth.",
        "operationId": "SamlIdpMetadata",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/orgs/{slug}/saml/idp/sso": {
      "get": {
        "tags": [
          "SAML Identity Provider"
        ],
        "summary": "SAML IdP SSO (HTTP-Redirect) — parse AuthnRequest, bridge to login. No auth.",
        "operationId": "SamlIdpSsoRedirect",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "tags": [
          "SAML Identity Provider"
        ],
        "summary": "SAML IdP SSO (HTTP-POST) — parse AuthnRequest, bridge to login. No auth.",
        "operationId": "SamlIdpSsoPost",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/api/public/orgs/{slug}/saml/idp/sso/continue": {
      "get": {
        "tags": [
          "SAML Identity Provider"
        ],
        "summary": "Internal SAML IdP bridge — turns a completed portal login into a signed SAML Response. No auth.",
        "operationId": "SamlIdpSsoContinue",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "AppFederatedLoginRequest": {
        "required": [
          "provider",
          "idToken"
        ],
        "type": "object",
        "properties": {
          "provider": {
            "type": "string"
          },
          "idToken": {
            "type": "string"
          }
        }
      },
      "AppGroupByIdRequest": {
        "required": [
          "appGroupId"
        ],
        "type": "object",
        "properties": {
          "appGroupId": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "AppKoreanIdentityFinalizeRequest": {
        "required": [
          "identityVerificationId"
        ],
        "type": "object",
        "properties": {
          "identityVerificationId": {
            "type": "string"
          },
          "purpose": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "AppKoreanIdentityInitiateRequest": {
        "type": "object",
        "properties": {
          "purpose": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "AppLogoutRequest": {
        "required": [
          "accessToken"
        ],
        "type": "object",
        "properties": {
          "accessToken": {
            "type": "string"
          }
        }
      },
      "AppMagicLinkSendRequest": {
        "required": [
          "email"
        ],
        "type": "object",
        "properties": {
          "email": {
            "type": "string"
          },
          "redirectUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "turnstileToken": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "AppMagicLinkVerifyRequest": {
        "required": [
          "token"
        ],
        "type": "object",
        "properties": {
          "token": {
            "type": "string"
          }
        }
      },
      "AppSocialExchangeRequest": {
        "required": [
          "code"
        ],
        "type": "object",
        "properties": {
          "code": {
            "type": "string"
          }
        }
      },
      "AppSocialLoginRequest": {
        "required": [
          "provider",
          "token"
        ],
        "type": "object",
        "properties": {
          "provider": {
            "type": "string"
          },
          "token": {
            "type": "string"
          },
          "ipAddress": {
            "type": [
              "null",
              "string"
            ]
          },
          "turnstileToken": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "AssertionRequest": {
        "required": [
          "apiKey",
          "audience"
        ],
        "type": "object",
        "properties": {
          "apiKey": {
            "type": "string"
          },
          "audience": {
            "type": "string"
          }
        }
      },
      "ByIdRequest": {
        "required": [
          "orgId"
        ],
        "type": "object",
        "properties": {
          "orgId": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "ByOwnerRequest": {
        "required": [
          "ownerEmail"
        ],
        "type": "object",
        "properties": {
          "ownerEmail": {
            "type": "string"
          }
        }
      },
      "CreateOrgRequest": {
        "required": [
          "ownerEmail",
          "name",
          "slug",
          "type",
          "countryIso",
          "taxId"
        ],
        "type": "object",
        "properties": {
          "ownerEmail": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "type": {
            "type": [
              "null",
              "string"
            ]
          },
          "countryIso": {
            "type": "string"
          },
          "taxId": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "EventSubscribeRequest": {
        "required": [
          "email"
        ],
        "type": "object",
        "properties": {
          "email": {
            "type": "string"
          },
          "turnstileToken": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ImportEndUserRowDto": {
        "required": [
          "email",
          "displayName",
          "sourceUserId",
          "passwordHashAlgorithm",
          "passwordHash",
          "passwordHashParams"
        ],
        "type": "object",
        "properties": {
          "email": {
            "type": "string"
          },
          "displayName": {
            "type": [
              "null",
              "string"
            ]
          },
          "sourceUserId": {
            "type": [
              "null",
              "string"
            ]
          },
          "passwordHashAlgorithm": {
            "type": [
              "null",
              "string"
            ]
          },
          "passwordHash": {
            "type": [
              "null",
              "string"
            ]
          },
          "passwordHashParams": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ImportEndUsersRequest": {
        "required": [
          "appGroupId",
          "source",
          "sendInvites",
          "users"
        ],
        "type": "object",
        "properties": {
          "appGroupId": {
            "type": "string",
            "format": "uuid"
          },
          "source": {
            "type": "string"
          },
          "sendInvites": {
            "type": "boolean"
          },
          "users": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/ImportEndUserRowDto"
            }
          }
        }
      },
      "JsonObject": {
        "type": "object"
      },
      "MatchOrgRequest": {
        "required": [
          "ownerEmail",
          "orgName"
        ],
        "type": "object",
        "properties": {
          "ownerEmail": {
            "type": "string"
          },
          "orgName": {
            "type": "string"
          }
        }
      },
      "MfaEnrollSendRequest": {
        "required": [
          "mfaToken",
          "phone"
        ],
        "type": "object",
        "properties": {
          "mfaToken": {
            "type": "string"
          },
          "phone": {
            "type": "string"
          }
        }
      },
      "MfaVerifyRequest": {
        "required": [
          "mfaToken",
          "code"
        ],
        "type": "object",
        "properties": {
          "mfaToken": {
            "type": "string"
          },
          "code": {
            "type": "string"
          }
        }
      },
      "OrgMonetizationRequest": {
        "required": [
          "orgId"
        ],
        "type": "object",
        "properties": {
          "orgId": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "SendSmsRequest": {
        "required": [
          "apiKey",
          "to",
          "body"
        ],
        "type": "object",
        "properties": {
          "apiKey": {
            "type": "string"
          },
          "to": {
            "type": "string"
          },
          "body": {
            "type": "string"
          }
        }
      },
      "SessionExchangeRequest": {
        "required": [
          "code"
        ],
        "type": "object",
        "properties": {
          "code": {
            "type": "string"
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "Legal"
    },
    {
      "name": "Partner Assertion"
    },
    {
      "name": "SMS"
    },
    {
      "name": "Authentication"
    },
    {
      "name": "Behavior Analytics"
    },
    {
      "name": "Management API"
    },
    {
      "name": "OIDC Provider"
    },
    {
      "name": "SAML Login"
    },
    {
      "name": "SAML Identity Provider"
    }
  ]
}