{
  "service": "Obelisk",
  "tagline": "Full-circle security for the agent era — a unified identity & security plane. Security you can prove, not just promise.",
  "description": "Passkey-first identity, one OIDC provider, tamper-evident receipts, a live security Rating, organizations, and a per-project profile layer. One identity across the whole ecosystem.",
  "version": "obelisk-api-1",
  "baseUrl": "https://obeliskgate.com",
  "forAgents": {
    "start_here": "https://obeliskgate.com/api",
    "guide": "https://obeliskgate.com/agents",
    "integrate": "https://obeliskgate.com/developers",
    "works_with_your_database": "https://obeliskgate.com/developers#database",
    "agent_interface": "https://obeliskgate.com/agent",
    "agent_onboarding": "https://obeliskgate.com/api#agent-identity",
    "agent_proof": "https://obeliskgate.com/agent/<proof_id>",
    "principles": [
      "passkey-first / WebAuthn",
      "no project-side secrets",
      "tamper-evident receipts",
      "sender-bound tokens (DPoP, RFC 9449) opt-in",
      "post-quantum migration-ready"
    ],
    "auth_vocabulary": {
      "none": "public",
      "session": "obelisk_session cookie OR body.token",
      "bearer": "OIDC access token (Authorization: Bearer)",
      "owner": "session + org owner/admin",
      "ack-secret": "x-obelisk-ecosystem-ack header for Studio ecosystem acknowledgements"
    },
    "token_binding": "DPoP (RFC 9449), opt-in: send a DPoP proof header at /auth/token and the access token binds to your key (cnf.jkt); protected calls then require token + fresh proof.",
    "content_negotiation": "Selected account, organization, API-manifest, and changelog surfaces return JSON with ?format=json or Accept: application/json; static editorial pages remain HTML.",
    "error_shape": "{ ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
  },
  "discovery": {
    "openid_configuration": "https://obeliskgate.com/.well-known/openid-configuration",
    "oauth_protected_resource": "https://obeliskgate.com/.well-known/oauth-protected-resource",
    "jwks": "https://obeliskgate.com/.well-known/jwks.json",
    "manifest": "https://obeliskgate.com/api",
    "openapi": "https://obeliskgate.com/openapi.json",
    "agent_interface": "https://obeliskgate.com/.well-known/obelisk-agent",
    "saml_idp_metadata": "https://obeliskgate.com/saml/idp/metadata",
    "health": "https://obeliskgate.com/healthz",
    "transparency": "https://obeliskgate.com/.well-known/obelisk-transparency.json",
    "security_txt": "https://obeliskgate.com/.well-known/security.txt",
    "agents_json": "https://obeliskgate.com/agents.json",
    "llms_txt": "https://obeliskgate.com/.well-known/llms.txt",
    "changelog": "https://obeliskgate.com/changelog?format=json",
    "studio_ecosystem": "https://obeliskgate.com/studio/ecosystem/manifest.json"
  },
  "pages": {
    "developers": "https://obeliskgate.com/developers",
    "integrate_with_your_db": "https://obeliskgate.com/developers#database",
    "pricing": "https://obeliskgate.com/pricing",
    "help": "https://obeliskgate.com/help",
    "faq": "https://obeliskgate.com/help/faq",
    "security": "https://obeliskgate.com/security",
    "about": "https://obeliskgate.com/about",
    "changelog": "https://obeliskgate.com/changelog",
    "mcp": "https://obeliskgate.com/mcp",
    "organizations": "https://obeliskgate.com/organizations"
  },
  "contracts": {
    "project_profile": "https://obeliskgate.com/developers#database"
  },
  "surfaces": {
    "release_history": {
      "title": "Release history",
      "summary": "Public-safe product history for humans and agents, bound to the revision serving it when deploy metadata is available.",
      "endpoints": [
        {
          "method": "GET",
          "path": "/changelog",
          "auth": "none",
          "summary": "Human release history (HTML; versioned JSON with ?format=json or Accept: application/json)."
        }
      ]
    },
    "auth": {
      "title": "Authentication & OIDC",
      "summary": "Passkey-first sign-in + OIDC.",
      "endpoints": [
        {
          "method": "GET",
          "path": "/auth",
          "auth": "none",
          "summary": "The immersive sign-in/sign-up experience (HTML)."
        },
        {
          "method": "GET",
          "path": "/auth/assertion-options",
          "auth": "none",
          "summary": "Begin a (usernameless) passkey sign-in.",
          "query": {
            "user": "optional account name"
          }
        },
        {
          "method": "POST",
          "path": "/auth/assertion-verify",
          "auth": "none",
          "summary": "Complete a passkey sign-in; returns the session + where to go (home).",
          "body": {
            "user": "optional",
            "response": "WebAuthn assertion",
            "deviceId": "optional"
          }
        },
        {
          "method": "POST",
          "path": "/auth/verify-session",
          "auth": "none",
          "summary": "Server-to-server: validate a session token; returns { ok, identityId }.",
          "body": {
            "token": "macaroon"
          }
        },
        {
          "method": "GET",
          "path": "/auth/userinfo",
          "auth": "bearer",
          "summary": "OIDC userinfo for an access token."
        },
        {
          "method": "POST",
          "path": "/auth/magic/request",
          "auth": "none",
          "summary": "Request an email magic-link (where enabled).",
          "body": {
            "email": "string",
            "dryRun": "session-gated: return the link for the session's OWN verified email (founder at sensitive assurance: any email, receipted), don't send"
          }
        },
        {
          "method": "GET",
          "path": "/auth/magic",
          "auth": "none",
          "summary": "W272 — the magic-link landing: consumes the token on the requesting device, then hands off (back to the relying party when one is waiting).",
          "query": {
            "token": "string",
            "return": "validated relying-party return",
            "project": "display name"
          }
        },
        {
          "method": "GET",
          "path": "/auth/authorize",
          "auth": "none",
          "summary": "OIDC authorization endpoint (PKCE S256). W272: response_mode=web_message posts the code to the opener page from a popup; prompt=none|login; a live session fast-paths to one tap.",
          "query": {
            "client_id": "registered client",
            "redirect_uri": "exact registered callback",
            "response_mode": "query | web_message",
            "prompt": "none | login"
          }
        },
        {
          "method": "POST",
          "path": "/auth/authorize/direct",
          "auth": "none",
          "summary": "W272 — the direct grant: a passkey assertion (or fresh registration) performed ON a registered relying party's page (WebAuthn related origins) becomes a one-time authorization code. CORS-scoped to the origin registered for client_id; PKCE S256; auth-throttled.",
          "body": {
            "client_id": "string",
            "redirect_uri": "string",
            "code_challenge": "S256",
            "code_challenge_method": "S256",
            "state": "string",
            "nonce": "optional",
            "assertion": "{ user?, response }",
            "registration": "{ user, response, signupToken? } — account creation in place"
          }
        }
      ]
    },
    "fedcm": {
      "title": "FedCM (browser-mediated sign-in)",
      "summary": "W272 — Federated Credential Management: the browser shows the Obelisk account chooser inline on a registered relying party's page and hands it a signed ID token. No redirect, no popup, third-party-cookie-proof. Discovery anchor: /.well-known/web-identity.",
      "endpoints": [
        {
          "method": "GET",
          "path": "/fedcm/config.json",
          "auth": "none",
          "summary": "Provider configuration (endpoints, branding, login_url)."
        },
        {
          "method": "GET",
          "path": "/fedcm/accounts",
          "auth": "session",
          "summary": "The signed-in person's account(s); requires Sec-Fetch-Dest: webidentity (sent only by the browser's FedCM machinery)."
        },
        {
          "method": "GET",
          "path": "/fedcm/client-metadata",
          "auth": "none",
          "summary": "Privacy/terms for a registered client; the request Origin must be registered to it.",
          "query": {
            "client_id": "string"
          }
        },
        {
          "method": "POST",
          "path": "/fedcm/assertion",
          "auth": "session",
          "summary": "Mint an ES256 ID token (aud = client_id, nonce-bound, truthful obelisk.assurance) for ONE registered client whose registered origin equals the request Origin.",
          "body": {
            "account_id": "the session's identity",
            "client_id": "string",
            "nonce": "string"
          }
        },
        {
          "method": "POST",
          "path": "/fedcm/disconnect",
          "auth": "session",
          "summary": "The person asked the browser to forget the link; acknowledged and receipted.",
          "body": {
            "client_id": "string",
            "account_hint": "string"
          }
        }
      ]
    },
    "agent_identity": {
      "title": "Agent identity & proof",
      "summary": "Register, key-bind, verify, publish, suspend, recover, and revoke AI agents, bots, automations, devices, and agent teams without claiming that behavior proves non-humanness.",
      "endpoints": [
        {
          "method": "GET",
          "path": "/account/agents",
          "auth": "session",
          "summary": "Agent Studio (HTML): the owner's whole agent fleet — create, import, pair, and manage."
        },
        {
          "method": "GET",
          "path": "/account/agents/:agentId",
          "auth": "session",
          "summary": "One agent's deep-linkable profile page (HTML): declared runtime facts, proof keys, evidence, delegation, and the full management card. Unknown or foreign ids 404 identically."
        },
        {
          "method": "POST",
          "path": "/auth/agents/proof/enroll/begin",
          "auth": "none",
          "summary": "Begin device-style enrollment with an agent-generated public JWK. Returns a short owner code; private key material is refused.",
          "body": {
            "name": "string",
            "subjectKind": "service_bot|automation|ai_agent|hybrid_agent|robot|device|agent_team|ephemeral_subagent",
            "publicJwk": "P-256/Ed25519 public JWK",
            "visibility": "private|unlisted|public"
          }
        },
        {
          "method": "POST",
          "path": "/auth/agents/proof/enroll/approve",
          "auth": "session",
          "summary": "Passkey-authorized accountable party approves the pending agent; identity remains pending until the agent proves its key.",
          "body": {
            "enrollment_id": "aen_..."
          }
        },
        {
          "method": "POST",
          "path": "/auth/agents/proof/enroll/complete",
          "auth": "none",
          "summary": "Agent completes enrollment with a nonce-bound ES256 DPoP header. Returns its opaque Proof Link.",
          "body": {
            "enrollment_id": "aen_..."
          },
          "headers": {
            "DPoP": "proof over POST + exact htu + enrollment nonce"
          }
        },
        {
          "method": "POST",
          "path": "/auth/agents/proof/enroll/status",
          "auth": "none",
          "summary": "Poll bounded enrollment state without learning owner details.",
          "body": {
            "enrollment_id": "aen_..."
          }
        },
        {
          "method": "GET",
          "path": "/agent/:proof_id",
          "auth": "none",
          "summary": "Human-readable opt-in Proof Link. Private profiles 404; a URL is never a credential."
        },
        {
          "method": "GET",
          "path": "/agent/:proof_id.json",
          "auth": "none",
          "summary": "Machine proof vector plus W3C VC and A2A projections. Claims remain independent and freshness-scoped."
        },
        {
          "method": "POST",
          "path": "/api/agent-proof/challenge",
          "auth": "none",
          "summary": "Request a short-lived nonce for a visible active agent.",
          "body": {
            "proof_id": "opaque id"
          }
        },
        {
          "method": "POST",
          "path": "/api/agent-proof/verify",
          "auth": "none",
          "summary": "Verify current agent-key control with a one-use challenge and DPoP; returns a short-lived signed proof token when signing is available.",
          "body": {
            "proof_id": "opaque id",
            "challenge_id": "apc_...",
            "audience": "optional verifier audience"
          },
          "headers": {
            "DPoP": "nonce-bound ES256 proof"
          }
        },
        {
          "method": "POST",
          "path": "/api/agent-proof/run",
          "auth": "none",
          "summary": "Bind a client-built, commitment-only run proof to the active agent key and one-use nonce; returns a short-lived Obelisk-signed run token. Raw task/input/output stay client-side.",
          "body": {
            "proof_id": "opaque id",
            "challenge_id": "purpose:run challenge",
            "run_proof": "obelisk-agent-run-proof-v1",
            "audience": "optional verifier audience"
          },
          "headers": {
            "DPoP": "nonce-bound ES256 proof"
          }
        },
        {
          "method": "POST",
          "path": "/auth/agents/proof/visibility",
          "auth": "session",
          "summary": "Set profile privacy: private, unlisted, or public. Requires passkey-grade step-up."
        },
        {
          "method": "POST",
          "path": "/auth/agents/proof/lifecycle",
          "auth": "session",
          "summary": "Suspend, resume, mark compromised, or revoke. Compromise/revoke cascades to agent-bound API keys."
        },
        {
          "method": "POST",
          "path": "/auth/agents/proof/key-rotate",
          "auth": "session",
          "summary": "Rotate a public key. Normal rotation requires proof of the current key; compromise recovery rotates first and resumes only from suspended state."
        },
        {
          "method": "POST",
          "path": "/auth/agents/proof/transfer/begin",
          "auth": "session",
          "summary": "Source owner suspends an agent and creates a short transfer code for one named target owner."
        },
        {
          "method": "POST",
          "path": "/auth/agents/proof/transfer/accept",
          "auth": "session",
          "summary": "Named target owner accepts with a separate passkey and fresh public JWK. The identity stays suspended until target passkey + new agent key co-sign resume."
        },
        {
          "method": "POST",
          "path": "/auth/agents/proof/dns-challenge",
          "auth": "session",
          "summary": "Get the stable _obelisk-agent TXT record that proves domain control for this agent. Publish it, then dns-verify.",
          "body": {
            "agent_id": "agt_...",
            "domain": "example.com"
          }
        },
        {
          "method": "POST",
          "path": "/auth/agents/proof/dns-verify",
          "auth": "session",
          "summary": "Resolve the TXT record and, on match, append domain-control evidence (channel_control leaves 'declared').",
          "body": {
            "agent_id": "agt_...",
            "domain": "example.com"
          }
        },
        {
          "method": "POST",
          "path": "/auth/agents/proof/a2a-discover",
          "auth": "session",
          "summary": "Fetch an A2A agent card (SSRF-guarded, 128KB cap) and append descriptor-integrity evidence; fills declared a2aCardUrl/capabilities only where empty.",
          "body": {
            "agent_id": "agt_...",
            "url": "https card URL"
          }
        },
        {
          "method": "GET",
          "path": "/api/agent-trust/bundle",
          "auth": "none",
          "summary": "This authority's claim registry as a signed trust bundle (JWS via jwks_uri) for federation peers to pin and import."
        }
      ],
      "claim_model": "Identity record + expiring posture + action-scoped run proof. No scalar ladder; no categorical 'not human', 'not exploited', or 'safe' claim."
    },
    "account": {
      "title": "Account",
      "summary": "The universal Obelisk account — one identity across every app.",
      "endpoints": [
        {
          "method": "GET",
          "path": "/account",
          "auth": "session",
          "summary": "Your account page (HTML; JSON with ?format=json → the full profile)."
        },
        {
          "method": "GET",
          "path": "/account/export",
          "auth": "session",
          "summary": "Download a portable, secret-free copy of your account (JSON)."
        },
        {
          "method": "POST",
          "path": "/auth/profile",
          "auth": "session",
          "summary": "Set your display name.",
          "body": {
            "displayName": "string"
          }
        },
        {
          "method": "POST",
          "path": "/auth/account/rename",
          "auth": "session",
          "summary": "Change your username (old name stays a resolvable alias).",
          "body": {
            "newUsername": "string"
          }
        },
        {
          "method": "POST",
          "path": "/auth/preferences",
          "auth": "session",
          "summary": "Merge your preferences (theme/lang/contrast/motion/text/email).",
          "body": {
            "preferences": "object"
          }
        },
        {
          "method": "POST",
          "path": "/auth/passkeys/list",
          "auth": "session",
          "summary": "List your passkeys (no public keys)."
        },
        {
          "method": "POST",
          "path": "/auth/passkeys/rename",
          "auth": "session",
          "summary": "Rename a passkey.",
          "body": {
            "credId": "string",
            "label": "string"
          }
        },
        {
          "method": "POST",
          "path": "/auth/passkeys/remove",
          "auth": "session",
          "summary": "Remove a passkey (never the last one).",
          "body": {
            "credId": "string"
          }
        },
        {
          "method": "POST",
          "path": "/auth/passkeys/add-options",
          "auth": "session",
          "summary": "Begin adding a passkey to your account."
        },
        {
          "method": "POST",
          "path": "/auth/passkeys/add-verify",
          "auth": "session",
          "summary": "Finish adding a passkey.",
          "body": {
            "response": "WebAuthn attestation"
          }
        },
        {
          "method": "POST",
          "path": "/auth/totp/setup",
          "auth": "session",
          "summary": "Begin TOTP setup (pending) → { secret, uri, qrSvg }."
        },
        {
          "method": "POST",
          "path": "/auth/totp/confirm",
          "auth": "session",
          "summary": "Confirm + activate TOTP with a code.",
          "body": {
            "code": "6 digits"
          }
        },
        {
          "method": "POST",
          "path": "/auth/totp/disable",
          "auth": "session",
          "summary": "Turn off TOTP."
        },
        {
          "method": "POST",
          "path": "/auth/recovery/regenerate",
          "auth": "session",
          "summary": "Generate new backup codes (shown once)."
        },
        {
          "method": "POST",
          "path": "/auth/sessions/revoke",
          "auth": "session",
          "summary": "Revoke a session.",
          "body": {
            "identifier": "string"
          }
        },
        {
          "method": "POST",
          "path": "/auth/devices/revoke",
          "auth": "session",
          "summary": "Sign out a whole device — revokes every session it holds, then drops it from the device ledger.",
          "body": {
            "deviceId": "string"
          }
        },
        {
          "method": "POST",
          "path": "/auth/signout",
          "auth": "session",
          "summary": "Clear the session cookie."
        }
      ]
    },
    "organizations": {
      "title": "Organizations",
      "summary": "Register a business/website for its own Obelisk Rating; manage a team.",
      "endpoints": [
        {
          "method": "POST",
          "path": "/auth/orgs/create",
          "auth": "session",
          "summary": "Create an organization (you become owner).",
          "body": {
            "name": "string",
            "website": "optional url"
          }
        },
        {
          "method": "GET",
          "path": "/org/:slug",
          "auth": "session",
          "summary": "Org dashboard (HTML; JSON with ?format=json), member-gated."
        },
        {
          "method": "POST",
          "path": "/org/:slug/scan",
          "auth": "owner",
          "summary": "Run an external security-posture scan → an Obelisk Rating. Scan a specific site with { siteId } or the primary by default.",
          "body": {
            "website": "optional override",
            "siteId": "optional registered-site id"
          }
        },
        {
          "method": "POST",
          "path": "/org/:slug/websites/add",
          "auth": "owner",
          "summary": "Add a website this org owns (first becomes primary).",
          "body": {
            "url": "https url",
            "label": "optional"
          }
        },
        {
          "method": "POST",
          "path": "/org/:slug/websites/remove",
          "auth": "owner",
          "summary": "Remove a registered site by id.",
          "body": {
            "siteId": "string"
          }
        },
        {
          "method": "POST",
          "path": "/org/:slug/websites/primary",
          "auth": "owner",
          "summary": "Promote a site to primary (the org headline rating tracks it).",
          "body": {
            "siteId": "string"
          }
        },
        {
          "method": "POST",
          "path": "/org/:slug/members/role",
          "auth": "owner",
          "summary": "Change a member's role. Owner-only for owner changes; the last owner can't be demoted.",
          "body": {
            "user": "identityId",
            "role": "member|admin|owner"
          }
        },
        {
          "method": "POST",
          "path": "/org/:slug/members/remove",
          "auth": "owner",
          "summary": "Remove a member. The last owner can never be removed.",
          "body": {
            "user": "identityId"
          }
        },
        {
          "method": "POST",
          "path": "/org/:slug/invite",
          "auth": "owner",
          "summary": "Create a role-scoped invite link.",
          "body": {
            "role": "member|admin",
            "email": "optional"
          }
        },
        {
          "method": "POST",
          "path": "/org/:slug/invite/revoke",
          "auth": "owner",
          "summary": "Revoke an invite.",
          "body": {
            "code": "string"
          }
        },
        {
          "method": "POST",
          "path": "/org/:slug/settings",
          "auth": "owner",
          "summary": "Rename the organization and/or set its description. The @slug handle is permanent; member profiles update automatically.",
          "body": {
            "name": "string ≤80",
            "description": "optional string ≤280"
          }
        },
        {
          "method": "POST",
          "path": "/org/:slug/leave",
          "auth": "session",
          "summary": "Leave the organization (self-service). The last owner must transfer ownership first; your agents scoped to it become personal."
        },
        {
          "method": "POST",
          "path": "/org/:slug/transfer-ownership",
          "auth": "owner",
          "summary": "Hand ownership to an existing member (passkey step-up required). You stay on as admin.",
          "body": {
            "user": "identityId"
          }
        },
        {
          "method": "GET",
          "path": "/join/:code",
          "auth": "none",
          "summary": "Accept an invite (signs you in first if needed)."
        }
      ]
    },
    "app_registration": {
      "title": "App registration",
      "summary": "Self-serve OAuth/OIDC client registration — the developer portal (/account/apps) + its API.",
      "endpoints": [
        {
          "method": "GET",
          "path": "/account/apps",
          "auth": "session",
          "summary": "The hosted app-registration portal (HTML): register apps, list, copy config, revoke."
        },
        {
          "method": "POST",
          "path": "/auth/register",
          "auth": "session",
          "summary": "RFC 7591 dynamic client registration → { client_id, ... }. Optional { org } scopes the app to an organization you administer (else your personal tenant). Requires OBELISK_DCR=open + tenant enforcement.",
          "body": {
            "client_name": "string",
            "redirect_uris": "https[]",
            "obelisk_project_trust_profile": "object",
            "resource_uris": "https[]",
            "org": "optional org slug"
          }
        },
        {
          "method": "POST",
          "path": "/auth/apps/list",
          "auth": "session",
          "summary": "List apps in tenants you control (personal + admin orgs). Never returns a secret."
        },
        {
          "method": "POST",
          "path": "/auth/apps/revoke",
          "auth": "session",
          "summary": "Revoke an app you own → status:revoked.",
          "body": {
            "clientId": "string"
          }
        }
      ]
    },
    "project_profile": {
      "title": "Per-project profile",
      "summary": "The on-top layer: each project's own per-user profile slice, scoped to its OIDC client.",
      "endpoints": [
        {
          "method": "GET",
          "path": "/api/project-profile",
          "auth": "bearer",
          "summary": "Read this project's profile slice for the token's user → { sub, project, profile }."
        },
        {
          "method": "POST",
          "path": "/api/project-profile",
          "auth": "bearer",
          "summary": "Shallow-merge into this project's slice.",
          "body": {
            "profile": "object"
          }
        }
      ]
    },
    "studio_ecosystem": {
      "title": "Studio ecosystem",
      "summary": "Studio-wide Obelisk rollout truth: declared, observed, required, migration cargo, and receipt-backed acknowledgement state.",
      "endpoints": [
        {
          "method": "GET",
          "path": "/studio/ecosystem/manifest.json",
          "auth": "none",
          "summary": "Fleet manifest for Studio Ops: every registered project, required Obelisk versions, observed status, and migration state."
        },
        {
          "method": "GET",
          "path": "/studio/ecosystem/projects/:slug",
          "auth": "none",
          "summary": "One project's ecosystem profile row from the canonical manifest."
        },
        {
          "method": "GET",
          "path": "/studio/ecosystem/projects/:slug/migration",
          "auth": "none",
          "summary": "Content-addressed recipient-owned Ark migration cargo for one project."
        },
        {
          "method": "POST",
          "path": "/studio/ecosystem/projects/:slug/ack",
          "auth": "ack-secret",
          "summary": "Record a recipient-owned migration acknowledgement. Requires x-obelisk-ecosystem-ack; writes an ecosystem-ack receipt.",
          "body": {
            "result": "applied|already-current|rejected|blocked",
            "cargoDigest": "sha256:...",
            "repoCommit": "optional commit",
            "evidence": "optional object"
          }
        }
      ]
    },
    "mcp": {
      "title": "MCP tool server",
      "summary": "Obelisk's public Model Context Protocol tool server — read-only trust tools over JSON-RPC 2.0, plus the plain catalogue.",
      "endpoints": [
        {
          "method": "POST",
          "path": "/mcp",
          "auth": "none",
          "summary": "JSON-RPC 2.0 MCP endpoint (initialize, tools/list, tools/call). Read-only trust tools; stateful per-client rate limit.",
          "body": {
            "jsonrpc": "the literal \"2.0\"",
            "id": "number|string",
            "method": "initialize | tools/list | tools/call",
            "params": "tools/call: { name, arguments }"
          }
        },
        {
          "method": "GET",
          "path": "/mcp/tools",
          "auth": "none",
          "summary": "The plain JSON tool catalogue: every tool's name, description, and input schema."
        }
      ]
    },
    "proof": {
      "title": "Receipt proof plane",
      "summary": "Public receipt-by-hash lookup with sorted-Merkle inclusion proofs against the tamper-evident ledger. By-hash only — no enumeration.",
      "endpoints": [
        {
          "method": "GET",
          "path": "/api/proof",
          "auth": "none",
          "summary": "Self-describing: how to verify, and where the transparency head to pin lives."
        },
        {
          "method": "GET",
          "path": "/api/proof/:hash",
          "auth": "none",
          "summary": "Receipt lookup by 64-hex currentHash → chain position + a Merkle inclusion proof {leafHash, path, root}. Hash material only; never bodies, actors, or intents."
        }
      ]
    },
    "federation": {
      "title": "SAML federation (Obelisk as your IdP)",
      "summary": "Obelisk as the SAML 2.0 IdP for downstream platforms (GitHub, Slack, AWS, …).",
      "endpoints": [
        {
          "method": "GET",
          "path": "/saml/idp/metadata",
          "auth": "none",
          "summary": "IdP metadata: entity ID, SSO URL, and the pinned X.509 signing certificate — hand this URL to any SAML service provider."
        },
        {
          "method": "GET",
          "path": "/saml/idp/sso",
          "auth": "none",
          "summary": "SAML sign-on endpoint: authenticates passkey-first, then auto-POSTs an RSA-SHA256-signed assertion to the SP's ACS.",
          "query": {
            "SAMLRequest": "SP AuthnRequest (redirect binding)"
          }
        }
      ]
    },
    "seal": {
      "title": "The verified seal",
      "summary": "The verified “Secured by Obelisk Gate” seal + per-site verification pages.",
      "endpoints": [
        {
          "method": "GET",
          "path": "/embed/seal.js",
          "auth": "none",
          "summary": "Drop-in loader: one script tag renders the live seal card on a relying party's page."
        },
        {
          "method": "GET",
          "path": "/embed/v1/seal",
          "auth": "none",
          "summary": "The framed seal card itself (rendered live from obeliskgate.com; can't be forged)."
        },
        {
          "method": "GET",
          "path": "/verify/:rp",
          "auth": "none",
          "summary": "Public verification page for a relying party — anyone can confirm the seal is genuine."
        }
      ]
    },
    "scan": {
      "title": "Website scan",
      "summary": "The public website posture scan (the same engine behind the org Rating).",
      "endpoints": [
        {
          "method": "POST",
          "path": "/api/scan",
          "auth": "none",
          "summary": "Scan a public https:// website's security posture → a rating with concrete levers. SSRF-guarded, throttled per client.",
          "body": {
            "url": "public https:// website (domains only)"
          }
        }
      ]
    },
    "contact": {
      "title": "Contact",
      "summary": "Reach the team.",
      "endpoints": [
        {
          "method": "POST",
          "path": "/api/contact",
          "auth": "none",
          "summary": "Send a message to the team (form-encoded: name, email, subject, message). Honeypot-protected, throttled.",
          "body": {
            "name": "string",
            "email": "string",
            "subject": "string?",
            "message": "string"
          }
        }
      ]
    }
  }
}