{
  "openapi": "3.1.0",
  "info": {
    "title": "Obelisk API",
    "version": "obelisk-api-1",
    "summary": "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."
  },
  "servers": [
    {
      "url": "https://obeliskgate.com"
    }
  ],
  "tags": [
    {
      "name": "release_history",
      "description": "Public-safe product history for humans and agents, bound to the revision serving it when deploy metadata is available."
    },
    {
      "name": "auth",
      "description": "Passkey-first sign-in + OIDC."
    },
    {
      "name": "account",
      "description": "The universal Obelisk account — one identity across every app."
    },
    {
      "name": "organizations",
      "description": "Register a business/website for its own Obelisk Rating; manage a team."
    },
    {
      "name": "app_registration",
      "description": "Self-serve OAuth/OIDC client registration — the developer portal (/account/apps) + its API."
    },
    {
      "name": "project_profile",
      "description": "The on-top layer: each project's own per-user profile slice, scoped to its OIDC client."
    },
    {
      "name": "studio_ecosystem",
      "description": "Studio-wide Obelisk rollout truth: declared, observed, required, migration cargo, and receipt-backed acknowledgement state."
    },
    {
      "name": "federation",
      "description": "Obelisk as the SAML 2.0 IdP for downstream platforms (GitHub, Slack, AWS, …)."
    },
    {
      "name": "seal",
      "description": "The verified “Secured by Obelisk Gate” seal + per-site verification pages."
    },
    {
      "name": "scan",
      "description": "The public website posture scan (the same engine behind the org Rating)."
    },
    {
      "name": "contact",
      "description": "Reach the team."
    }
  ],
  "paths": {
    "/changelog": {
      "get": {
        "operationId": "get-changelog",
        "tags": [
          "release_history"
        ],
        "summary": "Human release history (HTML; versioned JSON with ?format=json or Accept: application/json).",
        "x-obelisk-auth": "none",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        }
      }
    },
    "/auth": {
      "get": {
        "operationId": "get-auth",
        "tags": [
          "auth"
        ],
        "summary": "The immersive sign-in/sign-up experience (HTML).",
        "x-obelisk-auth": "none",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        }
      }
    },
    "/auth/assertion-options": {
      "get": {
        "operationId": "get-auth-assertion-options",
        "tags": [
          "auth"
        ],
        "summary": "Begin a (usernameless) passkey sign-in.",
        "x-obelisk-auth": "none",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "parameters": [
          {
            "name": "user",
            "in": "query",
            "required": false,
            "description": "optional account name",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/auth/assertion-verify": {
      "post": {
        "operationId": "post-auth-assertion-verify",
        "tags": [
          "auth"
        ],
        "summary": "Complete a passkey sign-in; returns the session + where to go (home).",
        "x-obelisk-auth": "none",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "user": {
                    "description": "optional"
                  },
                  "response": {
                    "description": "WebAuthn assertion"
                  },
                  "deviceId": {
                    "description": "optional"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/auth/verify-session": {
      "post": {
        "operationId": "post-auth-verify-session",
        "tags": [
          "auth"
        ],
        "summary": "Server-to-server: validate a session token; returns { ok, identityId }.",
        "x-obelisk-auth": "none",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "token": {
                    "description": "macaroon"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/auth/userinfo": {
      "get": {
        "operationId": "get-auth-userinfo",
        "tags": [
          "auth"
        ],
        "summary": "OIDC userinfo for an access token.",
        "x-obelisk-auth": "bearer",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/auth/magic/request": {
      "post": {
        "operationId": "post-auth-magic-request",
        "tags": [
          "auth"
        ],
        "summary": "Request an email magic-link (where enabled).",
        "x-obelisk-auth": "none",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "description": "string",
                    "type": "string"
                  },
                  "dryRun": {
                    "description": "session-gated: return the link, don't send"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/account": {
      "get": {
        "operationId": "get-account",
        "tags": [
          "account"
        ],
        "summary": "Your account page (HTML; JSON with ?format=json → the full profile).",
        "x-obelisk-auth": "session",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ]
      }
    },
    "/account/export": {
      "get": {
        "operationId": "get-account-export",
        "tags": [
          "account"
        ],
        "summary": "Download a portable, secret-free copy of your account (JSON).",
        "x-obelisk-auth": "session",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ]
      }
    },
    "/auth/profile": {
      "post": {
        "operationId": "post-auth-profile",
        "tags": [
          "account"
        ],
        "summary": "Set your display name.",
        "x-obelisk-auth": "session",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "displayName": {
                    "description": "string",
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/auth/account/rename": {
      "post": {
        "operationId": "post-auth-account-rename",
        "tags": [
          "account"
        ],
        "summary": "Change your username (old name stays a resolvable alias).",
        "x-obelisk-auth": "session",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "newUsername": {
                    "description": "string",
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/auth/preferences": {
      "post": {
        "operationId": "post-auth-preferences",
        "tags": [
          "account"
        ],
        "summary": "Merge your preferences (theme/lang/contrast/motion/text/email).",
        "x-obelisk-auth": "session",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "preferences": {
                    "description": "object",
                    "type": "object"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/auth/passkeys/list": {
      "post": {
        "operationId": "post-auth-passkeys-list",
        "tags": [
          "account"
        ],
        "summary": "List your passkeys (no public keys).",
        "x-obelisk-auth": "session",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ]
      }
    },
    "/auth/passkeys/rename": {
      "post": {
        "operationId": "post-auth-passkeys-rename",
        "tags": [
          "account"
        ],
        "summary": "Rename a passkey.",
        "x-obelisk-auth": "session",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "credId": {
                    "description": "string",
                    "type": "string"
                  },
                  "label": {
                    "description": "string",
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/auth/passkeys/remove": {
      "post": {
        "operationId": "post-auth-passkeys-remove",
        "tags": [
          "account"
        ],
        "summary": "Remove a passkey (never the last one).",
        "x-obelisk-auth": "session",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "credId": {
                    "description": "string",
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/auth/passkeys/add-options": {
      "post": {
        "operationId": "post-auth-passkeys-add-options",
        "tags": [
          "account"
        ],
        "summary": "Begin adding a passkey to your account.",
        "x-obelisk-auth": "session",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ]
      }
    },
    "/auth/passkeys/add-verify": {
      "post": {
        "operationId": "post-auth-passkeys-add-verify",
        "tags": [
          "account"
        ],
        "summary": "Finish adding a passkey.",
        "x-obelisk-auth": "session",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "response": {
                    "description": "WebAuthn attestation"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/auth/totp/setup": {
      "post": {
        "operationId": "post-auth-totp-setup",
        "tags": [
          "account"
        ],
        "summary": "Begin TOTP setup (pending) → { secret, uri, qrSvg }.",
        "x-obelisk-auth": "session",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ]
      }
    },
    "/auth/totp/confirm": {
      "post": {
        "operationId": "post-auth-totp-confirm",
        "tags": [
          "account"
        ],
        "summary": "Confirm + activate TOTP with a code.",
        "x-obelisk-auth": "session",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "code": {
                    "description": "6 digits"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/auth/totp/disable": {
      "post": {
        "operationId": "post-auth-totp-disable",
        "tags": [
          "account"
        ],
        "summary": "Turn off TOTP.",
        "x-obelisk-auth": "session",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ]
      }
    },
    "/auth/recovery/regenerate": {
      "post": {
        "operationId": "post-auth-recovery-regenerate",
        "tags": [
          "account"
        ],
        "summary": "Generate new backup codes (shown once).",
        "x-obelisk-auth": "session",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ]
      }
    },
    "/auth/sessions/revoke": {
      "post": {
        "operationId": "post-auth-sessions-revoke",
        "tags": [
          "account"
        ],
        "summary": "Revoke a session.",
        "x-obelisk-auth": "session",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "identifier": {
                    "description": "string",
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/auth/devices/revoke": {
      "post": {
        "operationId": "post-auth-devices-revoke",
        "tags": [
          "account"
        ],
        "summary": "Sign out a whole device — revokes every session it holds, then drops it from the device ledger.",
        "x-obelisk-auth": "session",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "deviceId": {
                    "description": "string",
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/auth/signout": {
      "post": {
        "operationId": "post-auth-signout",
        "tags": [
          "account"
        ],
        "summary": "Clear the session cookie.",
        "x-obelisk-auth": "session",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ]
      }
    },
    "/auth/orgs/create": {
      "post": {
        "operationId": "post-auth-orgs-create",
        "tags": [
          "organizations"
        ],
        "summary": "Create an organization (you become owner).",
        "x-obelisk-auth": "session",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "description": "string",
                    "type": "string"
                  },
                  "website": {
                    "description": "optional url"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/org/{slug}": {
      "get": {
        "operationId": "get-org-slug",
        "tags": [
          "organizations"
        ],
        "summary": "Org dashboard (HTML; JSON with ?format=json), member-gated.",
        "x-obelisk-auth": "session",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/org/{slug}/scan": {
      "post": {
        "operationId": "post-org-slug-scan",
        "tags": [
          "organizations"
        ],
        "summary": "Run an external security-posture scan → an Obelisk Rating. Scan a specific site with { siteId } or the primary by default.",
        "x-obelisk-auth": "owner",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "website": {
                    "description": "optional override"
                  },
                  "siteId": {
                    "description": "optional registered-site id"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/org/{slug}/websites/add": {
      "post": {
        "operationId": "post-org-slug-websites-add",
        "tags": [
          "organizations"
        ],
        "summary": "Add a website this org owns (first becomes primary).",
        "x-obelisk-auth": "owner",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "description": "https url"
                  },
                  "label": {
                    "description": "optional"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/org/{slug}/websites/remove": {
      "post": {
        "operationId": "post-org-slug-websites-remove",
        "tags": [
          "organizations"
        ],
        "summary": "Remove a registered site by id.",
        "x-obelisk-auth": "owner",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "siteId": {
                    "description": "string",
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/org/{slug}/websites/primary": {
      "post": {
        "operationId": "post-org-slug-websites-primary",
        "tags": [
          "organizations"
        ],
        "summary": "Promote a site to primary (the org headline rating tracks it).",
        "x-obelisk-auth": "owner",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "siteId": {
                    "description": "string",
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/org/{slug}/members/role": {
      "post": {
        "operationId": "post-org-slug-members-role",
        "tags": [
          "organizations"
        ],
        "summary": "Change a member's role. Owner-only for owner changes; the last owner can't be demoted.",
        "x-obelisk-auth": "owner",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "user": {
                    "description": "identityId"
                  },
                  "role": {
                    "description": "member|admin|owner"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/org/{slug}/members/remove": {
      "post": {
        "operationId": "post-org-slug-members-remove",
        "tags": [
          "organizations"
        ],
        "summary": "Remove a member. The last owner can never be removed.",
        "x-obelisk-auth": "owner",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "user": {
                    "description": "identityId"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/org/{slug}/invite": {
      "post": {
        "operationId": "post-org-slug-invite",
        "tags": [
          "organizations"
        ],
        "summary": "Create a role-scoped invite link.",
        "x-obelisk-auth": "owner",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "role": {
                    "description": "member|admin"
                  },
                  "email": {
                    "description": "optional"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/org/{slug}/invite/revoke": {
      "post": {
        "operationId": "post-org-slug-invite-revoke",
        "tags": [
          "organizations"
        ],
        "summary": "Revoke an invite.",
        "x-obelisk-auth": "owner",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "code": {
                    "description": "string",
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/join/{code}": {
      "get": {
        "operationId": "get-join-code",
        "tags": [
          "organizations"
        ],
        "summary": "Accept an invite (signs you in first if needed).",
        "x-obelisk-auth": "none",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/account/apps": {
      "get": {
        "operationId": "get-account-apps",
        "tags": [
          "app_registration"
        ],
        "summary": "The hosted app-registration portal (HTML): register apps, list, copy config, revoke.",
        "x-obelisk-auth": "session",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ]
      }
    },
    "/auth/register": {
      "post": {
        "operationId": "post-auth-register",
        "tags": [
          "app_registration"
        ],
        "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.",
        "x-obelisk-auth": "session",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "client_name": {
                    "description": "string",
                    "type": "string"
                  },
                  "redirect_uris": {
                    "description": "https[]"
                  },
                  "obelisk_project_trust_profile": {
                    "description": "object",
                    "type": "object"
                  },
                  "resource_uris": {
                    "description": "https[]"
                  },
                  "org": {
                    "description": "optional org slug"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/auth/apps/list": {
      "post": {
        "operationId": "post-auth-apps-list",
        "tags": [
          "app_registration"
        ],
        "summary": "List apps in tenants you control (personal + admin orgs). Never returns a secret.",
        "x-obelisk-auth": "session",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ]
      }
    },
    "/auth/apps/revoke": {
      "post": {
        "operationId": "post-auth-apps-revoke",
        "tags": [
          "app_registration"
        ],
        "summary": "Revoke an app you own → status:revoked.",
        "x-obelisk-auth": "session",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "clientId": {
                    "description": "string",
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/project-profile": {
      "get": {
        "operationId": "get-api-project-profile",
        "tags": [
          "project_profile"
        ],
        "summary": "Read this project's profile slice for the token's user → { sub, project, profile }.",
        "x-obelisk-auth": "bearer",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "operationId": "post-api-project-profile",
        "tags": [
          "project_profile"
        ],
        "summary": "Shallow-merge into this project's slice.",
        "x-obelisk-auth": "bearer",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "profile": {
                    "description": "object",
                    "type": "object"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/studio/ecosystem/manifest.json": {
      "get": {
        "operationId": "get-studio-ecosystem-manifest-json",
        "tags": [
          "studio_ecosystem"
        ],
        "summary": "Fleet manifest for Studio Ops: every registered project, required Obelisk versions, observed status, and migration state.",
        "x-obelisk-auth": "none",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        }
      }
    },
    "/studio/ecosystem/projects/{slug}": {
      "get": {
        "operationId": "get-studio-ecosystem-projects-slug",
        "tags": [
          "studio_ecosystem"
        ],
        "summary": "One project's ecosystem profile row from the canonical manifest.",
        "x-obelisk-auth": "none",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/studio/ecosystem/projects/{slug}/migration": {
      "get": {
        "operationId": "get-studio-ecosystem-projects-slug-migration",
        "tags": [
          "studio_ecosystem"
        ],
        "summary": "Content-addressed recipient-owned Ark migration cargo for one project.",
        "x-obelisk-auth": "none",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/studio/ecosystem/projects/{slug}/ack": {
      "post": {
        "operationId": "post-studio-ecosystem-projects-slug-ack",
        "tags": [
          "studio_ecosystem"
        ],
        "summary": "Record a recipient-owned migration acknowledgement. Requires x-obelisk-ecosystem-ack; writes an ecosystem-ack receipt.",
        "x-obelisk-auth": "ack-secret",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "result": {
                    "description": "applied|already-current|rejected|blocked"
                  },
                  "cargoDigest": {
                    "description": "sha256:..."
                  },
                  "repoCommit": {
                    "description": "optional commit"
                  },
                  "evidence": {
                    "description": "optional object"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/saml/idp/metadata": {
      "get": {
        "operationId": "get-saml-idp-metadata",
        "tags": [
          "federation"
        ],
        "summary": "IdP metadata: entity ID, SSO URL, and the pinned X.509 signing certificate — hand this URL to any SAML service provider.",
        "x-obelisk-auth": "none",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        }
      }
    },
    "/saml/idp/sso": {
      "get": {
        "operationId": "get-saml-idp-sso",
        "tags": [
          "federation"
        ],
        "summary": "SAML sign-on endpoint: authenticates passkey-first, then auto-POSTs an RSA-SHA256-signed assertion to the SP's ACS.",
        "x-obelisk-auth": "none",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "parameters": [
          {
            "name": "SAMLRequest",
            "in": "query",
            "required": false,
            "description": "SP AuthnRequest (redirect binding)",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/embed/seal.js": {
      "get": {
        "operationId": "get-embed-seal-js",
        "tags": [
          "seal"
        ],
        "summary": "Drop-in loader: one script tag renders the live seal card on a relying party's page.",
        "x-obelisk-auth": "none",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        }
      }
    },
    "/embed/v1/seal": {
      "get": {
        "operationId": "get-embed-v1-seal",
        "tags": [
          "seal"
        ],
        "summary": "The framed seal card itself (rendered live from obeliskgate.com; can't be forged).",
        "x-obelisk-auth": "none",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        }
      }
    },
    "/verify/{rp}": {
      "get": {
        "operationId": "get-verify-rp",
        "tags": [
          "seal"
        ],
        "summary": "Public verification page for a relying party — anyone can confirm the seal is genuine.",
        "x-obelisk-auth": "none",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "parameters": [
          {
            "name": "rp",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/scan": {
      "post": {
        "operationId": "post-api-scan",
        "tags": [
          "scan"
        ],
        "summary": "Scan a public https:// website's security posture → a rating with concrete levers. SSRF-guarded, throttled per client.",
        "x-obelisk-auth": "none",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "description": "public https:// website (domains only)"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/contact": {
      "post": {
        "operationId": "post-api-contact",
        "tags": [
          "contact"
        ],
        "summary": "Send a message to the team (form-encoded: name, email, subject, message). Honeypot-protected, throttled.",
        "x-obelisk-auth": "none",
        "responses": {
          "200": {
            "description": "Success."
          },
          "default": {
            "description": "Error — { ok:false, reason:<code>, message?:<human> } on failures; HTTP status mirrors it."
          }
        },
        "requestBody": {
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "description": "string",
                    "type": "string"
                  },
                  "email": {
                    "description": "string",
                    "type": "string"
                  },
                  "subject": {
                    "description": "string?",
                    "type": "string"
                  },
                  "message": {
                    "description": "string",
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "sessionCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "obelisk_session",
        "description": "obelisk_session cookie OR body.token"
      },
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "OIDC access token (Authorization: Bearer). Operations marked x-obelisk-auth:\"owner\" additionally require: session + org owner/admin."
      }
    }
  }
}