{
  "openapi": "3.1.0",
  "info": {
    "title": "YieldFabric Auth Domain API",
    "version": "0.6.0",
    "summary": "REST + GraphQL API for users, sessions, sign-in flows, signing keys, groups, delegation, and account deployment.",
    "description": "Canonical specification for the YieldFabric auth service.\n\nAuth is the identity service: users, sessions, roles, permissions,\nsigning keys, groups, delegation, and on-chain account deployment.\nAfter the auth/payments split, auth holds **no MQ connection**,\n**no RPC client**, and **no chain config** — all blockchain\nsubmission and chain reads cross the HTTP boundary into payments\n(`services.payments_url`). See `README.md` at the auth-crate root\nfor the architectural rationale.\n\n## How to read this spec\n\nAuth exposes 171 documented REST path items across\n16 path shards, while payment-domain GraphQL operations are documented\nthrough the federated gateway. Each `paths/*.yaml` shard documents one logical\ngroup; standard Swagger UI / Redoc consumers can walk the spec\ndirectly. A handful of in-process routes are intentionally not\ndocumented here (the same handlers are re-mounted under a parallel\nprefix — see notes in `paths/groups.yaml` and `paths/keys.yaml`).\n\nThe federated identity GraphQL surface at `/graphql/identity` is\ndocumented in `paths/graphql.yaml`; per-resolver detail is a\nfollow-up.\n\nSee `README.md` for the layout and `guides/authentication.md` for\nJWT lifecycle, service-token shape, and the auth-method matrix.\n",
    "contact": {
      "name": "YieldFabric Team",
      "email": "support@yieldfabric.com"
    },
    "license": {
      "name": "Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://auth.yieldfabric.com",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Push",
      "description": "Web Push — Javier reaches a person's phone when the app is closed.\nA subscription is a device of a user; sister services report items\ninto a coalescing outbox and auth sends one notification per user.\nSee [paths/push.yaml](paths/push.yaml) and\n[architecture/push-notifications.md](../architecture/push-notifications.md).\n"
    },
    {
      "name": "OAuth",
      "description": "OAuth 2.1 authorization server for MCP connector logins\n(claude.ai custom connectors, Claude Code/Desktop connecting to\nthe agents service's /mcp resource). RFC 8414 metadata, RFC 7591\ndynamic client registration, PKCE code flow with a\nfrontend-rendered consent page, and an isolated refresh-token\nfamily. See [paths/oauth.yaml](paths/oauth.yaml).\n"
    },
    {
      "name": "Auth",
      "description": "Sign-in flows, token lifecycle, MCP login. Both public (login,\nprovider-exchange) and authenticated (refresh, logout) endpoints\nlive here. See [guides/authentication.md](guides/authentication.md).\n",
      "externalDocs": {
        "url": "guides/authentication.md",
        "description": "Authentication guide"
      }
    },
    {
      "name": "Identity providers",
      "description": "Pluggable identity-provider exchange flows: Averer, MetaMask,\nEmail/OTP, WebAuthn. Public configuration plus\n`/auth/{provider}/exchange`, `/auth/{provider}/link`,\n`/auth/{provider}/unlink`. See\n[guides/identity-providers.md](guides/identity-providers.md).\n",
      "externalDocs": {
        "url": "guides/identity-providers.md",
        "description": "Identity providers guide"
      }
    },
    {
      "name": "Users",
      "description": "User CRUD, profile, email change, account lockdown, login-key\ndiscovery. Role and deactivation operations require admin\npermissions.\n"
    },
    {
      "name": "Permissions",
      "description": "Per-user permission grant / revoke / replace / check. Granular\nRBAC on top of the six built-in roles.\n"
    },
    {
      "name": "Signatures",
      "description": "Wallet-signature authentication + signature-key registration and\nmanagement. See [guides/signatures.md](guides/signatures.md).\n",
      "externalDocs": {
        "url": "guides/signatures.md",
        "description": "Signatures guide"
      }
    },
    {
      "name": "API keys",
      "description": "API-key authentication + management. Alternative to JWT for\nnon-interactive callers. See [guides/api-keys.md](guides/api-keys.md).\n",
      "externalDocs": {
        "url": "guides/api-keys.md",
        "description": "API keys guide"
      }
    },
    {
      "name": "Groups",
      "description": "Group CRUD, members, group keypairs, entity scope, audit logs,\non-chain account members/owners. See [guides/groups.md](guides/groups.md).\n",
      "externalDocs": {
        "url": "guides/groups.md",
        "description": "Groups guide"
      }
    },
    {
      "name": "Delegation",
      "description": "Delegation tokens + on-demand delegation JWT minting. Lets a\nuser act on behalf of a group with a scoped permission set.\nSee [guides/delegation.md](guides/delegation.md).\n",
      "externalDocs": {
        "url": "guides/delegation.md",
        "description": "Delegation guide"
      }
    },
    {
      "name": "Keys",
      "description": "Server-custodied key management (encryption keys, signing keys),\nper-key encrypt/decrypt/sign/verify operations, and vault-side\nkey operations used by sister services.\n"
    },
    {
      "name": "Accounts",
      "description": "On-chain account deployment for users and groups, default-chain\nmanagement. See [guides/account-deployment.md](guides/account-deployment.md).\n",
      "externalDocs": {
        "url": "guides/account-deployment.md",
        "description": "Account deployment guide"
      }
    },
    {
      "name": "Verifications",
      "description": "KYC / credential-issuance records and the Sumsub webhook.\n"
    },
    {
      "name": "Admin",
      "description": "Master-key management, emergency operations, protected\nvalidation endpoints, health checks. SuperAdmin role required\nfor `/admin/**` routes.\n"
    },
    {
      "name": "Connections",
      "description": "Entity-to-entity connections: requests, sharing preferences,\nnotifications. See [paths/connections.yaml](paths/connections.yaml).\n"
    },
    {
      "name": "Invitations",
      "description": "Unified invitation framework — magic-link invites to become a user and\nexecute a typed target action (none | obligation | deal_flow |\ngroup_join | connection). Supersedes /auth/connections/invite*. See\n[paths/invitations.yaml](paths/invitations.yaml).\n"
    },
    {
      "name": "GraphQL",
      "description": "Federated identity subgraph at `/graphql/identity`. The SDL\nendpoint at `/graphql/identity/sdl` is unauthenticated (Apollo\nRouter compose needs it); the data endpoint is JWT-gated.\n"
    }
  ],
  "paths": {
    "/auth/push/config": {
      "get": {
        "operationId": "getPushConfig",
        "tags": [
          "Push"
        ],
        "summary": "Whether this deployment can reach phones, and the key browsers subscribe with",
        "description": "Public. `vapid_public_key` is the base64url (no padding) uncompressed\nP-256 point the browser passes as `applicationServerKey` — verbatim.\n`enabled: false` (key `null`) when the deployment is not configured;\nthe app then shows no alerts toggle.\n",
        "responses": {
          "200": {
            "description": "Configuration.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "enabled": {
                      "type": "boolean"
                    },
                    "vapid_public_key": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/auth/me/push-subscriptions": {
      "get": {
        "operationId": "getMyPushSubscriptions",
        "tags": [
          "Push"
        ],
        "summary": "How many devices the caller can be reached on",
        "description": "The device's own state is its `PushSubscription`; this is the\naccount-wide count of active devices. Direct personal sessions only.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Active device count.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "devices": {
                      "type": "integer",
                      "format": "int64"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer",
            "or inactive user.": null
          },
          "403": {
            "description": "Not a direct personal session (delegation",
            "ownership-child or MCP credential).": null
          }
        }
      },
      "put": {
        "operationId": "putMyPushSubscription",
        "tags": [
          "Push"
        ],
        "summary": "Register or refresh this device",
        "description": "Idempotent on the endpoint: the same device re-registering updates its\nkeys and `last_seen_at`; a device that the push service had reported\ngone comes back alive; a device re-subscribing under another account\nmoves to it. The app calls this after `pushManager.subscribe(...)`,\non `pushsubscriptionchange`, and at most once a day on boot to keep\nthe row fresh. Endpoint and keys are AEAD-sealed at rest.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "endpoint",
                  "keys"
                ],
                "properties": {
                  "endpoint": {
                    "type": "string",
                    "format": "uri",
                    "description": "https URL from the push service (≤ 2048 chars)."
                  },
                  "keys": {
                    "type": "object",
                    "required": [
                      "p256dh",
                      "auth"
                    ],
                    "properties": {
                      "p256dh": {
                        "type": "string",
                        "description": "Client public key, base64url."
                      },
                      "auth": {
                        "type": "string",
                        "description": "Client auth secret, base64url."
                      }
                    }
                  },
                  "user_agent": {
                    "type": "string",
                    "nullable": true,
                    "description": "Free text for the person's own device list (truncated to 200 chars)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Registered.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Endpoint not https",
            "or keys not base64url.": null
          },
          "401": {
            "description": "Missing or invalid bearer",
            "or inactive user.": null
          },
          "403": {
            "description": "Not a direct personal session."
          },
          "503": {
            "description": "Push is not configured on this deployment."
          }
        }
      },
      "delete": {
        "operationId": "deleteMyPushSubscription",
        "tags": [
          "Push"
        ],
        "summary": "Forget this device",
        "description": "Scoped to the caller — one account cannot unsubscribe another's\ndevice by guessing its endpoint. The app calls this on sign-out\n(before the token is cleared) and when it finds permission revoked.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "endpoint"
                ],
                "properties": {
                  "endpoint": {
                    "type": "string",
                    "format": "uri"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Whether a row was removed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "removed": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer",
            "or inactive user.": null
          },
          "403": {
            "description": "Not a direct personal session."
          }
        }
      }
    },
    "/auth/me/push-subscriptions/test": {
      "post": {
        "operationId": "sendMyPushTest",
        "tags": [
          "Push"
        ],
        "summary": "Send me a test notification",
        "description": "Queues one `test` row for the caller (\"Alerts are on\"), delivered on\nthe worker's next tick to every active device. One open test at a\ntime: a second call while the first is queued returns `queued: false`.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "202": {
            "description": "Queued (or already queued).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "queued": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer",
            "or inactive user.": null
          },
          "403": {
            "description": "Not a direct personal session."
          },
          "503": {
            "description": "Push is not configured on this deployment."
          }
        }
      }
    },
    "/auth/internal/push": {
      "post": {
        "operationId": "reportPushItem",
        "tags": [
          "Push"
        ],
        "summary": "Report an item that now needs a person (service tokens only)",
        "description": "A sister service says \"this now needs this person\". Auth queues it\nand composes the notification later — the producer never learns\nwhether the person has a device, and a deployment without push\nanswers `202` with `queued: false`.\n\n`item_key` is the producer's stable identity for the item (agents\nuses the queue entry key, e.g. `proposal:<uuid>`; payments uses\n`message:<id>`). A second report of an OPEN item is a no-op\n(`queued: false`), so retries are safe. `url` must be an in-app\npath; a single item lands the tap there\n(`/private?reader=work/<entry_key>`), a digest lands on the Desk.\n`badge` is the producer's current count of what needs the person,\nwhen it knows it — the newest one wins and rides on the notification.\n\nAccepted only from a service token (`sub` = `service:…`) that carries\nno delegation, MCP or password-change context.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "user_id",
                  "kind",
                  "item_key",
                  "title",
                  "url"
                ],
                "properties": {
                  "user_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The person (auth `users.id`; agents' `on_behalf_of`)."
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "decision",
                      "signature",
                      "mention"
                    ]
                  },
                  "item_key": {
                    "type": "string",
                    "description": "Producer's stable item identity (≤ 200 chars)."
                  },
                  "title": {
                    "type": "string",
                    "description": "≤ 120 chars. Shown on the lock screen — no amounts, no counterparties."
                  },
                  "body": {
                    "type": "string",
                    "nullable": true,
                    "description": "≤ 240 chars."
                  },
                  "url": {
                    "type": "string",
                    "description": "In-app path the tap opens (≤ 512 chars)."
                  },
                  "badge": {
                    "type": "integer",
                    "nullable": true,
                    "minimum": 0,
                    "maximum": 9999
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued — or not, when the item was already open or push is off.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "queued": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unknown kind",
            "empty or oversized field": null,
            "or a URL that is not an in-app path.": null
          },
          "401": {
            "description": "Missing or invalid bearer."
          },
          "403": {
            "description": "Not a plain service token."
          }
        }
      },
      "delete": {
        "operationId": "retractPushItem",
        "tags": [
          "Push"
        ],
        "summary": "Withdraw an item the person already dealt with (service tokens only)",
        "description": "Removes the OPEN outbox rows for `(user_id, item_key)` — or for\n`item_key` alone when `user_id` is omitted (a producer that only knows\nthe item: the MQ after a signature, whose message is filed under a\ngroup). Producers call it when the item is resolved in the app inside\nthe coalescing window (a decision taken, a signature given), so the\nphone never buzzes for something already done. Rows already sent are\nhistory and stay.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "item_key"
                ],
                "properties": {
                  "user_id": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true,
                    "description": "Omit to retract by key alone."
                  },
                  "item_key": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "How many open rows were removed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "removed": {
                      "type": "integer",
                      "format": "int64"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer."
          },
          "403": {
            "description": "Not a plain service token."
          }
        }
      }
    },
    "/auth/login": {
      "post": {
        "operationId": "login",
        "summary": "Email + password login",
        "description": "Returns an access token + refresh token + user profile on success.\nFailed logins are rate-limited per-IP via a progressive-delay\nbackoff (see guides/authentication.md#rate-limits).\n",
        "tags": [
          "Auth"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "password"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "password": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Login successful",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "token",
                    "refresh_token",
                    "token_type",
                    "expires_in",
                    "user"
                  ],
                  "description": "Returned by `POST /auth/login` and `POST /auth/login/with-services`.",
                  "properties": {
                    "token": {
                      "type": "string",
                      "description": "Short-lived access token (default 15 min)."
                    },
                    "refresh_token": {
                      "type": "string",
                      "description": "Long-lived refresh token (30 days, single-use, rotated on each refresh)."
                    },
                    "token_type": {
                      "type": "string",
                      "const": "Bearer"
                    },
                    "expires_in": {
                      "type": "integer",
                      "description": "Access-token lifetime in seconds."
                    },
                    "user": {
                      "type": "object",
                      "required": [
                        "id",
                        "email",
                        "role",
                        "is_active"
                      ],
                      "description": "User record shape returned by `/auth/users/me` and friends.\n`password_hash` is always returned as an empty string (never\nexposed). See `yieldfabric-auth/src/handlers/types.rs::UserInfo`.\n",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "email": {
                          "type": "string",
                          "format": "email"
                        },
                        "password_hash": {
                          "type": "string",
                          "description": "Always empty in responses."
                        },
                        "role": {
                          "type": "string",
                          "description": "Built-in role assigned to every user. Role + Permission grants make\nup the RBAC matrix; see `auth.roles` in `config/system.yaml` for\nrole overrides. SuperAdmin always has every permission.\n",
                          "enum": [
                            "SuperAdmin",
                            "Admin",
                            "Manager",
                            "Operator",
                            "User",
                            "Viewer",
                            "ApiClient"
                          ]
                        },
                        "is_active": {
                          "type": "boolean"
                        },
                        "must_change_password": {
                          "type": "boolean"
                        },
                        "password_changed_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "failed_login_count": {
                          "type": "integer"
                        },
                        "last_failed_login": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "is_external_signer": {
                          "type": "boolean",
                          "description": "Derived. **True** when the user has no active `KeyType: Signing`\nkeypair with `encrypted_private_key` — i.e. they sign exclusively\nvia an external wallet (Averer / Dynamic / MetaMask /\nWebAuthn passkey). The frontend trusts this flag verbatim and\nsets `requireManualSignature: true` on every payment mutation\nwhen it's true, so the backend routes the message into the\nmanual-sign drawer instead of attempting server-side signing.\nRecomputed on every call from a case-insensitive\n`LOWER(key_type) = 'signing'` SQL filter. See the architecture\ndoc for the full chain and the cache-invalidation gotcha.\n"
                        },
                        "live_connectors_enabled": {
                          "type": "boolean",
                          "description": "The person's LIVE-connector opt-in (`users.live_connectors_enabled`,\nagent surface spec §5.4). While false, none of their connector\ncredentials can be generated for, minted on, or refreshed onto a\nlive chain. Set via `PUT /auth/users/me/live-connectors`\n(direct session + live verification floor). Returned by\n`GET /auth/users/me` only; best-effort read.\n"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired, or wrong-audience JWT (user or service).\nIncludes invalid API keys on the exchange endpoints, failed wallet\nsignatures on the signin endpoints, and failed Sumsub HMAC on the\nwebhook.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "no-token": {
                    "summary": "Missing Authorization header",
                    "value": {
                      "error": "missing bearer token"
                    }
                  },
                  "invalid-token": {
                    "summary": "JWT signature / structure invalid",
                    "value": {
                      "error": "invalid token"
                    }
                  },
                  "invalid-subject": {
                    "summary": "JWT sub field unparseable",
                    "value": {
                      "error": "invalid token subject"
                    }
                  },
                  "webhook-signature": {
                    "summary": "Sumsub HMAC verification failed",
                    "value": {
                      "error": "invalid webhook signature or payload"
                    }
                  },
                  "averer-reverify": {
                    "summary": "Averer flagged a re-verification requirement",
                    "value": {
                      "error": "averer_reverify_required"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error. Treat as transient; safe to retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "internal": {
                    "value": {
                      "error": "internal error"
                    }
                  },
                  "load-failed": {
                    "summary": "DB load error",
                    "value": {
                      "error": "load failed"
                    }
                  },
                  "persist-failed": {
                    "summary": "DB persist error",
                    "value": {
                      "error": "persist failed"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/login \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"email\": \"user@example.com\",\n  \"password\": \"<string>\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/login', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"email\": \"user@example.com\",\n    \"password\": \"<string>\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/login'\nresp = httpx.post(url, headers=headers, json={\"email\": \"user@example.com\", \"password\": \"<string>\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/login/with-services": {
      "post": {
        "operationId": "loginWithServices",
        "summary": "Login + mint a token scoped to specific service audiences",
        "description": "Same as `/auth/login` but the issued JWT's `aud` is narrowed to\nthe requested services. Use when an SDK / wallet needs a token\nwith limited cross-service reach.\n",
        "tags": [
          "Auth"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "password",
                  "services"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "password": {
                    "type": "string"
                  },
                  "services": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "default",
                        "payments",
                        "yieldfabric",
                        "vault"
                      ]
                    },
                    "minItems": 1,
                    "description": "Audiences to embed in the issued JWT."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Login successful",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "token",
                    "refresh_token",
                    "token_type",
                    "expires_in",
                    "user"
                  ],
                  "description": "Returned by `POST /auth/login` and `POST /auth/login/with-services`.",
                  "properties": {
                    "token": {
                      "type": "string",
                      "description": "Short-lived access token (default 15 min)."
                    },
                    "refresh_token": {
                      "type": "string",
                      "description": "Long-lived refresh token (30 days, single-use, rotated on each refresh)."
                    },
                    "token_type": {
                      "type": "string",
                      "const": "Bearer"
                    },
                    "expires_in": {
                      "type": "integer",
                      "description": "Access-token lifetime in seconds."
                    },
                    "user": {
                      "type": "object",
                      "required": [
                        "id",
                        "email",
                        "role",
                        "is_active"
                      ],
                      "description": "User record shape returned by `/auth/users/me` and friends.\n`password_hash` is always returned as an empty string (never\nexposed). See `yieldfabric-auth/src/handlers/types.rs::UserInfo`.\n",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "email": {
                          "type": "string",
                          "format": "email"
                        },
                        "password_hash": {
                          "type": "string",
                          "description": "Always empty in responses."
                        },
                        "role": {
                          "type": "string",
                          "description": "Built-in role assigned to every user. Role + Permission grants make\nup the RBAC matrix; see `auth.roles` in `config/system.yaml` for\nrole overrides. SuperAdmin always has every permission.\n",
                          "enum": [
                            "SuperAdmin",
                            "Admin",
                            "Manager",
                            "Operator",
                            "User",
                            "Viewer",
                            "ApiClient"
                          ]
                        },
                        "is_active": {
                          "type": "boolean"
                        },
                        "must_change_password": {
                          "type": "boolean"
                        },
                        "password_changed_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "failed_login_count": {
                          "type": "integer"
                        },
                        "last_failed_login": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "is_external_signer": {
                          "type": "boolean",
                          "description": "Derived. **True** when the user has no active `KeyType: Signing`\nkeypair with `encrypted_private_key` — i.e. they sign exclusively\nvia an external wallet (Averer / Dynamic / MetaMask /\nWebAuthn passkey). The frontend trusts this flag verbatim and\nsets `requireManualSignature: true` on every payment mutation\nwhen it's true, so the backend routes the message into the\nmanual-sign drawer instead of attempting server-side signing.\nRecomputed on every call from a case-insensitive\n`LOWER(key_type) = 'signing'` SQL filter. See the architecture\ndoc for the full chain and the cache-invalidation gotcha.\n"
                        },
                        "live_connectors_enabled": {
                          "type": "boolean",
                          "description": "The person's LIVE-connector opt-in (`users.live_connectors_enabled`,\nagent surface spec §5.4). While false, none of their connector\ncredentials can be generated for, minted on, or refreshed onto a\nlive chain. Set via `PUT /auth/users/me/live-connectors`\n(direct session + live verification floor). Returned by\n`GET /auth/users/me` only; best-effort read.\n"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired, or wrong-audience JWT (user or service).\nIncludes invalid API keys on the exchange endpoints, failed wallet\nsignatures on the signin endpoints, and failed Sumsub HMAC on the\nwebhook.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "no-token": {
                    "summary": "Missing Authorization header",
                    "value": {
                      "error": "missing bearer token"
                    }
                  },
                  "invalid-token": {
                    "summary": "JWT signature / structure invalid",
                    "value": {
                      "error": "invalid token"
                    }
                  },
                  "invalid-subject": {
                    "summary": "JWT sub field unparseable",
                    "value": {
                      "error": "invalid token subject"
                    }
                  },
                  "webhook-signature": {
                    "summary": "Sumsub HMAC verification failed",
                    "value": {
                      "error": "invalid webhook signature or payload"
                    }
                  },
                  "averer-reverify": {
                    "summary": "Averer flagged a re-verification requirement",
                    "value": {
                      "error": "averer_reverify_required"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/login/with-services \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"email\": \"user@example.com\",\n  \"password\": \"<string>\",\n  \"services\": [\n    \"default\"\n  ]\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/login/with-services', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"email\": \"user@example.com\",\n    \"password\": \"<string>\",\n    \"services\": [\n      \"default\"\n    ]\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/login/with-services'\nresp = httpx.post(url, headers=headers, json={\"email\": \"user@example.com\", \"password\": \"<string>\", \"services\": [\"default\"]})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/refresh": {
      "post": {
        "operationId": "refreshToken",
        "summary": "Rotate access + refresh tokens",
        "description": "Single-use refresh — the supplied refresh token is revoked and a\nfresh access + refresh pair is issued. Device + IP info is\ntracked for audit. `chain_id` is required: send the current JWT's\n`default_chain_id` for an ordinary refresh, or a different served\nand enabled chain to switch modes. Personal refresh is walletless-safe:\nwhen no account exists on the target chain, the new JWT keeps\n`default_chain_id` and carries null wallet/address claims. A delegation\nrefresh additionally requires the `acting_as` group's deployed default\nthere and re-mints a delegation JWT pinned to that chain. Chain/group\nvalidation happens before the single-use token is claimed, so a\ncorrectable 409 does not consume it. Returns 401 if the refresh token is\nunknown, revoked, or expired.\n\n**Connector lineages** (a refresh row minted by a connector API key) rotate from the ROW's binding, never the request:\nthe connector strip is re-applied, `session_kind` + `allowed_chains`\nare re-stamped verbatim (never widened), a `chain_id` outside\n`allowed_chains` is `409 chain_not_allowed`, and a LIVE `chain_id`\n(not in `auth.modes.test_chain_ids`) additionally needs the user's\nlive-connector opt-in (`PUT /auth/users/me/live-connectors`), else\n`403 live_connector_opt_in_required`. Both are checked BEFORE the\nsingle-use claim, so the token stays usable on an allowed test chain.\nPersonal and delegation lineages never consult the opt-in.\n",
        "tags": [
          "Auth"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "refresh_token",
                  "chain_id"
                ],
                "properties": {
                  "refresh_token": {
                    "type": "string"
                  },
                  "chain_id": {
                    "type": "string",
                    "description": "Target session chain; use the current JWT's `default_chain_id` for a normal refresh."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rotation successful",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "access_token",
                    "refresh_token",
                    "token_type",
                    "expires_in"
                  ],
                  "properties": {
                    "access_token": {
                      "type": "string"
                    },
                    "refresh_token": {
                      "type": "string"
                    },
                    "token_type": {
                      "type": "string",
                      "const": "Bearer"
                    },
                    "expires_in": {
                      "type": "integer",
                      "description": "Access-token lifetime in seconds."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or unsupported chain. Flat `{ \"error\": \"...\" }` body."
          },
          "401": {
            "description": "Missing, malformed, expired, or wrong-audience JWT (user or service).\nIncludes invalid API keys on the exchange endpoints, failed wallet\nsignatures on the signin endpoints, and failed Sumsub HMAC on the\nwebhook.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "no-token": {
                    "summary": "Missing Authorization header",
                    "value": {
                      "error": "missing bearer token"
                    }
                  },
                  "invalid-token": {
                    "summary": "JWT signature / structure invalid",
                    "value": {
                      "error": "invalid token"
                    }
                  },
                  "invalid-subject": {
                    "summary": "JWT sub field unparseable",
                    "value": {
                      "error": "invalid token subject"
                    }
                  },
                  "webhook-signature": {
                    "summary": "Sumsub HMAC verification failed",
                    "value": {
                      "error": "invalid webhook signature or payload"
                    }
                  },
                  "averer-reverify": {
                    "summary": "Averer flagged a re-verification requirement",
                    "value": {
                      "error": "averer_reverify_required"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Connector lineage only: `chain_id` is a LIVE chain and the user\nhas not enabled live connectors. Pre-claim — the token is not\nconsumed.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "403 from `/auth/api-key/generate` (a connector key naming a live\nchain), `/auth/api-key` (a connector session landing on a live chain)\nand `/auth/refresh` (a connector lineage rotating onto a live chain):\nthe user has not enabled live connectors\n(`users.live_connectors_enabled`, agent surface spec §5.4). Not a\nretry-on-another-chain refusal — the person opts in from a direct\nsession via `PUT /auth/users/me/live-connectors`, or the credential\nuses a test chain. On `/auth/refresh` the token is not consumed.\n",
                  "required": [
                    "error",
                    "code",
                    "chain_id"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "const": "live_connector_opt_in_required"
                    },
                    "chain_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Disabled chain, a delegation target group with no account there,\nor (connector lineage) a `chain_id` outside the row's\n`allowed_chains`. Validation occurs before refresh-token\nconsumption.\n",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "required": [
                        "error",
                        "code",
                        "chain_id"
                      ],
                      "properties": {
                        "error": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "const": "CHAIN_UNAVAILABLE"
                        },
                        "chain_id": {
                          "type": "string"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "error",
                        "code",
                        "entity_kind",
                        "entity_id",
                        "chain_id"
                      ],
                      "properties": {
                        "error": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "const": "CHAIN_ACCOUNT_REQUIRED"
                        },
                        "entity_kind": {
                          "type": "string",
                          "enum": [
                            "user",
                            "group"
                          ]
                        },
                        "entity_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "activation_url": {
                          "type": "string",
                          "description": "Exact recovery resource when the handler can name it."
                        },
                        "operation": {
                          "type": "string",
                          "description": "Stable operation label for diagnostics; clients route on `code` and the entity/chain fields."
                        }
                      }
                    },
                    {
                      "type": "object",
                      "description": "409 from `/auth/api-key` (connector exchange), `/auth/refresh` (connector\nlineage) and the delegation mints: the requested chain is outside the\ncredential's / caller session's `allowed_chains`. The token or key is\nnot consumed — retry on an allowed chain.\n",
                      "required": [
                        "error",
                        "code",
                        "chain_id",
                        "allowed_chains"
                      ],
                      "properties": {
                        "error": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "const": "chain_not_allowed"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "allowed_chains": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/refresh \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"refresh_token\": \"<string>\",\n  \"chain_id\": \"153\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/refresh', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"refresh_token\": \"<string>\",\n    \"chain_id\": \"153\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/refresh'\nresp = httpx.post(url, headers=headers, json={\"refresh_token\": \"<string>\", \"chain_id\": \"153\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/logout": {
      "post": {
        "operationId": "logoutDevice",
        "summary": "Revoke the current refresh token (single device)",
        "tags": [
          "Auth"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "204": {
            "description": "Token revoked"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/logout \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/logout', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/logout'\nresp = httpx.post(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/logout-all": {
      "post": {
        "operationId": "logoutAllDevices",
        "summary": "Revoke every refresh token for the current user",
        "tags": [
          "Auth"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "204": {
            "description": "All sessions revoked"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/logout-all \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/logout-all', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/logout-all'\nresp = httpx.post(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/.well-known/oauth-authorization-server": {
      "get": {
        "operationId": "oauthAuthorizationServerMetadata",
        "summary": "Authorization-server metadata (RFC 8414)",
        "description": "Discovery document for OAuth 2.1 clients. Advertises the\nauthorize/token/registration endpoints, `code` response type,\n`authorization_code` + `refresh_token` grants plus the RFC 8693\n`urn:ietf:params:oauth:grant-type:token-exchange` grant (YF services\nonly — see `/oauth/token`), PKCE `S256`, and public-client (`none`)\ntoken-endpoint auth. Base URL comes from the `AUTH_PUBLIC_URL` env\nvar (default `https://auth.yieldfabric.com`).\n",
        "tags": [
          "OAuth"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "RFC 8414 metadata document",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "issuer",
                    "authorization_endpoint",
                    "token_endpoint"
                  ],
                  "properties": {
                    "issuer": {
                      "type": "string"
                    },
                    "authorization_endpoint": {
                      "type": "string"
                    },
                    "token_endpoint": {
                      "type": "string"
                    },
                    "registration_endpoint": {
                      "type": "string"
                    },
                    "response_types_supported": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "grant_types_supported": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "code_challenge_methods_supported": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "token_endpoint_auth_methods_supported": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "scopes_supported": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "resource_indicators_supported": {
                      "type": "boolean",
                      "description": "RFC 8707. When true, `/oauth/authorize` and `/oauth/token` accept a `resource` parameter naming the MCP resource server the token is for, and the minted access token carries that URI in its `aud`."
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl https://auth.yieldfabric.com/.well-known/oauth-authorization-server"
          }
        ]
      }
    },
    "/oauth/register": {
      "post": {
        "operationId": "oauthRegisterClient",
        "summary": "Dynamic client registration (RFC 7591, public clients)",
        "description": "Registers an OAuth client (e.g. claude.ai registering itself when\na user adds the YF connector). Public clients only — no client\nsecret is issued; the code grant is protected by PKCE. Redirect\nURIs must be `https` or `http://localhost*` / `http://127.0.0.1*`.\n",
        "tags": [
          "OAuth"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "redirect_uris"
                ],
                "properties": {
                  "client_name": {
                    "type": "string",
                    "description": "Display name shown on the consent page. Default \"MCP client\"."
                  },
                  "redirect_uris": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Client registered",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "client_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "client_name": {
                      "type": "string"
                    },
                    "redirect_uris": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "grant_types": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "response_types": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "token_endpoint_auth_method": {
                      "type": "string",
                      "enum": [
                        "none"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_client_metadata / invalid_redirect_uri (OAuth error body)"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/oauth/register \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"client_name\": \"Claude\", \"redirect_uris\": [\"https://claude.ai/api/mcp/auth_callback\"]}'"
          }
        ]
      }
    },
    "/oauth/authorize": {
      "get": {
        "operationId": "oauthAuthorize",
        "summary": "Authorization endpoint (PKCE code flow) — hands off to the frontend consent page",
        "description": "Validates `client_id` + `redirect_uri` (400 on failure — never a\nredirect, per spec), then the remaining params (`response_type=code`,\n`code_challenge` + `code_challenge_method=S256`; errors go back to\nthe client via the redirect). On success persists a pending\nauthorization request (10-minute TTL) and 302s the browser to\n`https://app.yieldfabric.com/oauth/authorize?request_id=<uuid>` — the\nyieldfabric-app consent page, where the user signs in with any\nwallet-SDK login flow and approves or denies.\n",
        "tags": [
          "OAuth"
        ],
        "security": [],
        "parameters": [
          {
            "name": "response_type",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "code"
              ]
            }
          },
          {
            "name": "client_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "redirect_uri",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "code_challenge",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "code_challenge_method",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "S256"
              ]
            }
          },
          {
            "name": "state",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "Space-separated"
            }
          },
          {
            "name": "resource",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uri"
            },
            "description": "RFC 8707 resource indicator — the MCP resource server this token is for, e.g. `https://agents.yieldfabric.com/mcp`. Must be an absolute https URI (http loopback allowed for dev) with no fragment; a malformed value is rejected with `error=invalid_target` rather than silently dropped. Persisted on the authorization and stamped into the access token's `aud`, so a token minted for one resource server is rejected by another. Omit for an unbound token (generic audience; the session chain is then the deployment's test chain — see \"Chain from resource\" on `/oauth/token`)."
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to the frontend consent page (success) or back to `redirect_uri` with `error=…` (parameter errors after client validation), including `error=invalid_target` for a malformed `resource`."
          },
          "400": {
            "description": "Unknown client or unregistered redirect_uri (no redirect performed)."
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -i 'https://auth.yieldfabric.com/oauth/authorize?response_type=code&client_id=<uuid>&redirect_uri=https%3A%2F%2Fclaude.ai%2Fapi%2Fmcp%2Fauth_callback&code_challenge=<S256>&code_challenge_method=S256&state=xyz'"
          }
        ]
      }
    },
    "/oauth/authorize/request/{request_id}": {
      "get": {
        "operationId": "oauthGetAuthorizeRequest",
        "summary": "Consent-card metadata for a pending authorization request",
        "description": "Read by the frontend consent page. Only PENDING (unapproved,\nunexpired) requests are visible — a handled request 404s, so a\nrequest id can't be replayed into a second consent.\n",
        "tags": [
          "OAuth"
        ],
        "security": [],
        "parameters": [
          {
            "name": "request_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Consent metadata",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "request_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "client_name": {
                      "type": "string"
                    },
                    "scope": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found, already handled, or expired (OAuth error body)."
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl https://auth.yieldfabric.com/oauth/authorize/request/<request_id>"
          }
        ]
      }
    },
    "/oauth/authorize/approve": {
      "post": {
        "operationId": "oauthApproveAuthorizeRequest",
        "summary": "Approve a pending authorization request (consent page → auth)",
        "description": "Called by the frontend consent page with the logged-in user's\nbearer. Binds the user to the pending request, mints the\nsingle-use authorization code (stored as SHA-256 hash only), and\nreturns the `redirect_url` the browser should navigate to —\n`redirect_uri?code=…&state=…`. One-shot: a second approve (or a\nrace) gets 400.\n",
        "tags": [
          "OAuth"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "request_id"
                ],
                "properties": {
                  "request_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Code minted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "redirect_url": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Request not found, already handled, or expired."
          },
          "401": {
            "description": "Missing/invalid bearer."
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/oauth/authorize/approve \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"request_id\": \"<uuid>\"}'"
          }
        ]
      }
    },
    "/oauth/authorize/deny": {
      "post": {
        "operationId": "oauthDenyAuthorizeRequest",
        "summary": "Deny a pending authorization request",
        "description": "Burns the pending request and returns the\n`redirect_uri?error=access_denied&state=…` redirect target.\n",
        "tags": [
          "OAuth"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "request_id"
                ],
                "properties": {
                  "request_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Denied",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "redirect_url": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Request not found, already handled, or expired."
          },
          "401": {
            "description": "Missing/invalid bearer."
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/oauth/authorize/deny \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"request_id\": \"<uuid>\"}'"
          }
        ]
      }
    },
    "/oauth/token": {
      "post": {
        "operationId": "oauthToken",
        "summary": "Token endpoint — authorization_code (PKCE), refresh_token and token-exchange grants",
        "description": "Form-urlencoded, public client (`token_endpoint_auth_method:\nnone`) for the two connector grants; the token-exchange grant\nauthenticates with a service bearer instead (below).\n\n**`grant_type=authorization_code`** — atomically claims the\nsingle-use code (a replay gets `invalid_grant`), verifies\n`client_id` + `redirect_uri` binding and the PKCE `S256`\nverifier, then mints: a 1-hour MCP-claims access JWT\n(`mcp_agent_id = \"oauth:<client_id>\"`, `session_kind: connector`,\npermissions through the capability/billing gates AND the connector\nstrip — never `CryptoOperations` / `PolicyExecution`,\n`mcp_selected_key: null`, the user's live permissions through the\ncapability/billing gates — role defaults only on a genuinely empty\nset, reads fail closed) and a 30-day single-use refresh token bound\nto the client (`refresh_tokens.oauth_client_id`).\n\n**Chain from resource.** The session chain is derived\nfrom the RFC 8707 `resource`'s host: `pay.test.…` → `153`,\n`pay.live.…` → `151`; anything else (agents, Javier, a dev\nloopback) — or no `resource` at all — is UNBOUND and lands on the\ndeployment's first `auth.modes.test_chain_ids` entry, for every\nrole (such a grant is a knowledge connector that never needed a\nchain; a live-chain connector names the `pay.live…` resource), or\non the system default only when no test chain is configured.\nOverridable per deployment with `AUTH_OAUTH_RESOURCE_CHAINS`\n(`<host-prefix>=<chain_id>,…`; longest prefix wins; replaces the\nbuilt-in table). The chain is persisted on the grant's refresh row\n(`allowed_chains = [chain]`) and echoed in the JWT's\n`allowed_chains`. A session chain that is LIVE — however it was\nchosen: a `pay.live…` resource, or the unbound fallback on a\ndeployment with no test chain — requires the user's live-connector\nopt-in (`users.live_connectors_enabled`, set via\n`PUT /auth/users/me/live-connectors` from a direct session); without it the grant is refused with `400 invalid_target`.\nNo OAuth grant reaches a live chain without it. The opt-in is\nre-read on BOTH grants, so an opted-out user's live grant is\nrefused at its next `refresh_token` rotation — and since the\nrotation claims the row before minting, that grant is over\n(re-authorize after opting back in). A grant persisted on a chain\nthe fallback no longer derives (an agents / Javier grant minted on\na live system default before the test-chain fallback) is likewise\nrefused at rotation with `invalid_grant` — re-authorize; it is\nnever silently moved.\n\n**`grant_type=refresh_token`** — redeemable ONLY here: the same\nrow is rejected at `/auth/refresh` (and a regular session refresh\ntoken is rejected here) — the two credential families are\nisolated. Rotation is atomic (`try_claim_refresh_token`; a\ndouble-spend gets `invalid_grant`) and PRESERVES the original\n`expires_at`, so a connector session lives at most 30 days from\nfirst issue. The chain is re-derived from the persisted resource\nand must still be the one the grant was bound to (`invalid_grant`\notherwise) — a rotation never moves a session to another chain.\n\n**`grant_type=urn:ietf:params:oauth:grant-type:token-exchange`**\n(RFC 8693, on-behalf-of). For YF services only: a\ntool that must call a sister service AS the user exchanges the\nuser's connector bearer for a short-lived token minted FOR that\ncallee, so the caller's bearer never leaves the server and no bare\nservice token loses the user. Client authentication is the calling\nservice's own JWT (`ServiceJwtProvider`) in `Authorization:\nBearer`; anything but a canonical service token is `401\ninvalid_client` (+ `WWW-Authenticate: Bearer`). Parameters:\n`subject_token` (a connector bearer auth minted — OAuth grant or\nconnector API-key session), `subject_token_type` and optional\n`requested_token_type` (both\n`urn:ietf:params:oauth:token-type:access_token`), `audience` (the\ncallee's absolute URL — same rule as `resource`; `invalid_target`\notherwise). Mints a **60 s** access token: `sub` = the subject's\nuser, `aud = [<audience>]` — the callee and NOTHING else (unlike\nRFC 8707 resource binding, which appends to the generic entries):\n`/protected/jwt` admits an `act`-bearing token only with exactly\none callee-shaped audience, and every callee that can receive one\ntests containment of ITS OWN public URL (auth's identity and\nconnections surfaces answer `401` to a token minted for another\ncallee; sister services use\n`yieldfabric_auth_client::exchanged_token_accepted_at`) — so a\ntoken minted for agents cannot be replayed at payments or auth.\n`session_kind`, `allowed_chains`, `default_chain_id`, wallet\nsnapshot and `mcp_*` shape are copied from the subject,\npermissions = the subject's post-gate permissions (never widened;\nthe connector strip is re-applied), `act: {\"sub\":\n\"service:<caller>\"}`. Recorded in the issuance ledger, so it\nvalidates at `/protected/jwt`. Refused with `400 invalid_grant`: an\ninvalid/expired subject; a personal, delegated, service,\npassword-change or signer-bearing MCP (`mcp_signer`) subject (connector bearers only); a subject that is itself an\nexchanged token (no chaining). No `refresh_token` is issued.\n\nUnlike `/auth/mcp/login`, a user with no deployed wallet is fine —\nknowledge/RAG tools need no signer; the token simply carries\n`account_address: null`.\n",
        "tags": [
          "OAuth"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "grant_type"
                ],
                "properties": {
                  "grant_type": {
                    "type": "string",
                    "enum": [
                      "authorization_code",
                      "refresh_token",
                      "urn:ietf:params:oauth:grant-type:token-exchange"
                    ]
                  },
                  "client_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "authorization_code / refresh_token grants — required for both"
                  },
                  "code": {
                    "type": "string",
                    "description": "authorization_code grant"
                  },
                  "redirect_uri": {
                    "type": "string",
                    "description": "authorization_code grant — must match the /oauth/authorize value"
                  },
                  "code_verifier": {
                    "type": "string",
                    "description": "authorization_code grant — PKCE verifier"
                  },
                  "refresh_token": {
                    "type": "string",
                    "description": "refresh_token grant"
                  },
                  "resource": {
                    "type": "string",
                    "format": "uri",
                    "description": "RFC 8707 resource indicator. Optional on both grants, and only ever a RESTATEMENT — the binding comes from the authorization (the code's stored `resource`, or the refresh row's for a rotation), never from this field. A value that disagrees is rejected with `invalid_target`; omitting it still yields the BOUND token. This is what stops a connector authorized for one MCP server refreshing its way into a token for another."
                  },
                  "subject_token": {
                    "type": "string",
                    "description": "token-exchange grant — the connector bearer being exchanged"
                  },
                  "subject_token_type": {
                    "type": "string",
                    "enum": [
                      "urn:ietf:params:oauth:token-type:access_token"
                    ],
                    "description": "token-exchange grant — required"
                  },
                  "requested_token_type": {
                    "type": "string",
                    "enum": [
                      "urn:ietf:params:oauth:token-type:access_token"
                    ],
                    "description": "token-exchange grant — optional; only the access-token type is issued"
                  },
                  "audience": {
                    "type": "string",
                    "format": "uri",
                    "description": "token-exchange grant — the callee's absolute https URI (http loopback allowed), no fragment"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token pair (authorization_code / refresh_token grants), or the RFC\n8693 response for the token-exchange grant — `{access_token,\nissued_token_type, token_type: \"Bearer\", expires_in: 60}`, no\n`refresh_token`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "title": "TokenPair",
                      "required": [
                        "access_token",
                        "token_type",
                        "expires_in",
                        "refresh_token"
                      ],
                      "properties": {
                        "access_token": {
                          "type": "string",
                          "description": "MCP-claims JWT, 1h TTL"
                        },
                        "token_type": {
                          "type": "string",
                          "enum": [
                            "Bearer"
                          ]
                        },
                        "expires_in": {
                          "type": "integer",
                          "description": "3600"
                        },
                        "refresh_token": {
                          "type": "string",
                          "description": "Single-use; rotated on every refresh grant"
                        },
                        "scope": {
                          "type": "string",
                          "description": "Space-joined"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "title": "TokenExchangeResponse",
                      "required": [
                        "access_token",
                        "issued_token_type",
                        "token_type",
                        "expires_in"
                      ],
                      "properties": {
                        "access_token": {
                          "type": "string",
                          "description": "On-behalf-of JWT for the audience, 60 s TTL"
                        },
                        "issued_token_type": {
                          "type": "string",
                          "enum": [
                            "urn:ietf:params:oauth:token-type:access_token"
                          ]
                        },
                        "token_type": {
                          "type": "string",
                          "enum": [
                            "Bearer"
                          ]
                        },
                        "expires_in": {
                          "type": "integer",
                          "description": "60"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "invalid_request / invalid_client / invalid_grant / invalid_target / unsupported_grant_type (OAuth error body)"
          },
          "401": {
            "description": "token-exchange grant only: `invalid_client` — the bearer is not a canonical YF service token (`WWW-Authenticate: Bearer`)"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/oauth/token \\\n  -d grant_type=authorization_code \\\n  -d client_id=<uuid> \\\n  -d code=<code> \\\n  -d redirect_uri=https://claude.ai/api/mcp/auth_callback \\\n  -d code_verifier=<verifier>"
          }
        ]
      }
    },
    "/auth/mcp/login": {
      "post": {
        "operationId": "mcpLogin",
        "summary": "Login from an MCP host with optional impersonation",
        "description": "Mints a JWT carrying MCP-specific claims (`mcp_agent_id`,\n`mcp_session_id`, `mcp_impersonation`, `mcp_selected_key`). Used\nby the Model Context Protocol server to act on behalf of a user\nunder explicit `mcp_*` audit attribution. Unlike ordinary login, this\nsigner-bearing MCP credential requires a real account on auth's enabled\ndefault chain. A walletless user receives structured\n`409 CHAIN_ACCOUNT_REQUIRED` with the exact activation URL; activate,\nthen retry the login.\n\nThe token is stamped `session_kind: mcp_signer` (not `connector`):\nit keeps `CryptoOperations` so the pinned `selected_key` can sign\nthrough the vault, but it is never a direct session (no credential\nmanagement, no delegation mint), is not admitted at `/mcp`, and\ncannot be exchanged on behalf of. See the `session_kind` schema.\n",
        "tags": [
          "Auth"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "password",
                  "agent_id",
                  "entity_type",
                  "entity_id",
                  "selected_key",
                  "scope",
                  "duration_hours"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "password": {
                    "type": "string"
                  },
                  "agent_id": {
                    "type": "string"
                  },
                  "entity_type": {
                    "type": "string",
                    "enum": [
                      "user",
                      "entity",
                      "group"
                    ]
                  },
                  "entity_id": {
                    "type": "string"
                  },
                  "selected_key": {
                    "type": "string",
                    "description": "Key id the MCP host will sign with."
                  },
                  "scope": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Operations the agent is allowed to perform."
                  },
                  "duration_hours": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 24,
                    "description": "Token TTL; clamped to 24h."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "MCP token issued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "access_token",
                    "frontend_token",
                    "token_type",
                    "expires_in",
                    "agent_context"
                  ],
                  "properties": {
                    "access_token": {
                      "type": "string",
                      "description": "Wallet-required MCP agent token."
                    },
                    "frontend_token": {
                      "type": "string",
                      "description": "Service-scoped user JWT for companion frontend operations."
                    },
                    "token_type": {
                      "type": "string",
                      "const": "Bearer"
                    },
                    "expires_in": {
                      "type": "integer"
                    },
                    "agent_context": {
                      "type": "object",
                      "required": [
                        "entity_id",
                        "entity_type",
                        "entity_name",
                        "wallet_address",
                        "key_id",
                        "scope",
                        "expires_at"
                      ],
                      "properties": {
                        "entity_id": {
                          "type": "string"
                        },
                        "entity_type": {
                          "type": "string"
                        },
                        "entity_name": {
                          "type": "string"
                        },
                        "wallet_address": {
                          "type": "string",
                          "pattern": "^0x[0-9a-fA-F]{40}$"
                        },
                        "key_id": {
                          "type": "string"
                        },
                        "scope": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired, or wrong-audience JWT (user or service).\nIncludes invalid API keys on the exchange endpoints, failed wallet\nsignatures on the signin endpoints, and failed Sumsub HMAC on the\nwebhook.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "no-token": {
                    "summary": "Missing Authorization header",
                    "value": {
                      "error": "missing bearer token"
                    }
                  },
                  "invalid-token": {
                    "summary": "JWT signature / structure invalid",
                    "value": {
                      "error": "invalid token"
                    }
                  },
                  "invalid-subject": {
                    "summary": "JWT sub field unparseable",
                    "value": {
                      "error": "invalid token subject"
                    }
                  },
                  "webhook-signature": {
                    "summary": "Sumsub HMAC verification failed",
                    "value": {
                      "error": "invalid webhook signature or payload"
                    }
                  },
                  "averer-reverify": {
                    "summary": "Averer flagged a re-verification requirement",
                    "value": {
                      "error": "averer_reverify_required"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Default chain is disabled or the user has no account on it.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "required": [
                        "error",
                        "code",
                        "chain_id"
                      ],
                      "properties": {
                        "error": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "const": "CHAIN_UNAVAILABLE"
                        },
                        "chain_id": {
                          "type": "string"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "error",
                        "code",
                        "entity_kind",
                        "entity_id",
                        "chain_id"
                      ],
                      "properties": {
                        "error": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "const": "CHAIN_ACCOUNT_REQUIRED"
                        },
                        "entity_kind": {
                          "type": "string",
                          "enum": [
                            "user",
                            "group"
                          ]
                        },
                        "entity_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "activation_url": {
                          "type": "string",
                          "description": "Exact recovery resource when the handler can name it."
                        },
                        "operation": {
                          "type": "string",
                          "description": "Stable operation label for diagnostics; clients route on `code` and the entity/chain fields."
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/mcp/login \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"email\":\"user@example.com\",\"password\":\"<string>\",\"agent_id\":\"agent-1\",\"entity_type\":\"user\",\"entity_id\":\"<user_uuid>\",\"selected_key\":\"<key_id>\",\"scope\":[\"read\"],\"duration_hours\":1}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/mcp/login', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({ email: 'user@example.com', password: '<string>', agent_id: 'agent-1', entity_type: 'user', entity_id: '<user_uuid>', selected_key: '<key_id>', scope: ['read'], duration_hours: 1 }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx\npayload = {'email': 'user@example.com', 'password': '<string>', 'agent_id': 'agent-1', 'entity_type': 'user', 'entity_id': '<user_uuid>', 'selected_key': '<key_id>', 'scope': ['read'], 'duration_hours': 1}\nresp = httpx.post('https://auth.yieldfabric.com/auth/mcp/login', json=payload)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/mcp/generate-token": {
      "post": {
        "operationId": "mcpGenerateToken",
        "summary": "Mint an MCP token from an already-authenticated session",
        "description": "Derives a signer-bearing MCP credential on the validated parent JWT's\nchain. Requires the user's account on that chain. Missing activation is\nstructured `409 CHAIN_ACCOUNT_REQUIRED`; activate using the returned\nentity/chain context, refresh the parent JWT, and retry once. Stamped\n`session_kind: mcp_signer`, like `/auth/mcp/login`.\n",
        "tags": [
          "Auth"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "agent_id",
                  "entity_type",
                  "entity_id",
                  "selected_key",
                  "scope",
                  "duration_hours"
                ],
                "properties": {
                  "agent_id": {
                    "type": "string"
                  },
                  "entity_type": {
                    "type": "string",
                    "enum": [
                      "user",
                      "entity",
                      "group"
                    ]
                  },
                  "entity_id": {
                    "type": "string"
                  },
                  "selected_key": {
                    "type": "string",
                    "description": "Key id the MCP host will sign with."
                  },
                  "scope": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "duration_hours": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 24
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "MCP token issued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "access_token",
                    "frontend_token",
                    "token_type",
                    "expires_in",
                    "agent_context"
                  ],
                  "properties": {
                    "access_token": {
                      "type": "string",
                      "description": "Wallet-required MCP agent token."
                    },
                    "frontend_token": {
                      "type": "string",
                      "description": "Service-scoped user JWT for companion frontend operations."
                    },
                    "token_type": {
                      "type": "string",
                      "const": "Bearer"
                    },
                    "expires_in": {
                      "type": "integer"
                    },
                    "agent_context": {
                      "type": "object",
                      "required": [
                        "entity_id",
                        "entity_type",
                        "entity_name",
                        "wallet_address",
                        "key_id",
                        "scope",
                        "expires_at"
                      ],
                      "properties": {
                        "entity_id": {
                          "type": "string"
                        },
                        "entity_type": {
                          "type": "string"
                        },
                        "entity_name": {
                          "type": "string"
                        },
                        "wallet_address": {
                          "type": "string",
                          "pattern": "^0x[0-9a-fA-F]{40}$"
                        },
                        "key_id": {
                          "type": "string"
                        },
                        "scope": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired, or wrong-audience JWT (user or service).\nIncludes invalid API keys on the exchange endpoints, failed wallet\nsignatures on the signin endpoints, and failed Sumsub HMAC on the\nwebhook.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "no-token": {
                    "summary": "Missing Authorization header",
                    "value": {
                      "error": "missing bearer token"
                    }
                  },
                  "invalid-token": {
                    "summary": "JWT signature / structure invalid",
                    "value": {
                      "error": "invalid token"
                    }
                  },
                  "invalid-subject": {
                    "summary": "JWT sub field unparseable",
                    "value": {
                      "error": "invalid token subject"
                    }
                  },
                  "webhook-signature": {
                    "summary": "Sumsub HMAC verification failed",
                    "value": {
                      "error": "invalid webhook signature or payload"
                    }
                  },
                  "averer-reverify": {
                    "summary": "Averer flagged a re-verification requirement",
                    "value": {
                      "error": "averer_reverify_required"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Parent chain is disabled or the user has no account there.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "required": [
                        "error",
                        "code",
                        "chain_id"
                      ],
                      "properties": {
                        "error": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "const": "CHAIN_UNAVAILABLE"
                        },
                        "chain_id": {
                          "type": "string"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "error",
                        "code",
                        "entity_kind",
                        "entity_id",
                        "chain_id"
                      ],
                      "properties": {
                        "error": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "const": "CHAIN_ACCOUNT_REQUIRED"
                        },
                        "entity_kind": {
                          "type": "string",
                          "enum": [
                            "user",
                            "group"
                          ]
                        },
                        "entity_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "activation_url": {
                          "type": "string",
                          "description": "Exact recovery resource when the handler can name it."
                        },
                        "operation": {
                          "type": "string",
                          "description": "Stable operation label for diagnostics; clients route on `code` and the entity/chain fields."
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/mcp/generate-token \\\n  -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' \\\n  -d '{\"agent_id\":\"agent-1\",\"entity_type\":\"user\",\"entity_id\":\"<user_uuid>\",\"selected_key\":\"<key_id>\",\"scope\":[\"read\"],\"duration_hours\":1}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/mcp/generate-token', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({ agent_id: 'agent-1', entity_type: 'user', entity_id: '<user_uuid>', selected_key: '<key_id>', scope: ['read'], duration_hours: 1 }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\npayload = {'agent_id': 'agent-1', 'entity_type': 'user', 'entity_id': '<user_uuid>', 'selected_key': '<key_id>', 'scope': ['read'], 'duration_hours': 1}\nresp = httpx.post('https://auth.yieldfabric.com/auth/mcp/generate-token', headers=headers, json=payload)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/mcp/login": {
      "get": {
        "operationId": "mcpLoginForm",
        "summary": "HTML login form for MCP browser-based flows",
        "description": "Serves an HTML page; only used by browser-MCP integrations. Not part of the machine-readable surface.",
        "tags": [
          "Auth"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "HTML form",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/mcp/login"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/mcp/login', {\n  method: 'GET',\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nurl = 'https://auth.yieldfabric.com/mcp/login'\nresp = httpx.get(url)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/signature": {
      "post": {
        "operationId": "authenticateSignature",
        "summary": "Wallet-signature authentication (legacy path)",
        "description": "Verifies a wallet signature against the registered public key\nand returns a user JWT. The newer `/auth/signature/signin` path\nis preferred — it resolves the key from the registered set\nacross all sign-in methods, so a single signature works\nregardless of which onboarding flow created the user.\n",
        "tags": [
          "Signatures"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "public_key",
                  "signature",
                  "message",
                  "timestamp"
                ],
                "properties": {
                  "public_key": {
                    "type": "string"
                  },
                  "signature": {
                    "type": "string"
                  },
                  "message": {
                    "type": "string"
                  },
                  "timestamp": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signature verified, token issued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "token",
                    "refresh_token",
                    "token_type",
                    "expires_in",
                    "user"
                  ],
                  "description": "Returned by `POST /auth/login` and `POST /auth/login/with-services`.",
                  "properties": {
                    "token": {
                      "type": "string",
                      "description": "Short-lived access token (default 15 min)."
                    },
                    "refresh_token": {
                      "type": "string",
                      "description": "Long-lived refresh token (30 days, single-use, rotated on each refresh)."
                    },
                    "token_type": {
                      "type": "string",
                      "const": "Bearer"
                    },
                    "expires_in": {
                      "type": "integer",
                      "description": "Access-token lifetime in seconds."
                    },
                    "user": {
                      "type": "object",
                      "required": [
                        "id",
                        "email",
                        "role",
                        "is_active"
                      ],
                      "description": "User record shape returned by `/auth/users/me` and friends.\n`password_hash` is always returned as an empty string (never\nexposed). See `yieldfabric-auth/src/handlers/types.rs::UserInfo`.\n",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "email": {
                          "type": "string",
                          "format": "email"
                        },
                        "password_hash": {
                          "type": "string",
                          "description": "Always empty in responses."
                        },
                        "role": {
                          "type": "string",
                          "description": "Built-in role assigned to every user. Role + Permission grants make\nup the RBAC matrix; see `auth.roles` in `config/system.yaml` for\nrole overrides. SuperAdmin always has every permission.\n",
                          "enum": [
                            "SuperAdmin",
                            "Admin",
                            "Manager",
                            "Operator",
                            "User",
                            "Viewer",
                            "ApiClient"
                          ]
                        },
                        "is_active": {
                          "type": "boolean"
                        },
                        "must_change_password": {
                          "type": "boolean"
                        },
                        "password_changed_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "failed_login_count": {
                          "type": "integer"
                        },
                        "last_failed_login": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "is_external_signer": {
                          "type": "boolean",
                          "description": "Derived. **True** when the user has no active `KeyType: Signing`\nkeypair with `encrypted_private_key` — i.e. they sign exclusively\nvia an external wallet (Averer / Dynamic / MetaMask /\nWebAuthn passkey). The frontend trusts this flag verbatim and\nsets `requireManualSignature: true` on every payment mutation\nwhen it's true, so the backend routes the message into the\nmanual-sign drawer instead of attempting server-side signing.\nRecomputed on every call from a case-insensitive\n`LOWER(key_type) = 'signing'` SQL filter. See the architecture\ndoc for the full chain and the cache-invalidation gotcha.\n"
                        },
                        "live_connectors_enabled": {
                          "type": "boolean",
                          "description": "The person's LIVE-connector opt-in (`users.live_connectors_enabled`,\nagent surface spec §5.4). While false, none of their connector\ncredentials can be generated for, minted on, or refreshed onto a\nlive chain. Set via `PUT /auth/users/me/live-connectors`\n(direct session + live verification floor). Returned by\n`GET /auth/users/me` only; best-effort read.\n"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired, or wrong-audience JWT (user or service).\nIncludes invalid API keys on the exchange endpoints, failed wallet\nsignatures on the signin endpoints, and failed Sumsub HMAC on the\nwebhook.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "no-token": {
                    "summary": "Missing Authorization header",
                    "value": {
                      "error": "missing bearer token"
                    }
                  },
                  "invalid-token": {
                    "summary": "JWT signature / structure invalid",
                    "value": {
                      "error": "invalid token"
                    }
                  },
                  "invalid-subject": {
                    "summary": "JWT sub field unparseable",
                    "value": {
                      "error": "invalid token subject"
                    }
                  },
                  "webhook-signature": {
                    "summary": "Sumsub HMAC verification failed",
                    "value": {
                      "error": "invalid webhook signature or payload"
                    }
                  },
                  "averer-reverify": {
                    "summary": "Averer flagged a re-verification requirement",
                    "value": {
                      "error": "averer_reverify_required"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/signature \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"public_key\": \"<string>\",\n  \"signature\": \"<string>\",\n  \"message\": \"<string>\",\n  \"timestamp\": 1\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/signature', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"public_key\": \"<string>\",\n    \"signature\": \"<string>\",\n    \"message\": \"<string>\",\n    \"timestamp\": 1\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/signature'\nresp = httpx.post(url, headers=headers, json={\"public_key\": \"<string>\", \"signature\": \"<string>\", \"message\": \"<string>\", \"timestamp\": 1})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/signature/nonce": {
      "get": {
        "operationId": "issueSignatureNonce",
        "summary": "Issue a one-time nonce for signature sign-in",
        "description": "Returns a short-lived nonce the wallet should sign. The same\nnonce is required by `/auth/signature/signin` and the\n`/auth/{provider}/exchange` flows to prevent signature replay.\n",
        "tags": [
          "Signatures"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Nonce issued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "nonce",
                    "expires_at"
                  ],
                  "properties": {
                    "nonce": {
                      "type": "string"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/signature/nonce"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/signature/nonce', {\n  method: 'GET',\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nurl = 'https://auth.yieldfabric.com/auth/signature/nonce'\nresp = httpx.get(url)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/signature/signin": {
      "post": {
        "operationId": "signatureSignin",
        "summary": "Sign-in via a registered wallet signature",
        "description": "Sign-in-only path: never creates a user. Resolves the public key\nfrom the registered set across all sign-in methods. Pair with\n`GET /auth/signature/nonce`.\n",
        "tags": [
          "Signatures"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "public_key",
                  "signature",
                  "message",
                  "nonce"
                ],
                "properties": {
                  "public_key": {
                    "type": "string",
                    "description": "Hex-encoded public key (with or without `0x` prefix)."
                  },
                  "signature": {
                    "type": "string",
                    "description": "EIP-191 personal_sign signature over `message`."
                  },
                  "message": {
                    "type": "string",
                    "description": "The exact string the wallet signed. Must contain the `nonce`."
                  },
                  "nonce": {
                    "type": "string",
                    "description": "The nonce returned by `GET /auth/signature/nonce`; verified and consumed once."
                  },
                  "chain_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Optional. When supplied, the signature is also validated against this chain id."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sign-in successful",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "token",
                    "refresh_token",
                    "token_type",
                    "expires_in",
                    "user"
                  ],
                  "description": "Returned by `POST /auth/login` and `POST /auth/login/with-services`.",
                  "properties": {
                    "token": {
                      "type": "string",
                      "description": "Short-lived access token (default 15 min)."
                    },
                    "refresh_token": {
                      "type": "string",
                      "description": "Long-lived refresh token (30 days, single-use, rotated on each refresh)."
                    },
                    "token_type": {
                      "type": "string",
                      "const": "Bearer"
                    },
                    "expires_in": {
                      "type": "integer",
                      "description": "Access-token lifetime in seconds."
                    },
                    "user": {
                      "type": "object",
                      "required": [
                        "id",
                        "email",
                        "role",
                        "is_active"
                      ],
                      "description": "User record shape returned by `/auth/users/me` and friends.\n`password_hash` is always returned as an empty string (never\nexposed). See `yieldfabric-auth/src/handlers/types.rs::UserInfo`.\n",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "email": {
                          "type": "string",
                          "format": "email"
                        },
                        "password_hash": {
                          "type": "string",
                          "description": "Always empty in responses."
                        },
                        "role": {
                          "type": "string",
                          "description": "Built-in role assigned to every user. Role + Permission grants make\nup the RBAC matrix; see `auth.roles` in `config/system.yaml` for\nrole overrides. SuperAdmin always has every permission.\n",
                          "enum": [
                            "SuperAdmin",
                            "Admin",
                            "Manager",
                            "Operator",
                            "User",
                            "Viewer",
                            "ApiClient"
                          ]
                        },
                        "is_active": {
                          "type": "boolean"
                        },
                        "must_change_password": {
                          "type": "boolean"
                        },
                        "password_changed_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "failed_login_count": {
                          "type": "integer"
                        },
                        "last_failed_login": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "is_external_signer": {
                          "type": "boolean",
                          "description": "Derived. **True** when the user has no active `KeyType: Signing`\nkeypair with `encrypted_private_key` — i.e. they sign exclusively\nvia an external wallet (Averer / Dynamic / MetaMask /\nWebAuthn passkey). The frontend trusts this flag verbatim and\nsets `requireManualSignature: true` on every payment mutation\nwhen it's true, so the backend routes the message into the\nmanual-sign drawer instead of attempting server-side signing.\nRecomputed on every call from a case-insensitive\n`LOWER(key_type) = 'signing'` SQL filter. See the architecture\ndoc for the full chain and the cache-invalidation gotcha.\n"
                        },
                        "live_connectors_enabled": {
                          "type": "boolean",
                          "description": "The person's LIVE-connector opt-in (`users.live_connectors_enabled`,\nagent surface spec §5.4). While false, none of their connector\ncredentials can be generated for, minted on, or refreshed onto a\nlive chain. Set via `PUT /auth/users/me/live-connectors`\n(direct session + live verification floor). Returned by\n`GET /auth/users/me` only; best-effort read.\n"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired, or wrong-audience JWT (user or service).\nIncludes invalid API keys on the exchange endpoints, failed wallet\nsignatures on the signin endpoints, and failed Sumsub HMAC on the\nwebhook.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "no-token": {
                    "summary": "Missing Authorization header",
                    "value": {
                      "error": "missing bearer token"
                    }
                  },
                  "invalid-token": {
                    "summary": "JWT signature / structure invalid",
                    "value": {
                      "error": "invalid token"
                    }
                  },
                  "invalid-subject": {
                    "summary": "JWT sub field unparseable",
                    "value": {
                      "error": "invalid token subject"
                    }
                  },
                  "webhook-signature": {
                    "summary": "Sumsub HMAC verification failed",
                    "value": {
                      "error": "invalid webhook signature or payload"
                    }
                  },
                  "averer-reverify": {
                    "summary": "Averer flagged a re-verification requirement",
                    "value": {
                      "error": "averer_reverify_required"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (user, group, key, delegation token, wallet, provider).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "keypair": {
                    "summary": "Keypair UUID not found",
                    "value": {
                      "error": "keypair not found"
                    }
                  },
                  "kyc-provider": {
                    "summary": "Requested KYC provider not enabled in yaml",
                    "value": {
                      "error": "kyc provider not enabled"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/signature/signin \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"public_key\": \"<string>\",\n  \"signature\": \"<string>\",\n  \"message\": \"<string>\",\n  \"nonce\": \"<string>\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/signature/signin', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"public_key\": \"<string>\",\n    \"signature\": \"<string>\",\n    \"message\": \"<string>\",\n    \"nonce\": \"<string>\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/signature/signin'\nresp = httpx.post(url, headers=headers, json={\"public_key\": \"<string>\", \"signature\": \"<string>\", \"message\": \"<string>\", \"nonce\": \"<string>\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/webauthn/challenge": {
      "post": {
        "operationId": "webauthnChallenge",
        "summary": "Issue a WebAuthn challenge",
        "description": "Returns the challenge + relying-party metadata the browser's\nWebAuthn API consumes to produce an authenticator assertion.\n",
        "tags": [
          "Auth"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Challenge issued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "challenge",
                    "rp_id"
                  ],
                  "properties": {
                    "challenge": {
                      "type": "string"
                    },
                    "rp_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/webauthn/challenge"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/webauthn/challenge', {\n  method: 'POST',\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nurl = 'https://auth.yieldfabric.com/auth/webauthn/challenge'\nresp = httpx.post(url)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/webauthn/exchange": {
      "post": {
        "operationId": "webauthnExchange",
        "summary": "Exchange a WebAuthn assertion for a JWT",
        "description": "Verifies the assertion (clientDataJSON + authenticatorData + DER\nsignature) and returns a user JWT. find-or-create user pipeline\ndownstream (same as `/auth/{provider}/exchange`).\n",
        "tags": [
          "Auth"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "WebAuthn assertion payload (see browser WebAuthn API spec)."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Assertion verified, token issued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "token",
                    "refresh_token",
                    "token_type",
                    "expires_in",
                    "user"
                  ],
                  "description": "Returned by `POST /auth/login` and `POST /auth/login/with-services`.",
                  "properties": {
                    "token": {
                      "type": "string",
                      "description": "Short-lived access token (default 15 min)."
                    },
                    "refresh_token": {
                      "type": "string",
                      "description": "Long-lived refresh token (30 days, single-use, rotated on each refresh)."
                    },
                    "token_type": {
                      "type": "string",
                      "const": "Bearer"
                    },
                    "expires_in": {
                      "type": "integer",
                      "description": "Access-token lifetime in seconds."
                    },
                    "user": {
                      "type": "object",
                      "required": [
                        "id",
                        "email",
                        "role",
                        "is_active"
                      ],
                      "description": "User record shape returned by `/auth/users/me` and friends.\n`password_hash` is always returned as an empty string (never\nexposed). See `yieldfabric-auth/src/handlers/types.rs::UserInfo`.\n",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "email": {
                          "type": "string",
                          "format": "email"
                        },
                        "password_hash": {
                          "type": "string",
                          "description": "Always empty in responses."
                        },
                        "role": {
                          "type": "string",
                          "description": "Built-in role assigned to every user. Role + Permission grants make\nup the RBAC matrix; see `auth.roles` in `config/system.yaml` for\nrole overrides. SuperAdmin always has every permission.\n",
                          "enum": [
                            "SuperAdmin",
                            "Admin",
                            "Manager",
                            "Operator",
                            "User",
                            "Viewer",
                            "ApiClient"
                          ]
                        },
                        "is_active": {
                          "type": "boolean"
                        },
                        "must_change_password": {
                          "type": "boolean"
                        },
                        "password_changed_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "failed_login_count": {
                          "type": "integer"
                        },
                        "last_failed_login": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "is_external_signer": {
                          "type": "boolean",
                          "description": "Derived. **True** when the user has no active `KeyType: Signing`\nkeypair with `encrypted_private_key` — i.e. they sign exclusively\nvia an external wallet (Averer / Dynamic / MetaMask /\nWebAuthn passkey). The frontend trusts this flag verbatim and\nsets `requireManualSignature: true` on every payment mutation\nwhen it's true, so the backend routes the message into the\nmanual-sign drawer instead of attempting server-side signing.\nRecomputed on every call from a case-insensitive\n`LOWER(key_type) = 'signing'` SQL filter. See the architecture\ndoc for the full chain and the cache-invalidation gotcha.\n"
                        },
                        "live_connectors_enabled": {
                          "type": "boolean",
                          "description": "The person's LIVE-connector opt-in (`users.live_connectors_enabled`,\nagent surface spec §5.4). While false, none of their connector\ncredentials can be generated for, minted on, or refreshed onto a\nlive chain. Set via `PUT /auth/users/me/live-connectors`\n(direct session + live verification floor). Returned by\n`GET /auth/users/me` only; best-effort read.\n"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired, or wrong-audience JWT (user or service).\nIncludes invalid API keys on the exchange endpoints, failed wallet\nsignatures on the signin endpoints, and failed Sumsub HMAC on the\nwebhook.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "no-token": {
                    "summary": "Missing Authorization header",
                    "value": {
                      "error": "missing bearer token"
                    }
                  },
                  "invalid-token": {
                    "summary": "JWT signature / structure invalid",
                    "value": {
                      "error": "invalid token"
                    }
                  },
                  "invalid-subject": {
                    "summary": "JWT sub field unparseable",
                    "value": {
                      "error": "invalid token subject"
                    }
                  },
                  "webhook-signature": {
                    "summary": "Sumsub HMAC verification failed",
                    "value": {
                      "error": "invalid webhook signature or payload"
                    }
                  },
                  "averer-reverify": {
                    "summary": "Averer flagged a re-verification requirement",
                    "value": {
                      "error": "averer_reverify_required"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/webauthn/exchange \\\n  -H 'Content-Type: application/json' \\\n  -d '{}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/webauthn/exchange', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({}),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/webauthn/exchange'\nresp = httpx.post(url, headers=headers, json={})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/{provider}/exchange": {
      "post": {
        "operationId": "providerExchange",
        "summary": "Sign-in / sign-up via an identity provider",
        "description": "Public sign-in/up — verifies the provider-signed payload and\nissues a user JWT, creating a new `User` record when no\nmatching identity link exists. Per-provider request shape:\n\n- **averer** — `{ idToken, nonce }` (Averer JWT + replay nonce).\n- **metamask** — `{ address, signature, message, chainId }`\n  (EIP-191 signature over the auth-issued nonce).\n- **dynamic** — `{ jwt }` (Dynamic.xyz JWT — Phase 2; not\n  enabled by default).\n- **email** — `{ email, otp }` (one-time code emailed by the\n  delivery hook).\n\n**Available providers** are returned by\n`GET /auth/providers/config` — call that endpoint to discover\nwhich providers this deployment has enabled (configured by the\noperator).\n",
        "tags": [
          "Identity providers"
        ],
        "security": [],
        "parameters": [
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "averer",
                "metamask",
                "dynamic",
                "email",
                "webauthn"
              ]
            },
            "description": "Identity-provider id. The full enum is shown above; the set of\nproviders actually mounted depends on this deployment's operator\nconfig. Clients should call `GET /auth/providers/config` at boot\nto discover which providers are available.\n"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Provider-specific payload."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Exchange successful",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "token",
                    "refresh_token",
                    "token_type",
                    "expires_in",
                    "user"
                  ],
                  "description": "Returned by `POST /auth/login` and `POST /auth/login/with-services`.",
                  "properties": {
                    "token": {
                      "type": "string",
                      "description": "Short-lived access token (default 15 min)."
                    },
                    "refresh_token": {
                      "type": "string",
                      "description": "Long-lived refresh token (30 days, single-use, rotated on each refresh)."
                    },
                    "token_type": {
                      "type": "string",
                      "const": "Bearer"
                    },
                    "expires_in": {
                      "type": "integer",
                      "description": "Access-token lifetime in seconds."
                    },
                    "user": {
                      "type": "object",
                      "required": [
                        "id",
                        "email",
                        "role",
                        "is_active"
                      ],
                      "description": "User record shape returned by `/auth/users/me` and friends.\n`password_hash` is always returned as an empty string (never\nexposed). See `yieldfabric-auth/src/handlers/types.rs::UserInfo`.\n",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "email": {
                          "type": "string",
                          "format": "email"
                        },
                        "password_hash": {
                          "type": "string",
                          "description": "Always empty in responses."
                        },
                        "role": {
                          "type": "string",
                          "description": "Built-in role assigned to every user. Role + Permission grants make\nup the RBAC matrix; see `auth.roles` in `config/system.yaml` for\nrole overrides. SuperAdmin always has every permission.\n",
                          "enum": [
                            "SuperAdmin",
                            "Admin",
                            "Manager",
                            "Operator",
                            "User",
                            "Viewer",
                            "ApiClient"
                          ]
                        },
                        "is_active": {
                          "type": "boolean"
                        },
                        "must_change_password": {
                          "type": "boolean"
                        },
                        "password_changed_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "failed_login_count": {
                          "type": "integer"
                        },
                        "last_failed_login": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "is_external_signer": {
                          "type": "boolean",
                          "description": "Derived. **True** when the user has no active `KeyType: Signing`\nkeypair with `encrypted_private_key` — i.e. they sign exclusively\nvia an external wallet (Averer / Dynamic / MetaMask /\nWebAuthn passkey). The frontend trusts this flag verbatim and\nsets `requireManualSignature: true` on every payment mutation\nwhen it's true, so the backend routes the message into the\nmanual-sign drawer instead of attempting server-side signing.\nRecomputed on every call from a case-insensitive\n`LOWER(key_type) = 'signing'` SQL filter. See the architecture\ndoc for the full chain and the cache-invalidation gotcha.\n"
                        },
                        "live_connectors_enabled": {
                          "type": "boolean",
                          "description": "The person's LIVE-connector opt-in (`users.live_connectors_enabled`,\nagent surface spec §5.4). While false, none of their connector\ncredentials can be generated for, minted on, or refreshed onto a\nlive chain. Set via `PUT /auth/users/me/live-connectors`\n(direct session + live verification floor). Returned by\n`GET /auth/users/me` only; best-effort read.\n"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Malformed request, invalid field, or business-rule violation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "invalid-email": {
                    "summary": "Email format check failed",
                    "value": {
                      "error": "invalid email format"
                    }
                  },
                  "invalid-uuid": {
                    "summary": "Path parameter not a UUID",
                    "value": {
                      "error": "user_id must be a UUID"
                    }
                  },
                  "lockdown-state": {
                    "summary": "Lockdown flow state mismatch",
                    "value": {
                      "error": "no_active_lockdown"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired, or wrong-audience JWT (user or service).\nIncludes invalid API keys on the exchange endpoints, failed wallet\nsignatures on the signin endpoints, and failed Sumsub HMAC on the\nwebhook.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "no-token": {
                    "summary": "Missing Authorization header",
                    "value": {
                      "error": "missing bearer token"
                    }
                  },
                  "invalid-token": {
                    "summary": "JWT signature / structure invalid",
                    "value": {
                      "error": "invalid token"
                    }
                  },
                  "invalid-subject": {
                    "summary": "JWT sub field unparseable",
                    "value": {
                      "error": "invalid token subject"
                    }
                  },
                  "webhook-signature": {
                    "summary": "Sumsub HMAC verification failed",
                    "value": {
                      "error": "invalid webhook signature or payload"
                    }
                  },
                  "averer-reverify": {
                    "summary": "Averer flagged a re-verification requirement",
                    "value": {
                      "error": "averer_reverify_required"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/{provider}/exchange \\\n  -H 'Content-Type: application/json' \\\n  -d '{}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/{provider}/exchange', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({}),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/{provider}/exchange'\nresp = httpx.post(url, headers=headers, json={})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/{provider}/link": {
      "post": {
        "operationId": "providerLink",
        "summary": "Link an additional provider identity to the current user",
        "tags": [
          "Identity providers"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "averer",
                "metamask",
                "dynamic",
                "email",
                "webauthn"
              ]
            },
            "description": "Identity-provider id. The full enum is shown above; the set of\nproviders actually mounted depends on this deployment's operator\nconfig. Clients should call `GET /auth/providers/config` at boot\nto discover which providers are available.\n"
          },
          {
            "in": "query",
            "name": "register_on_chain",
            "schema": {
              "type": "boolean",
              "default": true
            },
            "description": "When true, register the verified provider key as an owner of the selected-chain account."
          },
          {
            "in": "query",
            "name": "chain_id",
            "schema": {
              "type": "string"
            },
            "description": "Required when `register_on_chain=true`; must equal the validated JWT chain."
          },
          {
            "in": "query",
            "name": "wallet_address",
            "schema": {
              "type": "string"
            },
            "description": "Optional assertion that must equal the user's default account address on that chain."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Provider-specific verification payload (same shape as `/auth/{provider}/exchange`)."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Provider linked; includes the durable owner-registration state when requested.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "linked",
                    "keypair_id",
                    "on_chain_registration_queued",
                    "email_updated"
                  ],
                  "properties": {
                    "linked": {
                      "type": "boolean",
                      "const": true
                    },
                    "keypair_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "on_chain_registration_queued": {
                      "type": "boolean"
                    },
                    "owner_registration_status": {
                      "type": "string",
                      "enum": [
                        "confirmed",
                        "pending_signature",
                        "retry_required",
                        "failed"
                      ]
                    },
                    "owner_registration_message_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "email_updated": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Malformed request, invalid field, or business-rule violation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "invalid-email": {
                    "summary": "Email format check failed",
                    "value": {
                      "error": "invalid email format"
                    }
                  },
                  "invalid-uuid": {
                    "summary": "Path parameter not a UUID",
                    "value": {
                      "error": "user_id must be a UUID"
                    }
                  },
                  "lockdown-state": {
                    "summary": "Lockdown flow state mismatch",
                    "value": {
                      "error": "no_active_lockdown"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Email/provider conflict, or the on-chain registration target is not activated. The account gate runs before nonce consumption or provider-link persistence.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "required": [
                        "error",
                        "code",
                        "entity_kind",
                        "entity_id",
                        "chain_id"
                      ],
                      "properties": {
                        "error": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "const": "CHAIN_ACCOUNT_REQUIRED"
                        },
                        "entity_kind": {
                          "type": "string",
                          "enum": [
                            "user",
                            "group"
                          ]
                        },
                        "entity_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "activation_url": {
                          "type": "string",
                          "description": "Exact recovery resource when the handler can name it."
                        },
                        "operation": {
                          "type": "string",
                          "description": "Stable operation label for diagnostics; clients route on `code` and the entity/chain fields."
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "error"
                      ],
                      "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                      "properties": {
                        "error": {
                          "type": "string",
                          "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/{provider}/link \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/{provider}/link', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({}),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/{provider}/link'\nresp = httpx.post(url, headers=headers, json={})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/{provider}/unlink": {
      "post": {
        "operationId": "providerUnlink",
        "summary": "Unlink a provider identity from the current user",
        "tags": [
          "Identity providers"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "averer",
                "metamask",
                "dynamic",
                "email",
                "webauthn"
              ]
            },
            "description": "Identity-provider id. The full enum is shown above; the set of\nproviders actually mounted depends on this deployment's operator\nconfig. Clients should call `GET /auth/providers/config` at boot\nto discover which providers are available.\n"
          }
        ],
        "responses": {
          "204": {
            "description": "Provider unlinked"
          },
          "404": {
            "description": "Resource not found (user, group, key, delegation token, wallet, provider).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "keypair": {
                    "summary": "Keypair UUID not found",
                    "value": {
                      "error": "keypair not found"
                    }
                  },
                  "kyc-provider": {
                    "summary": "Requested KYC provider not enabled in yaml",
                    "value": {
                      "error": "kyc provider not enabled"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/{provider}/unlink \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/{provider}/unlink', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/{provider}/unlink'\nresp = httpx.post(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/users/me/login-keys": {
      "get": {
        "operationId": "listMyLoginKeys",
        "summary": "List public keys that can sign you in",
        "description": "Returns the registered public keys + active provider links the\ncurrent user can sign in with. Used by the wallet UX to show\n\"this account can sign in with these keys\".\n",
        "tags": [
          "Auth"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Login keys",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "required": [
                      "id",
                      "source",
                      "provider",
                      "accounts",
                      "created_at"
                    ],
                    "properties": {
                      "id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "source": {
                        "type": "string",
                        "enum": [
                          "signature",
                          "keypair"
                        ]
                      },
                      "provider": {
                        "type": "string"
                      },
                      "key_name": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "wallet_address": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "label": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "accounts": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "required": [
                            "chain_id",
                            "wallet_address",
                            "registered_at"
                          ],
                          "properties": {
                            "chain_id": {
                              "type": "string"
                            },
                            "wallet_address": {
                              "type": "string"
                            },
                            "registered_at": {
                              "type": "string",
                              "format": "date-time"
                            }
                          }
                        }
                      },
                      "created_at": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "last_used_at": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "format": "date-time"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/users/me/login-keys \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/users/me/login-keys', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/users/me/login-keys'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/providers/config": {
      "get": {
        "operationId": "getProvidersConfig",
        "summary": "Public identity-provider chooser config",
        "description": "Returns the auth-method registry the frontend reads at boot.\nContains only public fields (Averer `configId`, MetaMask\nallowed chain ids, primary provider) — secrets like API\nkeys are never surfaced. The exact set of providers depends on\nwhat the operator has enabled in this deployment.\n\nWith `?platform=<group_id>` the response is the PLATFORM-MERGED\nview: the same `{auth,kyc}` shape narrowed by that room's\nonboarding policy (sign-in subset/primary, preferred KYC flow)\nand its verification profile (only vendors that can SATISFY the\nprofile are listed, `required_for_completion` forced true), plus\nan additive `platform` block\n(`{group_id, verification_profile, verification_profile_misconfigured,\nrequire_email, ux_steps}`). Lists only ever NARROW the\ndeployment view. Unknown platform / read failures degrade to the\ndeployment view — enforcement lives at token mint (the platform\ncapability-gate overlay), never in this read. See\n`docs/architecture/platform-onboarding-policies.md`.\n",
        "tags": [
          "Identity providers"
        ],
        "security": [],
        "parameters": [
          {
            "name": "platform",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Platform room (auth group) id — serves that room's merged onboarding view."
          }
        ],
        "responses": {
          "200": {
            "description": "Provider registry",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "enabled",
                    "primary"
                  ],
                  "properties": {
                    "enabled": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "primary": {
                      "type": "string"
                    },
                    "averer": {
                      "type": "object",
                      "properties": {
                        "config_id": {
                          "type": "string"
                        },
                        "app_name": {
                          "type": "string"
                        }
                      }
                    },
                    "metamask": {
                      "type": "object",
                      "properties": {
                        "chains_allowed": {
                          "type": "array",
                          "items": {
                            "type": "integer"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/providers/config"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/providers/config', {\n  method: 'GET',\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nurl = 'https://auth.yieldfabric.com/auth/providers/config'\nresp = httpx.get(url)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/chain-config": {
      "get": {
        "operationId": "getChainConfig",
        "summary": "Public chain-config snapshot for the frontend",
        "description": "Returns `default_chain_id` + a per-chain map of public on-chain\nconstants the frontend needs (token addresses, identity factory,\netc.). Forwarded from payments — auth itself holds no chain\nconfig.\n",
        "tags": [
          "Identity providers"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Chain config",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "default_chain_id",
                    "chains"
                  ],
                  "properties": {
                    "default_chain_id": {
                      "type": "string"
                    },
                    "chains": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "object",
                        "description": "Per-chain public addresses."
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/chain-config"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/chain-config', {\n  method: 'GET',\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nurl = 'https://auth.yieldfabric.com/auth/chain-config'\nresp = httpx.get(url)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/change-password": {
      "post": {
        "operationId": "changePassword",
        "summary": "Change the current user's password",
        "description": "Works with a regular access token AND with a\n`password_change_only` token issued after `must_change_password`\n= true (e.g. forced rotation). Old password is required as a\nproof-of-knowledge.\n",
        "tags": [
          "Users"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "old_password",
                  "new_password"
                ],
                "properties": {
                  "old_password": {
                    "type": "string"
                  },
                  "new_password": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Password changed"
          },
          "400": {
            "description": "Malformed request, invalid field, or business-rule violation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "invalid-email": {
                    "summary": "Email format check failed",
                    "value": {
                      "error": "invalid email format"
                    }
                  },
                  "invalid-uuid": {
                    "summary": "Path parameter not a UUID",
                    "value": {
                      "error": "user_id must be a UUID"
                    }
                  },
                  "lockdown-state": {
                    "summary": "Lockdown flow state mismatch",
                    "value": {
                      "error": "no_active_lockdown"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired, or wrong-audience JWT (user or service).\nIncludes invalid API keys on the exchange endpoints, failed wallet\nsignatures on the signin endpoints, and failed Sumsub HMAC on the\nwebhook.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "no-token": {
                    "summary": "Missing Authorization header",
                    "value": {
                      "error": "missing bearer token"
                    }
                  },
                  "invalid-token": {
                    "summary": "JWT signature / structure invalid",
                    "value": {
                      "error": "invalid token"
                    }
                  },
                  "invalid-subject": {
                    "summary": "JWT sub field unparseable",
                    "value": {
                      "error": "invalid token subject"
                    }
                  },
                  "webhook-signature": {
                    "summary": "Sumsub HMAC verification failed",
                    "value": {
                      "error": "invalid webhook signature or payload"
                    }
                  },
                  "averer-reverify": {
                    "summary": "Averer flagged a re-verification requirement",
                    "value": {
                      "error": "averer_reverify_required"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/change-password \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"old_password\": \"<string>\",\n  \"new_password\": \"<string>\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/change-password', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"old_password\": \"<string>\",\n    \"new_password\": \"<string>\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/change-password'\nresp = httpx.post(url, headers=headers, json={\"old_password\": \"<string>\", \"new_password\": \"<string>\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/users": {
      "post": {
        "operationId": "createUser",
        "summary": "Create a new user",
        "description": "Public user-creation endpoint. Atomically creates the user, default key\nmaterial, role permissions, and identity entity. It makes no payments\ncall and creates no wallet; per-chain accounts are activated explicitly\nonly when a wallet-required operation is requested.\n",
        "tags": [
          "Users"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "password"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "password": {
                    "type": "string"
                  },
                  "role": {
                    "type": "string",
                    "description": "Built-in role assigned to every user. Role + Permission grants make\nup the RBAC matrix; see `auth.roles` in `config/system.yaml` for\nrole overrides. SuperAdmin always has every permission.\n",
                    "enum": [
                      "SuperAdmin",
                      "Admin",
                      "Manager",
                      "Operator",
                      "User",
                      "Viewer",
                      "ApiClient"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "User created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "user"
                  ],
                  "properties": {
                    "user": {
                      "type": "object",
                      "required": [
                        "id",
                        "email",
                        "role",
                        "is_active"
                      ],
                      "description": "User record shape returned by `/auth/users/me` and friends.\n`password_hash` is always returned as an empty string (never\nexposed). See `yieldfabric-auth/src/handlers/types.rs::UserInfo`.\n",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "email": {
                          "type": "string",
                          "format": "email"
                        },
                        "password_hash": {
                          "type": "string",
                          "description": "Always empty in responses."
                        },
                        "role": {
                          "type": "string",
                          "description": "Built-in role assigned to every user. Role + Permission grants make\nup the RBAC matrix; see `auth.roles` in `config/system.yaml` for\nrole overrides. SuperAdmin always has every permission.\n",
                          "enum": [
                            "SuperAdmin",
                            "Admin",
                            "Manager",
                            "Operator",
                            "User",
                            "Viewer",
                            "ApiClient"
                          ]
                        },
                        "is_active": {
                          "type": "boolean"
                        },
                        "must_change_password": {
                          "type": "boolean"
                        },
                        "password_changed_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "failed_login_count": {
                          "type": "integer"
                        },
                        "last_failed_login": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "is_external_signer": {
                          "type": "boolean",
                          "description": "Derived. **True** when the user has no active `KeyType: Signing`\nkeypair with `encrypted_private_key` — i.e. they sign exclusively\nvia an external wallet (Averer / Dynamic / MetaMask /\nWebAuthn passkey). The frontend trusts this flag verbatim and\nsets `requireManualSignature: true` on every payment mutation\nwhen it's true, so the backend routes the message into the\nmanual-sign drawer instead of attempting server-side signing.\nRecomputed on every call from a case-insensitive\n`LOWER(key_type) = 'signing'` SQL filter. See the architecture\ndoc for the full chain and the cache-invalidation gotcha.\n"
                        },
                        "live_connectors_enabled": {
                          "type": "boolean",
                          "description": "The person's LIVE-connector opt-in (`users.live_connectors_enabled`,\nagent surface spec §5.4). While false, none of their connector\ncredentials can be generated for, minted on, or refreshed onto a\nlive chain. Set via `PUT /auth/users/me/live-connectors`\n(direct session + live verification floor). Returned by\n`GET /auth/users/me` only; best-effort read.\n"
                        }
                      }
                    },
                    "account_address": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Malformed request, invalid field, or business-rule violation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "invalid-email": {
                    "summary": "Email format check failed",
                    "value": {
                      "error": "invalid email format"
                    }
                  },
                  "invalid-uuid": {
                    "summary": "Path parameter not a UUID",
                    "value": {
                      "error": "user_id must be a UUID"
                    }
                  },
                  "lockdown-state": {
                    "summary": "Lockdown flow state mismatch",
                    "value": {
                      "error": "no_active_lockdown"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Request would violate a uniqueness or state invariant. Examples:\ncreating a user with an email that already exists; submitting a\nlockdown request while one is already pending; cancelling a\nlockdown that has already been cancelled.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "lockdown-already-cancelled": {
                    "value": {
                      "error": "lockdown_already_cancelled"
                    }
                  },
                  "cooldown-not-elapsed": {
                    "value": {
                      "error": "cooldown_not_elapsed"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/users \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"email\": \"user@example.com\",\n  \"password\": \"<string>\",\n  \"role\": \"<see UserRole>\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/users', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"email\": \"user@example.com\",\n    \"password\": \"<string>\",\n    \"role\": \"<see UserRole>\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/users'\nresp = httpx.post(url, headers=headers, json={\"email\": \"user@example.com\", \"password\": \"<string>\", \"role\": \"<see UserRole>\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/users/me": {
      "get": {
        "operationId": "getMyProfile",
        "summary": "Get the current user's profile",
        "description": "Returns the authenticated user's profile, including the\n`is_external_signer` flag — a derived boolean that the wallet-SDK\ncaches and the app frontend uses to decide whether payment\nmutations should request manual on-chain signing.\n\n`is_external_signer` is **true** when the user has no active\n`KeyType: Signing` keypair carrying a server-decryptable\n`encrypted_private_key` (i.e. wallet-led signups: Averer /\nDynamic / MetaMask / passkey-only). It is **false** for email-led\nsignups whose internal `default-key` is server-custodied.\n\nThe frontend stores this value at login and reuses it across the\nsession; consumers refresh it on token-renewal. See\n[`../../architecture/key-storage.md`](../../architecture/key-storage.md#is_external_signer--the-boolean-every-payment-mutation-depends-on)\nfor the full chain into `ExecutionMode::Manual` and the\ncache-invalidation gotcha.\n\nAlso carries `live_connectors_enabled` — the person's LIVE-connector\nopt-in (agent surface spec §5.4), flipped via\n`PUT /auth/users/me/live-connectors`. Best-effort read (a failed\nread shows `false`); the flag itself is what every connector mint\nre-reads.\n",
        "tags": [
          "Users"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "User profile",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "email",
                    "role",
                    "is_active"
                  ],
                  "description": "User record shape returned by `/auth/users/me` and friends.\n`password_hash` is always returned as an empty string (never\nexposed). See `yieldfabric-auth/src/handlers/types.rs::UserInfo`.\n",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "email": {
                      "type": "string",
                      "format": "email"
                    },
                    "password_hash": {
                      "type": "string",
                      "description": "Always empty in responses."
                    },
                    "role": {
                      "type": "string",
                      "description": "Built-in role assigned to every user. Role + Permission grants make\nup the RBAC matrix; see `auth.roles` in `config/system.yaml` for\nrole overrides. SuperAdmin always has every permission.\n",
                      "enum": [
                        "SuperAdmin",
                        "Admin",
                        "Manager",
                        "Operator",
                        "User",
                        "Viewer",
                        "ApiClient"
                      ]
                    },
                    "is_active": {
                      "type": "boolean"
                    },
                    "must_change_password": {
                      "type": "boolean"
                    },
                    "password_changed_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "failed_login_count": {
                      "type": "integer"
                    },
                    "last_failed_login": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "is_external_signer": {
                      "type": "boolean",
                      "description": "Derived. **True** when the user has no active `KeyType: Signing`\nkeypair with `encrypted_private_key` — i.e. they sign exclusively\nvia an external wallet (Averer / Dynamic / MetaMask /\nWebAuthn passkey). The frontend trusts this flag verbatim and\nsets `requireManualSignature: true` on every payment mutation\nwhen it's true, so the backend routes the message into the\nmanual-sign drawer instead of attempting server-side signing.\nRecomputed on every call from a case-insensitive\n`LOWER(key_type) = 'signing'` SQL filter. See the architecture\ndoc for the full chain and the cache-invalidation gotcha.\n"
                    },
                    "live_connectors_enabled": {
                      "type": "boolean",
                      "description": "The person's LIVE-connector opt-in (`users.live_connectors_enabled`,\nagent surface spec §5.4). While false, none of their connector\ncredentials can be generated for, minted on, or refreshed onto a\nlive chain. Set via `PUT /auth/users/me/live-connectors`\n(direct session + live verification floor). Returned by\n`GET /auth/users/me` only; best-effort read.\n"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/users/me \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/users/me', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/users/me'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/users/me/live-connectors": {
      "get": {
        "operationId": "getMyLiveConnectors",
        "summary": "Read the current user's live-connector opt-in",
        "description": "The person's LIVE-connector opt-in (`users.live_connectors_enabled`,\nagent surface spec §5.4). While `false`, no connector credential of\ntheirs — a connector API key (`POST /auth/api-key/generate\n{kind: connector}`) or an OAuth connector grant — can be generated\nfor, minted on, or refreshed onto a LIVE chain\n(`chain_is_live`; anything not in `auth.modes.test_chain_ids`).\n\nA read of the caller's own state: any self-scoped user session is\nadmitted, a connector session included (so it can learn why its\nlive mint was refused). Delegated, service and password-change\ncredentials are 403. An RFC 8693 on-behalf-of token must have been\nminted FOR auth (its `aud` names `AUTH_PUBLIC_URL` or the manifest's\n`services.auth_url` — the identity / connections convention, spec\n§5.3); one minted for payments or agents is a valid token at the\nwrong resource and is 401, never 403.\n",
        "tags": [
          "Users"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The opt-in",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "live_connectors_enabled"
                  ],
                  "properties": {
                    "live_connectors_enabled": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired, or wrong-audience JWT (user or service).\nIncludes invalid API keys on the exchange endpoints, failed wallet\nsignatures on the signin endpoints, and failed Sumsub HMAC on the\nwebhook.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "no-token": {
                    "summary": "Missing Authorization header",
                    "value": {
                      "error": "missing bearer token"
                    }
                  },
                  "invalid-token": {
                    "summary": "JWT signature / structure invalid",
                    "value": {
                      "error": "invalid token"
                    }
                  },
                  "invalid-subject": {
                    "summary": "JWT sub field unparseable",
                    "value": {
                      "error": "invalid token subject"
                    }
                  },
                  "webhook-signature": {
                    "summary": "Sumsub HMAC verification failed",
                    "value": {
                      "error": "invalid webhook signature or payload"
                    }
                  },
                  "averer-reverify": {
                    "summary": "Averer flagged a re-verification requirement",
                    "value": {
                      "error": "averer_reverify_required"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "JWT valid (signature + audience accepted) but the caller is not\nauthorised for this operation. Examples: `role` lacks the required\npermission; trying to act on another user's resource without\nSuperAdmin; trying to use a delegation token outside its\n`delegation_scope`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "permission-required": {
                    "summary": "Caller lacks the permission the handler requires",
                    "value": {
                      "error": "permission 'ManageGroupMembers' required"
                    }
                  },
                  "not-account-member": {
                    "summary": "Caller is not a member or NFT-owner of the group",
                    "value": {
                      "error": "not a member of group"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/users/me/live-connectors \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/users/me/live-connectors', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/users/me/live-connectors'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      },
      "put": {
        "operationId": "putMyLiveConnectors",
        "summary": "Enable or disable live connectors for the current user",
        "description": "The ONLY writer of `users.live_connectors_enabled` (agent surface\nspec §5.4). The flag is a user-level gate on top of every connector\ncredential's `allowed_chains` binding: it is re-read at connector-key\ngeneration (every chain the key names), at the connector-key exchange\n(`POST /auth/api-key`, the chain the session lands on — an\nunrestricted key's system default included), on every\n`POST /auth/refresh` of a connector lineage (pre-claim), and by both\nOAuth grants whenever the session chain is live — a `pay.live…`\nresource, or an unbound grant on a deployment with no test chain\nconfigured. It never rewrites a credential's `allowed_chains`.\n\n**Gates.** A DIRECT personal session only — a connector, `mcp_signer`,\ndelegated, service or password-change credential is\n`403 {\"error\", \"code\": \"direct_user_session_required\"}` (a\nprogram-held credential must never unlock live money for itself).\nEnabling additionally requires the LIVE verification floor, composed\nexactly like chain-account activation: the capability gate\n(`AUTH_CAPABILITY_GATE`, default on) → staff roles exempt\n(`SuperAdmin` / `Admin` / `Manager`) → the caller's verification tier\nmust be at least `KycVerified`, else\n`403 {\"error\", \"code\": \"live_verification_required\", \"required_tier\",\n\"current_tier\"}`. Disabling never needs the floor.\n\n**Effect of disabling.** Every live connector mint and rotation of\nthis user's credentials is refused from the next call on (outstanding\naccess tokens run to their 15-minute expiry; there is no revocation\nsweep). A `/auth/refresh` refusal is pre-claim, so the connector\nlineage stays usable on a test chain; an OAuth `refresh_token` grant\nclaims its row before minting, so an opted-out live grant ends there\nand must be re-authorized after opting back in. Re-enabling lets the\nsame credentials reach live again — the row binding is the\ncredential's truth; the flag is the person's live gate on top.\n",
        "tags": [
          "Users"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "enabled"
                ],
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The persisted opt-in",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "live_connectors_enabled"
                  ],
                  "properties": {
                    "live_connectors_enabled": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired, or wrong-audience JWT (user or service).\nIncludes invalid API keys on the exchange endpoints, failed wallet\nsignatures on the signin endpoints, and failed Sumsub HMAC on the\nwebhook.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "no-token": {
                    "summary": "Missing Authorization header",
                    "value": {
                      "error": "missing bearer token"
                    }
                  },
                  "invalid-token": {
                    "summary": "JWT signature / structure invalid",
                    "value": {
                      "error": "invalid token"
                    }
                  },
                  "invalid-subject": {
                    "summary": "JWT sub field unparseable",
                    "value": {
                      "error": "invalid token subject"
                    }
                  },
                  "webhook-signature": {
                    "summary": "Sumsub HMAC verification failed",
                    "value": {
                      "error": "invalid webhook signature or payload"
                    }
                  },
                  "averer-reverify": {
                    "summary": "Averer flagged a re-verification requirement",
                    "value": {
                      "error": "averer_reverify_required"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Not a direct personal session (`direct_user_session_required`),\nor enabling below the live verification floor\n(`live_verification_required`, with the tier vocabulary of\n`ACCOUNT_ACTIVATION_VERIFICATION_REQUIRED`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "required": [
                        "error",
                        "code"
                      ],
                      "properties": {
                        "error": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "const": "direct_user_session_required"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "error",
                        "code",
                        "required_tier",
                        "current_tier"
                      ],
                      "properties": {
                        "error": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "const": "live_verification_required"
                        },
                        "required_tier": {
                          "type": "string",
                          "enum": [
                            "KycVerified"
                          ]
                        },
                        "current_tier": {
                          "type": "string",
                          "enum": [
                            "Unverified",
                            "EmailVerified",
                            "KycVerified"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X PUT https://auth.yieldfabric.com/auth/users/me/live-connectors \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"enabled\": true\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/users/me/live-connectors', {\n  method: 'PUT',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"enabled\": true\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/users/me/live-connectors'\nresp = httpx.put(url, headers=headers, json={\"enabled\": True})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/users/{user_id}/role": {
      "put": {
        "operationId": "changeUserRole",
        "summary": "Change a user's role (admin only)",
        "description": "Requires `Permission::ManageRoles`. SuperAdmin self-demotion is\nblocked when the caller is the only SuperAdmin.\n",
        "tags": [
          "Users"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "User UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "role"
                ],
                "properties": {
                  "role": {
                    "type": "string",
                    "description": "Built-in role assigned to every user. Role + Permission grants make\nup the RBAC matrix; see `auth.roles` in `config/system.yaml` for\nrole overrides. SuperAdmin always has every permission.\n",
                    "enum": [
                      "SuperAdmin",
                      "Admin",
                      "Manager",
                      "Operator",
                      "User",
                      "Viewer",
                      "ApiClient"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Role updated"
          },
          "403": {
            "description": "JWT valid (signature + audience accepted) but the caller is not\nauthorised for this operation. Examples: `role` lacks the required\npermission; trying to act on another user's resource without\nSuperAdmin; trying to use a delegation token outside its\n`delegation_scope`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "permission-required": {
                    "summary": "Caller lacks the permission the handler requires",
                    "value": {
                      "error": "permission 'ManageGroupMembers' required"
                    }
                  },
                  "not-account-member": {
                    "summary": "Caller is not a member or NFT-owner of the group",
                    "value": {
                      "error": "not a member of group"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X PUT https://auth.yieldfabric.com/auth/users/{user_id}/role \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"role\": \"<see UserRole>\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/users/{user_id}/role', {\n  method: 'PUT',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"role\": \"<see UserRole>\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/users/{user_id}/role'\nresp = httpx.put(url, headers=headers, json={\"role\": \"<see UserRole>\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/users/{user_id}/deactivate": {
      "post": {
        "operationId": "deactivateUser",
        "summary": "Deactivate a user (admin only)",
        "description": "Marks the user inactive. Blocks self-deactivation when the\ncaller is the only SuperAdmin. Deactivated users can't log in\nbut their data is retained for audit.\n",
        "tags": [
          "Users"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "User UUID."
          }
        ],
        "responses": {
          "204": {
            "description": "User deactivated"
          },
          "403": {
            "description": "JWT valid (signature + audience accepted) but the caller is not\nauthorised for this operation. Examples: `role` lacks the required\npermission; trying to act on another user's resource without\nSuperAdmin; trying to use a delegation token outside its\n`delegation_scope`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "permission-required": {
                    "summary": "Caller lacks the permission the handler requires",
                    "value": {
                      "error": "permission 'ManageGroupMembers' required"
                    }
                  },
                  "not-account-member": {
                    "summary": "Caller is not a member or NFT-owner of the group",
                    "value": {
                      "error": "not a member of group"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (user, group, key, delegation token, wallet, provider).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "keypair": {
                    "summary": "Keypair UUID not found",
                    "value": {
                      "error": "keypair not found"
                    }
                  },
                  "kyc-provider": {
                    "summary": "Requested KYC provider not enabled in yaml",
                    "value": {
                      "error": "kyc provider not enabled"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/users/{user_id}/deactivate \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/users/{user_id}/deactivate', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/users/{user_id}/deactivate'\nresp = httpx.post(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/users/me/profile": {
      "get": {
        "operationId": "getMyExtendedProfile",
        "summary": "Get the current user's extended profile (B5)",
        "description": "Returns the user's extended profile — display name, locale, notifications, etc.",
        "tags": [
          "Users"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Extended profile",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "User profile fields."
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/users/me/profile \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/users/me/profile', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/users/me/profile'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      },
      "patch": {
        "operationId": "patchMyExtendedProfile",
        "summary": "Partially update the current user's extended profile",
        "tags": [
          "Users"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Partial-update object (only supplied fields are written)."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated extended profile"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X PATCH https://auth.yieldfabric.com/auth/users/me/profile \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/users/me/profile', {\n  method: 'PATCH',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({}),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/users/me/profile'\nresp = httpx.patch(url, headers=headers, json={})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/users/me/profile/avatar": {
      "post": {
        "operationId": "uploadMyProfileAvatar",
        "summary": "Upload the current user's profile photo",
        "description": "Accepts a multipart image file named `avatar` and stores it in\nthe current profile's `avatar_url` field. Uploading a photo clears\n`avatar_choice`; clients should render `avatar_url` first.\n",
        "tags": [
          "Users"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "avatar"
                ],
                "properties": {
                  "avatar": {
                    "type": "string",
                    "format": "binary",
                    "description": "JPEG, PNG, WebP, or GIF image, max 2 MB."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated extended profile",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "User profile fields."
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/users/me/profile/avatar \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -F avatar=@avatar.png"
          }
        ]
      },
      "delete": {
        "operationId": "deleteMyProfileAvatar",
        "summary": "Remove the current user's uploaded profile photo",
        "description": "Clears `avatar_url` while leaving any curated `avatar_choice` unchanged.",
        "tags": [
          "Users"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Updated extended profile",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "User profile fields."
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X DELETE https://auth.yieldfabric.com/auth/users/me/profile/avatar \\\n  -H \"Authorization: Bearer $TOKEN\""
          }
        ]
      }
    },
    "/auth/users/me/email": {
      "patch": {
        "operationId": "patchMyEmail",
        "summary": "Change the current user's email (B6/B9)",
        "description": "Triggers optional Averer reverification when the configured\nKYC provider requires it. Conflict (409) if the target email is\nalready registered.\n",
        "tags": [
          "Users"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "new_email"
                ],
                "properties": {
                  "new_email": {
                    "type": "string",
                    "format": "email"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Email change recorded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "email",
                    "averer_email_refreshed"
                  ],
                  "properties": {
                    "email": {
                      "type": "string",
                      "format": "email"
                    },
                    "averer_email_refreshed": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Request would violate a uniqueness or state invariant. Examples:\ncreating a user with an email that already exists; submitting a\nlockdown request while one is already pending; cancelling a\nlockdown that has already been cancelled.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "lockdown-already-cancelled": {
                    "value": {
                      "error": "lockdown_already_cancelled"
                    }
                  },
                  "cooldown-not-elapsed": {
                    "value": {
                      "error": "cooldown_not_elapsed"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X PATCH https://auth.yieldfabric.com/auth/users/me/email \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"new_email\": \"user@example.com\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/users/me/email', {\n  method: 'PATCH',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"new_email\": \"user@example.com\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/users/me/email'\nresp = httpx.patch(url, headers=headers, json={\"new_email\": \"user@example.com\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/users/me/email/verification": {
      "get": {
        "operationId": "getMyEmailVerificationStatus",
        "summary": "Get current email verification state",
        "description": "Returns whether the authenticated user's current `users.email`\nhas been confirmed by a short-lived email code. Synthetic\nwallet-led emails (`@external.yieldfabric.local`) are reported as\n`is_synthetic: true` and must be replaced before verification can\nstart.\n",
        "tags": [
          "Users"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Email verification status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "email",
                    "is_synthetic",
                    "verified",
                    "pending"
                  ],
                  "properties": {
                    "email": {
                      "type": "string",
                      "format": "email"
                    },
                    "is_synthetic": {
                      "type": "boolean"
                    },
                    "verified": {
                      "type": "boolean"
                    },
                    "verified_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "pending": {
                      "type": "boolean"
                    },
                    "expires_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "can_resend_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "attempts_remaining": {
                      "type": [
                        "integer",
                        "null"
                      ],
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired, or wrong-audience JWT (user or service).\nIncludes invalid API keys on the exchange endpoints, failed wallet\nsignatures on the signin endpoints, and failed Sumsub HMAC on the\nwebhook.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "no-token": {
                    "summary": "Missing Authorization header",
                    "value": {
                      "error": "missing bearer token"
                    }
                  },
                  "invalid-token": {
                    "summary": "JWT signature / structure invalid",
                    "value": {
                      "error": "invalid token"
                    }
                  },
                  "invalid-subject": {
                    "summary": "JWT sub field unparseable",
                    "value": {
                      "error": "invalid token subject"
                    }
                  },
                  "webhook-signature": {
                    "summary": "Sumsub HMAC verification failed",
                    "value": {
                      "error": "invalid webhook signature or payload"
                    }
                  },
                  "averer-reverify": {
                    "summary": "Averer flagged a re-verification requirement",
                    "value": {
                      "error": "averer_reverify_required"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/users/me/email/verification \\\n  -H \"Authorization: Bearer $TOKEN\""
          }
        ]
      }
    },
    "/auth/users/me/email/verification/start": {
      "post": {
        "operationId": "startMyEmailVerification",
        "summary": "Send an email verification code",
        "description": "Sends a 6-digit code to the authenticated user's current email via\nResend (`RESEND_API_KEY`, `MAIL_FROM`). The endpoint is idempotent\ninside the resend cooldown and returns `delivery: recently_sent`\nwhen a still-valid code was already sent.\nNon-production test mode is enabled only when `RESEND_API_KEY` is\nexplicitly set to `0000` or `test`; in that mode no email is sent,\n`delivery: test_code` is returned, and `0000` is the valid code.\n",
        "tags": [
          "Users"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Code sent or recently sent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "email",
                    "delivery",
                    "expires_at",
                    "can_resend_at"
                  ],
                  "properties": {
                    "email": {
                      "type": "string",
                      "format": "email"
                    },
                    "delivery": {
                      "type": "string",
                      "enum": [
                        "sent",
                        "recently_sent",
                        "test_code"
                      ]
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "can_resend_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "A real email must be added before a code can be sent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "example": "real_email_required"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "Resend could not be reached or rejected the message.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "example": "email delivery failed"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "Email delivery is not configured or temporarily unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "example": "email delivery unavailable"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/users/me/email/verification/start \\\n  -H \"Authorization: Bearer $TOKEN\""
          }
        ]
      }
    },
    "/auth/users/me/email/verification/confirm": {
      "post": {
        "operationId": "confirmMyEmailVerification",
        "summary": "Confirm an email verification code",
        "description": "Consumes the latest active code for the authenticated user's\ncurrent email. On success, auth records the verified email state\nand sends a best-effort welcome email once per `(user, email)`.\nNormal codes are six digits; non-production email test mode\naccepts the active `0000` code created by the start endpoint.\n",
        "tags": [
          "Users"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code"
                ],
                "properties": {
                  "code": {
                    "type": "string",
                    "minLength": 4,
                    "maxLength": 6,
                    "pattern": "^(0000|[0-9]{6})$"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Email confirmed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "email",
                    "verified",
                    "verified_at",
                    "welcome_email_sent"
                  ],
                  "properties": {
                    "email": {
                      "type": "string",
                      "format": "email"
                    },
                    "verified": {
                      "type": "boolean"
                    },
                    "verified_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "welcome_email_sent": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Malformed request, invalid field, or business-rule violation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "invalid-email": {
                    "summary": "Email format check failed",
                    "value": {
                      "error": "invalid email format"
                    }
                  },
                  "invalid-uuid": {
                    "summary": "Path parameter not a UUID",
                    "value": {
                      "error": "user_id must be a UUID"
                    }
                  },
                  "lockdown-state": {
                    "summary": "Lockdown flow state mismatch",
                    "value": {
                      "error": "no_active_lockdown"
                    }
                  }
                }
              }
            }
          },
          "410": {
            "description": "The active code expired.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "example": "verification code expired"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many failed attempts for the active code.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "example": "too many verification attempts"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/users/me/email/verification/confirm \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"code\":\"123456\"}'"
          }
        ]
      }
    },
    "/auth/users/me/lockdown": {
      "get": {
        "operationId": "getLockdownStatus",
        "summary": "Get the current user's account-lockdown status",
        "description": "Returns the lockdown record (if any) plus computed status.\nThe two-step flow is request → cooldown 24h → execute, with\n`cancel` available during the cooldown. `finalize` records\npost-execute artefacts.\n",
        "tags": [
          "Users"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Lockdown status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "status"
                  ],
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "none",
                        "pending",
                        "cooldown_expired",
                        "executed"
                      ]
                    },
                    "requested_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "executable_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/users/me/lockdown \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/users/me/lockdown', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/users/me/lockdown'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/users/me/lockdown/request": {
      "post": {
        "operationId": "requestLockdown",
        "summary": "Open a lockdown request (start the 24h cooldown)",
        "tags": [
          "Users"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "202": {
            "description": "Request accepted; cooldown started"
          },
          "409": {
            "description": "Request would violate a uniqueness or state invariant. Examples:\ncreating a user with an email that already exists; submitting a\nlockdown request while one is already pending; cancelling a\nlockdown that has already been cancelled.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "lockdown-already-cancelled": {
                    "value": {
                      "error": "lockdown_already_cancelled"
                    }
                  },
                  "cooldown-not-elapsed": {
                    "value": {
                      "error": "cooldown_not_elapsed"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/users/me/lockdown/request \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/users/me/lockdown/request', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/users/me/lockdown/request'\nresp = httpx.post(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/users/me/lockdown/cancel": {
      "post": {
        "operationId": "cancelLockdown",
        "summary": "Cancel a pending lockdown request",
        "tags": [
          "Users"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "204": {
            "description": "Lockdown cancelled"
          },
          "404": {
            "description": "Resource not found (user, group, key, delegation token, wallet, provider).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "keypair": {
                    "summary": "Keypair UUID not found",
                    "value": {
                      "error": "keypair not found"
                    }
                  },
                  "kyc-provider": {
                    "summary": "Requested KYC provider not enabled in yaml",
                    "value": {
                      "error": "kyc provider not enabled"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/users/me/lockdown/cancel \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/users/me/lockdown/cancel', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/users/me/lockdown/cancel'\nresp = httpx.post(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/users/me/lockdown/execute": {
      "post": {
        "operationId": "executeLockdown",
        "summary": "Execute lockdown after cooldown",
        "description": "Revokes refresh tokens, optionally cycles signing keys, and\nrecords the lockdown. Returns 400 if the cooldown hasn't\nexpired.\n",
        "tags": [
          "Users"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "204": {
            "description": "Lockdown executed"
          },
          "400": {
            "description": "Malformed request, invalid field, or business-rule violation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "invalid-email": {
                    "summary": "Email format check failed",
                    "value": {
                      "error": "invalid email format"
                    }
                  },
                  "invalid-uuid": {
                    "summary": "Path parameter not a UUID",
                    "value": {
                      "error": "user_id must be a UUID"
                    }
                  },
                  "lockdown-state": {
                    "summary": "Lockdown flow state mismatch",
                    "value": {
                      "error": "no_active_lockdown"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/users/me/lockdown/execute \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/users/me/lockdown/execute', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/users/me/lockdown/execute'\nresp = httpx.post(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/users/me/lockdown/finalize": {
      "post": {
        "operationId": "finalizeLockdown",
        "summary": "Finalize an executed lockdown",
        "description": "Records post-execute artefacts (e.g. fresh key ids, replacement contact info).",
        "tags": [
          "Users"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "204": {
            "description": "Lockdown finalized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/users/me/lockdown/finalize \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/users/me/lockdown/finalize', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/users/me/lockdown/finalize'\nresp = httpx.post(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/users/{user_id}/permissions": {
      "get": {
        "operationId": "getUserPermissions",
        "summary": "List a user's granted permissions",
        "tags": [
          "Permissions"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "User UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "Permission list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "permissions"
                  ],
                  "properties": {
                    "permissions": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "description": "Granular permission grant. Roles ship with a default permission set\n(see `permissions_management.rs::default_permissions_for_role`);\n`POST /auth/users/{user_id}/permissions` overrides per-user.\n",
                        "enum": [
                          "CreateUser",
                          "ReadUser",
                          "UpdateUser",
                          "DeleteUser",
                          "UsersManage",
                          "CreateEntity",
                          "ReadEntity",
                          "UpdateEntity",
                          "DeleteEntity",
                          "SystemConfig",
                          "ViewLogs",
                          "ManageRoles",
                          "ApiRead",
                          "ApiWrite",
                          "ApiAdmin",
                          "BulkOperations",
                          "CreateGroup",
                          "ReadGroup",
                          "UpdateGroup",
                          "DeleteGroup",
                          "ManageGroupMembers",
                          "ManageGroupPermissions",
                          "ManageGroupOwners",
                          "ManageGroupEntityScope",
                          "CreateDelegationToken",
                          "ViewDelegationTokens",
                          "RevokeDelegationToken",
                          "CryptoOperations",
                          "PolicyExecution",
                          "ManageCompliance",
                          "LlmInference"
                        ]
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/users/{user_id}/permissions \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/users/{user_id}/permissions', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/users/{user_id}/permissions'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      },
      "post": {
        "operationId": "grantPermissions",
        "summary": "Add permissions to a user (additive)",
        "description": "Existing permissions are preserved; only the listed ones are added.",
        "tags": [
          "Permissions"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "User UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "permissions"
                ],
                "properties": {
                  "permissions": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "description": "Granular permission grant. Roles ship with a default permission set\n(see `permissions_management.rs::default_permissions_for_role`);\n`POST /auth/users/{user_id}/permissions` overrides per-user.\n",
                      "enum": [
                        "CreateUser",
                        "ReadUser",
                        "UpdateUser",
                        "DeleteUser",
                        "UsersManage",
                        "CreateEntity",
                        "ReadEntity",
                        "UpdateEntity",
                        "DeleteEntity",
                        "SystemConfig",
                        "ViewLogs",
                        "ManageRoles",
                        "ApiRead",
                        "ApiWrite",
                        "ApiAdmin",
                        "BulkOperations",
                        "CreateGroup",
                        "ReadGroup",
                        "UpdateGroup",
                        "DeleteGroup",
                        "ManageGroupMembers",
                        "ManageGroupPermissions",
                        "ManageGroupOwners",
                        "ManageGroupEntityScope",
                        "CreateDelegationToken",
                        "ViewDelegationTokens",
                        "RevokeDelegationToken",
                        "CryptoOperations",
                        "PolicyExecution",
                        "ManageCompliance",
                        "LlmInference"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Permissions granted"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/users/{user_id}/permissions \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"permissions\": [\n    \"<see Permission>\"\n  ]\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/users/{user_id}/permissions', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"permissions\": [\n      \"<see Permission>\"\n    ]\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/users/{user_id}/permissions'\nresp = httpx.post(url, headers=headers, json={\"permissions\": [\"<see Permission>\"]})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      },
      "delete": {
        "operationId": "revokePermissions",
        "summary": "Remove permissions from a user (subtractive)",
        "tags": [
          "Permissions"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "User UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "permissions"
                ],
                "properties": {
                  "permissions": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "description": "Granular permission grant. Roles ship with a default permission set\n(see `permissions_management.rs::default_permissions_for_role`);\n`POST /auth/users/{user_id}/permissions` overrides per-user.\n",
                      "enum": [
                        "CreateUser",
                        "ReadUser",
                        "UpdateUser",
                        "DeleteUser",
                        "UsersManage",
                        "CreateEntity",
                        "ReadEntity",
                        "UpdateEntity",
                        "DeleteEntity",
                        "SystemConfig",
                        "ViewLogs",
                        "ManageRoles",
                        "ApiRead",
                        "ApiWrite",
                        "ApiAdmin",
                        "BulkOperations",
                        "CreateGroup",
                        "ReadGroup",
                        "UpdateGroup",
                        "DeleteGroup",
                        "ManageGroupMembers",
                        "ManageGroupPermissions",
                        "ManageGroupOwners",
                        "ManageGroupEntityScope",
                        "CreateDelegationToken",
                        "ViewDelegationTokens",
                        "RevokeDelegationToken",
                        "CryptoOperations",
                        "PolicyExecution",
                        "ManageCompliance",
                        "LlmInference"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Permissions revoked"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X DELETE https://auth.yieldfabric.com/auth/users/{user_id}/permissions \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"permissions\": [\n    \"<see Permission>\"\n  ]\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/users/{user_id}/permissions', {\n  method: 'DELETE',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"permissions\": [\n      \"<see Permission>\"\n    ]\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/users/{user_id}/permissions'\nresp = httpx.delete(url, headers=headers, json={\"permissions\": [\"<see Permission>\"]})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      },
      "put": {
        "operationId": "replacePermissions",
        "summary": "Replace the user's entire permission set",
        "description": "Discards the existing set and writes the supplied list verbatim. Use with care.",
        "tags": [
          "Permissions"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "User UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "permissions"
                ],
                "properties": {
                  "permissions": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "description": "Granular permission grant. Roles ship with a default permission set\n(see `permissions_management.rs::default_permissions_for_role`);\n`POST /auth/users/{user_id}/permissions` overrides per-user.\n",
                      "enum": [
                        "CreateUser",
                        "ReadUser",
                        "UpdateUser",
                        "DeleteUser",
                        "UsersManage",
                        "CreateEntity",
                        "ReadEntity",
                        "UpdateEntity",
                        "DeleteEntity",
                        "SystemConfig",
                        "ViewLogs",
                        "ManageRoles",
                        "ApiRead",
                        "ApiWrite",
                        "ApiAdmin",
                        "BulkOperations",
                        "CreateGroup",
                        "ReadGroup",
                        "UpdateGroup",
                        "DeleteGroup",
                        "ManageGroupMembers",
                        "ManageGroupPermissions",
                        "ManageGroupOwners",
                        "ManageGroupEntityScope",
                        "CreateDelegationToken",
                        "ViewDelegationTokens",
                        "RevokeDelegationToken",
                        "CryptoOperations",
                        "PolicyExecution",
                        "ManageCompliance",
                        "LlmInference"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Permissions replaced"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X PUT https://auth.yieldfabric.com/auth/users/{user_id}/permissions \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"permissions\": [\n    \"<see Permission>\"\n  ]\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/users/{user_id}/permissions', {\n  method: 'PUT',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"permissions\": [\n      \"<see Permission>\"\n    ]\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/users/{user_id}/permissions'\nresp = httpx.put(url, headers=headers, json={\"permissions\": [\"<see Permission>\"]})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/users/{user_id}/permissions/{permission}": {
      "post": {
        "operationId": "grantPermission",
        "summary": "Grant a single permission",
        "tags": [
          "Permissions"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "User UUID."
          },
          {
            "name": "permission",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "Granular permission grant. Roles ship with a default permission set\n(see `permissions_management.rs::default_permissions_for_role`);\n`POST /auth/users/{user_id}/permissions` overrides per-user.\n",
              "enum": [
                "CreateUser",
                "ReadUser",
                "UpdateUser",
                "DeleteUser",
                "UsersManage",
                "CreateEntity",
                "ReadEntity",
                "UpdateEntity",
                "DeleteEntity",
                "SystemConfig",
                "ViewLogs",
                "ManageRoles",
                "ApiRead",
                "ApiWrite",
                "ApiAdmin",
                "BulkOperations",
                "CreateGroup",
                "ReadGroup",
                "UpdateGroup",
                "DeleteGroup",
                "ManageGroupMembers",
                "ManageGroupPermissions",
                "ManageGroupOwners",
                "ManageGroupEntityScope",
                "CreateDelegationToken",
                "ViewDelegationTokens",
                "RevokeDelegationToken",
                "CryptoOperations",
                "PolicyExecution",
                "ManageCompliance",
                "LlmInference"
              ]
            },
            "description": "Permission identifier (e.g. `CreateUser`, `ApiRead`, `CryptoOperations`)."
          }
        ],
        "responses": {
          "204": {
            "description": "Permission granted"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/users/{user_id}/permissions/{permission} \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/users/{user_id}/permissions/{permission}', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/users/{user_id}/permissions/{permission}'\nresp = httpx.post(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      },
      "delete": {
        "operationId": "revokePermission",
        "summary": "Revoke a single permission",
        "tags": [
          "Permissions"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "User UUID."
          },
          {
            "name": "permission",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "Granular permission grant. Roles ship with a default permission set\n(see `permissions_management.rs::default_permissions_for_role`);\n`POST /auth/users/{user_id}/permissions` overrides per-user.\n",
              "enum": [
                "CreateUser",
                "ReadUser",
                "UpdateUser",
                "DeleteUser",
                "UsersManage",
                "CreateEntity",
                "ReadEntity",
                "UpdateEntity",
                "DeleteEntity",
                "SystemConfig",
                "ViewLogs",
                "ManageRoles",
                "ApiRead",
                "ApiWrite",
                "ApiAdmin",
                "BulkOperations",
                "CreateGroup",
                "ReadGroup",
                "UpdateGroup",
                "DeleteGroup",
                "ManageGroupMembers",
                "ManageGroupPermissions",
                "ManageGroupOwners",
                "ManageGroupEntityScope",
                "CreateDelegationToken",
                "ViewDelegationTokens",
                "RevokeDelegationToken",
                "CryptoOperations",
                "PolicyExecution",
                "ManageCompliance",
                "LlmInference"
              ]
            },
            "description": "Permission identifier (e.g. `CreateUser`, `ApiRead`, `CryptoOperations`)."
          }
        ],
        "responses": {
          "204": {
            "description": "Permission revoked"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X DELETE https://auth.yieldfabric.com/auth/users/{user_id}/permissions/{permission} \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/users/{user_id}/permissions/{permission}', {\n  method: 'DELETE',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/users/{user_id}/permissions/{permission}'\nresp = httpx.delete(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      },
      "get": {
        "operationId": "checkPermission",
        "summary": "Check whether a user has a specific permission",
        "tags": [
          "Permissions"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "User UUID."
          },
          {
            "name": "permission",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "Granular permission grant. Roles ship with a default permission set\n(see `permissions_management.rs::default_permissions_for_role`);\n`POST /auth/users/{user_id}/permissions` overrides per-user.\n",
              "enum": [
                "CreateUser",
                "ReadUser",
                "UpdateUser",
                "DeleteUser",
                "UsersManage",
                "CreateEntity",
                "ReadEntity",
                "UpdateEntity",
                "DeleteEntity",
                "SystemConfig",
                "ViewLogs",
                "ManageRoles",
                "ApiRead",
                "ApiWrite",
                "ApiAdmin",
                "BulkOperations",
                "CreateGroup",
                "ReadGroup",
                "UpdateGroup",
                "DeleteGroup",
                "ManageGroupMembers",
                "ManageGroupPermissions",
                "ManageGroupOwners",
                "ManageGroupEntityScope",
                "CreateDelegationToken",
                "ViewDelegationTokens",
                "RevokeDelegationToken",
                "CryptoOperations",
                "PolicyExecution",
                "ManageCompliance",
                "LlmInference"
              ]
            },
            "description": "Permission identifier (e.g. `CreateUser`, `ApiRead`, `CryptoOperations`)."
          }
        ],
        "responses": {
          "200": {
            "description": "Check result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "has_permission"
                  ],
                  "properties": {
                    "has_permission": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/users/{user_id}/permissions/{permission} \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/users/{user_id}/permissions/{permission}', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/users/{user_id}/permissions/{permission}'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/groups": {
      "get": {
        "operationId": "listGroups",
        "summary": "List all groups (SuperAdmin scope)",
        "tags": [
          "Groups"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Group list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "required": [
                      "id",
                      "name",
                      "description",
                      "group_type",
                      "is_active",
                      "created_at",
                      "updated_at",
                      "role",
                      "account_address",
                      "account_chain_id",
                      "entity_verification_profile",
                      "entity_verification_status"
                    ],
                    "description": "Group record shape returned by `/auth/groups` and friends. Account fields\nare explicitly chain-qualified: a null `account_address` means the group\nhas no default wallet on `account_chain_id`.\n",
                    "properties": {
                      "id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "name": {
                        "type": "string"
                      },
                      "description": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "group_type": {
                        "type": "string",
                        "enum": [
                          "organization",
                          "team",
                          "project",
                          "custom"
                        ]
                      },
                      "role": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Current caller's off-chain membership role when the list endpoint supplies it."
                      },
                      "account_address": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Default account address on `account_chain_id`, or null when not activated there."
                      },
                      "account_chain_id": {
                        "type": "string",
                        "description": "Chain against which `account_address` was resolved."
                      },
                      "account_activation": {
                        "oneOf": [
                          {
                            "type": "object",
                            "required": [
                              "status",
                              "chain_id",
                              "wallet_id",
                              "account_address",
                              "deployed"
                            ],
                            "properties": {
                              "status": {
                                "type": "string",
                                "const": "ready"
                              },
                              "chain_id": {
                                "type": "string"
                              },
                              "wallet_id": {
                                "type": "string"
                              },
                              "account_address": {
                                "type": "string",
                                "pattern": "^0x[0-9a-fA-F]{40}$"
                              },
                              "deployed": {
                                "type": "boolean",
                                "description": "True only when this request completed a new deployment."
                              }
                            }
                          },
                          {
                            "type": "object",
                            "required": [
                              "status",
                              "chain_id",
                              "message_id"
                            ],
                            "properties": {
                              "status": {
                                "type": "string",
                                "const": "provisioning"
                              },
                              "chain_id": {
                                "type": "string"
                              },
                              "message_id": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "format": "uuid"
                              }
                            }
                          },
                          {
                            "type": "object",
                            "required": [
                              "status",
                              "chain_id",
                              "message_id"
                            ],
                            "properties": {
                              "status": {
                                "type": "string",
                                "const": "pending_signature"
                              },
                              "chain_id": {
                                "type": "string"
                              },
                              "message_id": {
                                "type": "string",
                                "format": "uuid"
                              }
                            }
                          },
                          {
                            "type": "object",
                            "required": [
                              "status",
                              "chain_id",
                              "error"
                            ],
                            "properties": {
                              "status": {
                                "type": "string",
                                "const": "failed_retryable"
                              },
                              "chain_id": {
                                "type": "string"
                              },
                              "error": {
                                "type": "string"
                              }
                            }
                          }
                        ],
                        "discriminator": {
                          "propertyName": "status"
                        }
                      },
                      "billing_policy": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "enum": [
                          "admin_pays",
                          "member_pays",
                          null
                        ],
                        "description": "Present on create when a declared policy committed atomically with the\ngroup. Omitted when no policy was declared or billing is disabled.\n"
                      },
                      "entity_verification_profile": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Bound entity/KYB verification profile, or null when the group is not declared as a business."
                      },
                      "entity_verification_status": {
                        "type": "string",
                        "enum": [
                          "not_required",
                          "unverified",
                          "pending",
                          "verified",
                          "rejected",
                          "expired"
                        ],
                        "description": "Derived current group KYB posture; always serialized."
                      },
                      "is_active": {
                        "type": "boolean"
                      },
                      "created_at": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "updated_at": {
                        "type": "string",
                        "format": "date-time"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/groups \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/groups', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/groups'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      },
      "post": {
        "operationId": "createGroup",
        "summary": "Create a new group",
        "description": "Atomically creates the complete off-chain group identity: group row,\nsigning/encryption keys, zero-wallet identity entity, and creator Admin\nmembership. `deploy` defaults to true for compatibility; when true, the\ncommitted group is then passed to canonical activation on the caller's\nJWT chain. When false, creation is entirely off-chain and any\nauthenticated user may create the billing-only group.\n\nGroup creation never rolls back a committed group because deployment is\npending or failed. Inspect the optional nested `account_activation`:\n`202` means `provisioning`/`pending_signature`; `200` may contain\n`ready`, `failed_retryable`, or no activation (`deploy: false`). Retry\nthe activation resource, not group creation, after a retryable failure.\n",
        "tags": [
          "Groups"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "group_type"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "group_type": {
                    "type": "string",
                    "description": "Group flavor. Drives default permissions, entity-scope hints, and\non-chain account naming.\n",
                    "enum": [
                      "Organization",
                      "Team",
                      "Project",
                      "Custom"
                    ]
                  },
                  "deploy": {
                    "type": "boolean",
                    "default": true,
                    "description": "Request canonical account activation after off-chain creation."
                  },
                  "legal_entity": {
                    "type": "boolean",
                    "default": false,
                    "description": "Declare the group a business: binds the deployment's default\nentity (KYB) verification profile at creation. 400 when the\ndeployment has no `verification_profiles.entity_default`.\n"
                  },
                  "billing_policy": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "admin_pays",
                      "member_pays",
                      null
                    ],
                    "description": "Declarative group billing policy committed atomically with\nthe group identity and creator membership. Absent ⇒ the\n`admin_pays` default, which routes\ngroup-target deploy pre-flights to the group's OWN billing\naccount — empty for a fresh consumer group, so the first\nchain-account activation 402s. Consumer platforms whose\nmembers run sponsored should declare `member_pays`.\nValidated before any row is committed (400 on unknown\nvalues); ignored with a server-side warning when billing is\ndisabled. On billing-enabled deployments the response echo\nproves the policy was part of the successful transaction.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Off-chain group committed; activation is ready, retryable, or not requested.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "name",
                    "description",
                    "group_type",
                    "is_active",
                    "created_at",
                    "updated_at",
                    "role",
                    "account_address",
                    "account_chain_id",
                    "entity_verification_profile",
                    "entity_verification_status"
                  ],
                  "description": "Group record shape returned by `/auth/groups` and friends. Account fields\nare explicitly chain-qualified: a null `account_address` means the group\nhas no default wallet on `account_chain_id`.\n",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "description": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "group_type": {
                      "type": "string",
                      "enum": [
                        "organization",
                        "team",
                        "project",
                        "custom"
                      ]
                    },
                    "role": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Current caller's off-chain membership role when the list endpoint supplies it."
                    },
                    "account_address": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Default account address on `account_chain_id`, or null when not activated there."
                    },
                    "account_chain_id": {
                      "type": "string",
                      "description": "Chain against which `account_address` was resolved."
                    },
                    "account_activation": {
                      "oneOf": [
                        {
                          "type": "object",
                          "required": [
                            "status",
                            "chain_id",
                            "wallet_id",
                            "account_address",
                            "deployed"
                          ],
                          "properties": {
                            "status": {
                              "type": "string",
                              "const": "ready"
                            },
                            "chain_id": {
                              "type": "string"
                            },
                            "wallet_id": {
                              "type": "string"
                            },
                            "account_address": {
                              "type": "string",
                              "pattern": "^0x[0-9a-fA-F]{40}$"
                            },
                            "deployed": {
                              "type": "boolean",
                              "description": "True only when this request completed a new deployment."
                            }
                          }
                        },
                        {
                          "type": "object",
                          "required": [
                            "status",
                            "chain_id",
                            "message_id"
                          ],
                          "properties": {
                            "status": {
                              "type": "string",
                              "const": "provisioning"
                            },
                            "chain_id": {
                              "type": "string"
                            },
                            "message_id": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "uuid"
                            }
                          }
                        },
                        {
                          "type": "object",
                          "required": [
                            "status",
                            "chain_id",
                            "message_id"
                          ],
                          "properties": {
                            "status": {
                              "type": "string",
                              "const": "pending_signature"
                            },
                            "chain_id": {
                              "type": "string"
                            },
                            "message_id": {
                              "type": "string",
                              "format": "uuid"
                            }
                          }
                        },
                        {
                          "type": "object",
                          "required": [
                            "status",
                            "chain_id",
                            "error"
                          ],
                          "properties": {
                            "status": {
                              "type": "string",
                              "const": "failed_retryable"
                            },
                            "chain_id": {
                              "type": "string"
                            },
                            "error": {
                              "type": "string"
                            }
                          }
                        }
                      ],
                      "discriminator": {
                        "propertyName": "status"
                      }
                    },
                    "billing_policy": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "admin_pays",
                        "member_pays",
                        null
                      ],
                      "description": "Present on create when a declared policy committed atomically with the\ngroup. Omitted when no policy was declared or billing is disabled.\n"
                    },
                    "entity_verification_profile": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Bound entity/KYB verification profile, or null when the group is not declared as a business."
                    },
                    "entity_verification_status": {
                      "type": "string",
                      "enum": [
                        "not_required",
                        "unverified",
                        "pending",
                        "verified",
                        "rejected",
                        "expired"
                      ],
                      "description": "Derived current group KYB posture; always serialized."
                    },
                    "is_active": {
                      "type": "boolean"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "202": {
            "description": "Off-chain group committed; account activation is provisioning or pending a signature.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "name",
                    "description",
                    "group_type",
                    "is_active",
                    "created_at",
                    "updated_at",
                    "role",
                    "account_address",
                    "account_chain_id",
                    "entity_verification_profile",
                    "entity_verification_status"
                  ],
                  "description": "Group record shape returned by `/auth/groups` and friends. Account fields\nare explicitly chain-qualified: a null `account_address` means the group\nhas no default wallet on `account_chain_id`.\n",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "description": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "group_type": {
                      "type": "string",
                      "enum": [
                        "organization",
                        "team",
                        "project",
                        "custom"
                      ]
                    },
                    "role": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Current caller's off-chain membership role when the list endpoint supplies it."
                    },
                    "account_address": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Default account address on `account_chain_id`, or null when not activated there."
                    },
                    "account_chain_id": {
                      "type": "string",
                      "description": "Chain against which `account_address` was resolved."
                    },
                    "account_activation": {
                      "oneOf": [
                        {
                          "type": "object",
                          "required": [
                            "status",
                            "chain_id",
                            "wallet_id",
                            "account_address",
                            "deployed"
                          ],
                          "properties": {
                            "status": {
                              "type": "string",
                              "const": "ready"
                            },
                            "chain_id": {
                              "type": "string"
                            },
                            "wallet_id": {
                              "type": "string"
                            },
                            "account_address": {
                              "type": "string",
                              "pattern": "^0x[0-9a-fA-F]{40}$"
                            },
                            "deployed": {
                              "type": "boolean",
                              "description": "True only when this request completed a new deployment."
                            }
                          }
                        },
                        {
                          "type": "object",
                          "required": [
                            "status",
                            "chain_id",
                            "message_id"
                          ],
                          "properties": {
                            "status": {
                              "type": "string",
                              "const": "provisioning"
                            },
                            "chain_id": {
                              "type": "string"
                            },
                            "message_id": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "uuid"
                            }
                          }
                        },
                        {
                          "type": "object",
                          "required": [
                            "status",
                            "chain_id",
                            "message_id"
                          ],
                          "properties": {
                            "status": {
                              "type": "string",
                              "const": "pending_signature"
                            },
                            "chain_id": {
                              "type": "string"
                            },
                            "message_id": {
                              "type": "string",
                              "format": "uuid"
                            }
                          }
                        },
                        {
                          "type": "object",
                          "required": [
                            "status",
                            "chain_id",
                            "error"
                          ],
                          "properties": {
                            "status": {
                              "type": "string",
                              "const": "failed_retryable"
                            },
                            "chain_id": {
                              "type": "string"
                            },
                            "error": {
                              "type": "string"
                            }
                          }
                        }
                      ],
                      "discriminator": {
                        "propertyName": "status"
                      }
                    },
                    "billing_policy": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "admin_pays",
                        "member_pays",
                        null
                      ],
                      "description": "Present on create when a declared policy committed atomically with the\ngroup. Omitted when no policy was declared or billing is disabled.\n"
                    },
                    "entity_verification_profile": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Bound entity/KYB verification profile, or null when the group is not declared as a business."
                    },
                    "entity_verification_status": {
                      "type": "string",
                      "enum": [
                        "not_required",
                        "unverified",
                        "pending",
                        "verified",
                        "rejected",
                        "expired"
                      ],
                      "description": "Derived current group KYB posture; always serialized."
                    },
                    "is_active": {
                      "type": "boolean"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid `billing_policy` value, or `legal_entity: true` on a deployment without a configured entity profile. Validated before any row is committed."
          },
          "403": {
            "description": "`deploy: true` requires `CreateGroup`; billing-only creation does not."
          },
          "409": {
            "description": "Request would violate a uniqueness or state invariant. Examples:\ncreating a user with an email that already exists; submitting a\nlockdown request while one is already pending; cancelling a\nlockdown that has already been cancelled.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "lockdown-already-cancelled": {
                    "value": {
                      "error": "lockdown_already_cancelled"
                    }
                  },
                  "cooldown-not-elapsed": {
                    "value": {
                      "error": "cooldown_not_elapsed"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Billing-only creator already holds the active-membership safety limit."
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/groups \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"name\": \"<string>\",\n  \"group_type\": \"Project\",\n  \"deploy\": true\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/groups', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"name\": \"<string>\",\n    \"group_type\": \"<see GroupType>\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/groups'\nresp = httpx.post(url, headers=headers, json={\"name\": \"<string>\", \"group_type\": \"<see GroupType>\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/groups/user": {
      "get": {
        "operationId": "listUserGroups",
        "summary": "List groups the current user is a member of",
        "description": "Returns only the caller's OWN memberships, so it accepts any credential\nthat speaks for exactly one human — including MCP-shaped bearers\n(`/auth/mcp/login` and OAuth connector grants, which carry\n`mcp_impersonation`). The group MUTATIONS and the SuperAdmin-only\n`GET /auth/groups` keep the stricter direct-user gate and refuse those.\n\nStill refused here: service tokens, any delegation or `acting_as`\ncredential, and password-change-only tokens — `403`.\n\nRequires the `ReadGroup` permission, which every role from `Viewer`\nupward carries by default.\n",
        "tags": [
          "Groups"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Groups + the caller's role in each",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "allOf": [
                      {
                        "type": "object",
                        "required": [
                          "id",
                          "name",
                          "description",
                          "group_type",
                          "is_active",
                          "created_at",
                          "updated_at",
                          "role",
                          "account_address",
                          "account_chain_id",
                          "entity_verification_profile",
                          "entity_verification_status"
                        ],
                        "description": "Group record shape returned by `/auth/groups` and friends. Account fields\nare explicitly chain-qualified: a null `account_address` means the group\nhas no default wallet on `account_chain_id`.\n",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "name": {
                            "type": "string"
                          },
                          "description": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "group_type": {
                            "type": "string",
                            "enum": [
                              "organization",
                              "team",
                              "project",
                              "custom"
                            ]
                          },
                          "role": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Current caller's off-chain membership role when the list endpoint supplies it."
                          },
                          "account_address": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Default account address on `account_chain_id`, or null when not activated there."
                          },
                          "account_chain_id": {
                            "type": "string",
                            "description": "Chain against which `account_address` was resolved."
                          },
                          "account_activation": {
                            "oneOf": [
                              {
                                "type": "object",
                                "required": [
                                  "status",
                                  "chain_id",
                                  "wallet_id",
                                  "account_address",
                                  "deployed"
                                ],
                                "properties": {
                                  "status": {
                                    "type": "string",
                                    "const": "ready"
                                  },
                                  "chain_id": {
                                    "type": "string"
                                  },
                                  "wallet_id": {
                                    "type": "string"
                                  },
                                  "account_address": {
                                    "type": "string",
                                    "pattern": "^0x[0-9a-fA-F]{40}$"
                                  },
                                  "deployed": {
                                    "type": "boolean",
                                    "description": "True only when this request completed a new deployment."
                                  }
                                }
                              },
                              {
                                "type": "object",
                                "required": [
                                  "status",
                                  "chain_id",
                                  "message_id"
                                ],
                                "properties": {
                                  "status": {
                                    "type": "string",
                                    "const": "provisioning"
                                  },
                                  "chain_id": {
                                    "type": "string"
                                  },
                                  "message_id": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "format": "uuid"
                                  }
                                }
                              },
                              {
                                "type": "object",
                                "required": [
                                  "status",
                                  "chain_id",
                                  "message_id"
                                ],
                                "properties": {
                                  "status": {
                                    "type": "string",
                                    "const": "pending_signature"
                                  },
                                  "chain_id": {
                                    "type": "string"
                                  },
                                  "message_id": {
                                    "type": "string",
                                    "format": "uuid"
                                  }
                                }
                              },
                              {
                                "type": "object",
                                "required": [
                                  "status",
                                  "chain_id",
                                  "error"
                                ],
                                "properties": {
                                  "status": {
                                    "type": "string",
                                    "const": "failed_retryable"
                                  },
                                  "chain_id": {
                                    "type": "string"
                                  },
                                  "error": {
                                    "type": "string"
                                  }
                                }
                              }
                            ],
                            "discriminator": {
                              "propertyName": "status"
                            }
                          },
                          "billing_policy": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "enum": [
                              "admin_pays",
                              "member_pays",
                              null
                            ],
                            "description": "Present on create when a declared policy committed atomically with the\ngroup. Omitted when no policy was declared or billing is disabled.\n"
                          },
                          "entity_verification_profile": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Bound entity/KYB verification profile, or null when the group is not declared as a business."
                          },
                          "entity_verification_status": {
                            "type": "string",
                            "enum": [
                              "not_required",
                              "unverified",
                              "pending",
                              "verified",
                              "rejected",
                              "expired"
                            ],
                            "description": "Derived current group KYB posture; always serialized."
                          },
                          "is_active": {
                            "type": "boolean"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updated_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      },
                      {
                        "type": "object",
                        "required": [
                          "role"
                        ],
                        "properties": {
                          "role": {
                            "type": "string",
                            "enum": [
                              "owner",
                              "admin",
                              "member"
                            ]
                          }
                        }
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/groups/user \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/groups/user', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/groups/user'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/groups/{source_group_id}/owned-groups": {
      "get": {
        "operationId": "listOwnedGroups",
        "summary": "Discover groups owned by the directly delegated source group",
        "description": "Lists active immutable NFT membership relationships whose parent is\n`source_group_id` on the validated bearer chain. The bearer must be a\ndirect user delegation acting as that source group with `ReadGroup`.\nPersonal, service, impersonation, wrong-source, already nested, and\npartial-provenance credentials fail closed. The human's live source-group\nauthority is rechecked.\n\nEach target must still use the relationship's bound current default\naccount. Results include the durable relationship ID and exact credential\ncontract/token tuple, sort by group name then UUID, and never imply an\ninherited off-chain membership role. Discovery performs no per-row RPC;\n`POST /auth/delegation/jwt/assume-owned-group` remains authoritative and\nrechecks the exact NFT edge and selected human signer on chain.\n",
        "tags": [
          "Groups",
          "Delegation",
          "Accounts"
        ],
        "externalDocs": {
          "description": "Head Co → Subsidiary setup and operation guide",
          "url": "guides/group-account-ownership.md"
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "source_group_id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Active owned-group projections on the bearer session chain.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "source_group_id",
                    "chain_id",
                    "owned_groups"
                  ],
                  "properties": {
                    "source_group_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "chain_id": {
                      "type": "string",
                      "description": "Chain pinned by the validated direct delegation JWT."
                    },
                    "owned_groups": {
                      "type": "array",
                      "description": "Target groups sorted case-insensitively by name, then exact name and UUID.",
                      "items": {
                        "type": "object",
                        "required": [
                          "group",
                          "account_address",
                          "relationship_id",
                          "credential_contract",
                          "token_id",
                          "added_at"
                        ],
                        "description": "One active relationship generation binding the source-group account's\nimmutable credential to the target account as an exact NFT member.\nThis is a discovery record; JWT exchange performs fresh live edge and\nsigner-access checks.\n",
                        "properties": {
                          "group": {
                            "allOf": [
                              {
                                "type": "object",
                                "required": [
                                  "id",
                                  "name",
                                  "description",
                                  "group_type",
                                  "is_active",
                                  "created_at",
                                  "updated_at",
                                  "role",
                                  "account_address",
                                  "account_chain_id",
                                  "entity_verification_profile",
                                  "entity_verification_status"
                                ],
                                "description": "Group record shape returned by `/auth/groups` and friends. Account fields\nare explicitly chain-qualified: a null `account_address` means the group\nhas no default wallet on `account_chain_id`.\n",
                                "properties": {
                                  "id": {
                                    "type": "string",
                                    "format": "uuid"
                                  },
                                  "name": {
                                    "type": "string"
                                  },
                                  "description": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "group_type": {
                                    "type": "string",
                                    "enum": [
                                      "organization",
                                      "team",
                                      "project",
                                      "custom"
                                    ]
                                  },
                                  "role": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "description": "Current caller's off-chain membership role when the list endpoint supplies it."
                                  },
                                  "account_address": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "description": "Default account address on `account_chain_id`, or null when not activated there."
                                  },
                                  "account_chain_id": {
                                    "type": "string",
                                    "description": "Chain against which `account_address` was resolved."
                                  },
                                  "account_activation": {
                                    "oneOf": [
                                      {
                                        "type": "object",
                                        "required": [
                                          "status",
                                          "chain_id",
                                          "wallet_id",
                                          "account_address",
                                          "deployed"
                                        ],
                                        "properties": {
                                          "status": {
                                            "type": "string",
                                            "const": "ready"
                                          },
                                          "chain_id": {
                                            "type": "string"
                                          },
                                          "wallet_id": {
                                            "type": "string"
                                          },
                                          "account_address": {
                                            "type": "string",
                                            "pattern": "^0x[0-9a-fA-F]{40}$"
                                          },
                                          "deployed": {
                                            "type": "boolean",
                                            "description": "True only when this request completed a new deployment."
                                          }
                                        }
                                      },
                                      {
                                        "type": "object",
                                        "required": [
                                          "status",
                                          "chain_id",
                                          "message_id"
                                        ],
                                        "properties": {
                                          "status": {
                                            "type": "string",
                                            "const": "provisioning"
                                          },
                                          "chain_id": {
                                            "type": "string"
                                          },
                                          "message_id": {
                                            "type": [
                                              "string",
                                              "null"
                                            ],
                                            "format": "uuid"
                                          }
                                        }
                                      },
                                      {
                                        "type": "object",
                                        "required": [
                                          "status",
                                          "chain_id",
                                          "message_id"
                                        ],
                                        "properties": {
                                          "status": {
                                            "type": "string",
                                            "const": "pending_signature"
                                          },
                                          "chain_id": {
                                            "type": "string"
                                          },
                                          "message_id": {
                                            "type": "string",
                                            "format": "uuid"
                                          }
                                        }
                                      },
                                      {
                                        "type": "object",
                                        "required": [
                                          "status",
                                          "chain_id",
                                          "error"
                                        ],
                                        "properties": {
                                          "status": {
                                            "type": "string",
                                            "const": "failed_retryable"
                                          },
                                          "chain_id": {
                                            "type": "string"
                                          },
                                          "error": {
                                            "type": "string"
                                          }
                                        }
                                      }
                                    ],
                                    "discriminator": {
                                      "propertyName": "status"
                                    }
                                  },
                                  "billing_policy": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "enum": [
                                      "admin_pays",
                                      "member_pays",
                                      null
                                    ],
                                    "description": "Present on create when a declared policy committed atomically with the\ngroup. Omitted when no policy was declared or billing is disabled.\n"
                                  },
                                  "entity_verification_profile": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "description": "Bound entity/KYB verification profile, or null when the group is not declared as a business."
                                  },
                                  "entity_verification_status": {
                                    "type": "string",
                                    "enum": [
                                      "not_required",
                                      "unverified",
                                      "pending",
                                      "verified",
                                      "rejected",
                                      "expired"
                                    ],
                                    "description": "Derived current group KYB posture; always serialized."
                                  },
                                  "is_active": {
                                    "type": "boolean"
                                  },
                                  "created_at": {
                                    "type": "string",
                                    "format": "date-time"
                                  },
                                  "updated_at": {
                                    "type": "string",
                                    "format": "date-time"
                                  }
                                }
                              },
                              {
                                "type": "object",
                                "properties": {
                                  "role": {
                                    "type": "null",
                                    "description": "Ownership does not create or inherit an off-chain membership role."
                                  }
                                }
                              }
                            ]
                          },
                          "account_address": {
                            "type": "string",
                            "pattern": "^0x[0-9a-f]{40}$",
                            "description": "Canonical current default target account; identical to `group.account_address`."
                          },
                          "relationship_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "credential_contract": {
                            "type": "string",
                            "pattern": "^0x[0-9a-f]{40}$"
                          },
                          "token_id": {
                            "type": "string",
                            "pattern": "^[1-9][0-9]*$"
                          },
                          "added_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Source group exists but is inactive."
          },
          "401": {
            "description": "Missing or invalid bearer JWT."
          },
          "403": {
            "description": "Bearer is not the exact direct source-group delegation with `ReadGroup`."
          },
          "404": {
            "description": "Source group does not exist."
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/groups/{source_group_id}/owned-groups \\\n  -H \"Authorization: Bearer $DIRECT_GROUP_JWT\""
          }
        ]
      }
    },
    "/auth/groups/{child_group_id}/group-owners/{parent_group_id}": {
      "post": {
        "operationId": "establishGroupOwnershipRelationship",
        "summary": "Advance an immutable parent→child group relationship",
        "description": "Idempotently advances the multi-actor relationship saga. A direct child\nOwner reserves the edge; a direct parent Owner mints the immutable\ncredential to the parent account; a direct child Owner\nadds that exact NFT as a child-account member. Each on-chain operation\nuses the acting human's ordinary account-signing rail: custodial keys are\nAutomatic and external ECDSA keys are Manual. No parent/group key signs\nfor the child.\n\nRepeat the same request while a stage is pending. Route UX from the\nstable `stage`, `message_id`, `operation_id`, and `retry_required`\nfields. The active session chain and request `chain_id` must match.\n",
        "tags": [
          "Groups",
          "Accounts"
        ],
        "externalDocs": {
          "description": "Relationship ceremony and runtime guide",
          "url": "guides/group-account-ownership.md"
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "child_group_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "parent_group_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "chain_id"
                ],
                "properties": {
                  "chain_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Current durable relationship stage",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "relationship_id",
                    "parent_group_id",
                    "child_group_id",
                    "chain_id",
                    "status",
                    "stage",
                    "credential_contract",
                    "token_id",
                    "retry_required"
                  ],
                  "properties": {
                    "relationship_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "parent_group_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "child_group_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "chain_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "provisioning",
                        "active",
                        "failed_retryable",
                        "revoking",
                        "revoked"
                      ]
                    },
                    "stage": {
                      "type": "string",
                      "enum": [
                        "parent_credential_authorization_required",
                        "parent_credential_pending",
                        "parent_credential_retry_required",
                        "child_membership_authorization_required",
                        "child_membership_pending",
                        "child_membership_retry_required",
                        "active",
                        "revocation_pending",
                        "revocation_retry_required",
                        "revoked",
                        "relationship_retry_required"
                      ]
                    },
                    "credential_contract": {
                      "type": "string",
                      "pattern": "^0x[0-9a-f]{40}$"
                    },
                    "token_id": {
                      "type": "string",
                      "pattern": "^[1-9][0-9]*$"
                    },
                    "message_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "operation_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "retry_required": {
                      "type": "boolean"
                    },
                    "error": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid chain or self-relationship."
          },
          "401": {
            "description": "Missing, malformed, expired, or wrong-audience JWT (user or service).\nIncludes invalid API keys on the exchange endpoints, failed wallet\nsignatures on the signin endpoints, and failed Sumsub HMAC on the\nwebhook.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "no-token": {
                    "summary": "Missing Authorization header",
                    "value": {
                      "error": "missing bearer token"
                    }
                  },
                  "invalid-token": {
                    "summary": "JWT signature / structure invalid",
                    "value": {
                      "error": "invalid token"
                    }
                  },
                  "invalid-subject": {
                    "summary": "JWT sub field unparseable",
                    "value": {
                      "error": "invalid token subject"
                    }
                  },
                  "webhook-signature": {
                    "summary": "Sumsub HMAC verification failed",
                    "value": {
                      "error": "invalid webhook signature or payload"
                    }
                  },
                  "averer-reverify": {
                    "summary": "Averer flagged a re-verification requirement",
                    "value": {
                      "error": "averer_reverify_required"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "A direct human session with live owner authority for the current stage is required."
          },
          "409": {
            "description": "Stale account generation",
            "revoked generation": null,
            "or conflicting durable operation.": null
          },
          "503": {
            "description": "Group credential configuration or durable payments handoff is unavailable."
          }
        }
      },
      "delete": {
        "operationId": "removeGroupOwnershipRelationship",
        "summary": "Remove the exact parent→child NFT member edge",
        "description": "A direct child Owner removes the relationship's exact immutable NFT\nmember through the ordinary child-account operation rail. Repeat pending\nrequests until `revoked`; a revoked relationship generation cannot be\nreactivated.\n",
        "tags": [
          "Groups",
          "Accounts"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "child_group_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "parent_group_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "chain_id"
                ],
                "properties": {
                  "chain_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Current revocation stage",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "relationship_id",
                    "parent_group_id",
                    "child_group_id",
                    "chain_id",
                    "status",
                    "stage",
                    "credential_contract",
                    "token_id",
                    "retry_required"
                  ],
                  "properties": {
                    "relationship_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "parent_group_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "child_group_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "chain_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "provisioning",
                        "active",
                        "failed_retryable",
                        "revoking",
                        "revoked"
                      ]
                    },
                    "stage": {
                      "type": "string",
                      "enum": [
                        "parent_credential_authorization_required",
                        "parent_credential_pending",
                        "parent_credential_retry_required",
                        "child_membership_authorization_required",
                        "child_membership_pending",
                        "child_membership_retry_required",
                        "active",
                        "revocation_pending",
                        "revocation_retry_required",
                        "revoked",
                        "relationship_retry_required"
                      ]
                    },
                    "credential_contract": {
                      "type": "string",
                      "pattern": "^0x[0-9a-f]{40}$"
                    },
                    "token_id": {
                      "type": "string",
                      "pattern": "^[1-9][0-9]*$"
                    },
                    "message_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "operation_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "retry_required": {
                      "type": "boolean"
                    },
                    "error": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired, or wrong-audience JWT (user or service).\nIncludes invalid API keys on the exchange endpoints, failed wallet\nsignatures on the signin endpoints, and failed Sumsub HMAC on the\nwebhook.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "no-token": {
                    "summary": "Missing Authorization header",
                    "value": {
                      "error": "missing bearer token"
                    }
                  },
                  "invalid-token": {
                    "summary": "JWT signature / structure invalid",
                    "value": {
                      "error": "invalid token"
                    }
                  },
                  "invalid-subject": {
                    "summary": "JWT sub field unparseable",
                    "value": {
                      "error": "invalid token subject"
                    }
                  },
                  "webhook-signature": {
                    "summary": "Sumsub HMAC verification failed",
                    "value": {
                      "error": "invalid webhook signature or payload"
                    }
                  },
                  "averer-reverify": {
                    "summary": "Averer flagged a re-verification requirement",
                    "value": {
                      "error": "averer_reverify_required"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Direct child-owner authority required."
          },
          "404": {
            "description": "Relationship not found."
          },
          "409": {
            "description": "Relationship is not active or revoking."
          }
        }
      }
    },
    "/auth/groups/{child_group_id}/group-owners": {
      "get": {
        "operationId": "listGroupOwnershipRelationships",
        "summary": "List relationship generations for a child group",
        "description": "Returns relationships for the caller's active chain with their stable\nsaga stage. Requires normal read access to the child group.\n",
        "tags": [
          "Groups",
          "Accounts"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "child_group_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Child relationships",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "child_group_id",
                    "chain_id",
                    "relationships"
                  ],
                  "properties": {
                    "child_group_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "chain_id": {
                      "type": "string"
                    },
                    "relationships": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "relationship_id",
                          "parent_group_id",
                          "child_group_id",
                          "chain_id",
                          "status",
                          "stage",
                          "credential_contract",
                          "token_id",
                          "retry_required"
                        ],
                        "properties": {
                          "relationship_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "parent_group_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "child_group_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "chain_id": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "provisioning",
                              "active",
                              "failed_retryable",
                              "revoking",
                              "revoked"
                            ]
                          },
                          "stage": {
                            "type": "string",
                            "enum": [
                              "parent_credential_authorization_required",
                              "parent_credential_pending",
                              "parent_credential_retry_required",
                              "child_membership_authorization_required",
                              "child_membership_pending",
                              "child_membership_retry_required",
                              "active",
                              "revocation_pending",
                              "revocation_retry_required",
                              "revoked",
                              "relationship_retry_required"
                            ]
                          },
                          "credential_contract": {
                            "type": "string",
                            "pattern": "^0x[0-9a-f]{40}$"
                          },
                          "token_id": {
                            "type": "string",
                            "pattern": "^[1-9][0-9]*$"
                          },
                          "message_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid"
                          },
                          "operation_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid"
                          },
                          "retry_required": {
                            "type": "boolean"
                          },
                          "error": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired, or wrong-audience JWT (user or service).\nIncludes invalid API keys on the exchange endpoints, failed wallet\nsignatures on the signin endpoints, and failed Sumsub HMAC on the\nwebhook.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "no-token": {
                    "summary": "Missing Authorization header",
                    "value": {
                      "error": "missing bearer token"
                    }
                  },
                  "invalid-token": {
                    "summary": "JWT signature / structure invalid",
                    "value": {
                      "error": "invalid token"
                    }
                  },
                  "invalid-subject": {
                    "summary": "JWT sub field unparseable",
                    "value": {
                      "error": "invalid token subject"
                    }
                  },
                  "webhook-signature": {
                    "summary": "Sumsub HMAC verification failed",
                    "value": {
                      "error": "invalid webhook signature or payload"
                    }
                  },
                  "averer-reverify": {
                    "summary": "Averer flagged a re-verification requirement",
                    "value": {
                      "error": "averer_reverify_required"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Child-group read access required."
          }
        }
      }
    },
    "/auth/groups/{id}": {
      "get": {
        "operationId": "getGroup",
        "summary": "Get a group by ID",
        "tags": [
          "Groups"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Group UUID. (Auth uses `{id}` for group paths and `{user_id}` for user paths — kept distinct so the URL itself reads as the entity kind.)"
          }
        ],
        "responses": {
          "200": {
            "description": "Group profile",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "name",
                    "description",
                    "group_type",
                    "is_active",
                    "created_at",
                    "updated_at",
                    "role",
                    "account_address",
                    "account_chain_id",
                    "entity_verification_profile",
                    "entity_verification_status"
                  ],
                  "description": "Group record shape returned by `/auth/groups` and friends. Account fields\nare explicitly chain-qualified: a null `account_address` means the group\nhas no default wallet on `account_chain_id`.\n",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "description": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "group_type": {
                      "type": "string",
                      "enum": [
                        "organization",
                        "team",
                        "project",
                        "custom"
                      ]
                    },
                    "role": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Current caller's off-chain membership role when the list endpoint supplies it."
                    },
                    "account_address": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Default account address on `account_chain_id`, or null when not activated there."
                    },
                    "account_chain_id": {
                      "type": "string",
                      "description": "Chain against which `account_address` was resolved."
                    },
                    "account_activation": {
                      "oneOf": [
                        {
                          "type": "object",
                          "required": [
                            "status",
                            "chain_id",
                            "wallet_id",
                            "account_address",
                            "deployed"
                          ],
                          "properties": {
                            "status": {
                              "type": "string",
                              "const": "ready"
                            },
                            "chain_id": {
                              "type": "string"
                            },
                            "wallet_id": {
                              "type": "string"
                            },
                            "account_address": {
                              "type": "string",
                              "pattern": "^0x[0-9a-fA-F]{40}$"
                            },
                            "deployed": {
                              "type": "boolean",
                              "description": "True only when this request completed a new deployment."
                            }
                          }
                        },
                        {
                          "type": "object",
                          "required": [
                            "status",
                            "chain_id",
                            "message_id"
                          ],
                          "properties": {
                            "status": {
                              "type": "string",
                              "const": "provisioning"
                            },
                            "chain_id": {
                              "type": "string"
                            },
                            "message_id": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "uuid"
                            }
                          }
                        },
                        {
                          "type": "object",
                          "required": [
                            "status",
                            "chain_id",
                            "message_id"
                          ],
                          "properties": {
                            "status": {
                              "type": "string",
                              "const": "pending_signature"
                            },
                            "chain_id": {
                              "type": "string"
                            },
                            "message_id": {
                              "type": "string",
                              "format": "uuid"
                            }
                          }
                        },
                        {
                          "type": "object",
                          "required": [
                            "status",
                            "chain_id",
                            "error"
                          ],
                          "properties": {
                            "status": {
                              "type": "string",
                              "const": "failed_retryable"
                            },
                            "chain_id": {
                              "type": "string"
                            },
                            "error": {
                              "type": "string"
                            }
                          }
                        }
                      ],
                      "discriminator": {
                        "propertyName": "status"
                      }
                    },
                    "billing_policy": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "admin_pays",
                        "member_pays",
                        null
                      ],
                      "description": "Present on create when a declared policy committed atomically with the\ngroup. Omitted when no policy was declared or billing is disabled.\n"
                    },
                    "entity_verification_profile": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Bound entity/KYB verification profile, or null when the group is not declared as a business."
                    },
                    "entity_verification_status": {
                      "type": "string",
                      "enum": [
                        "not_required",
                        "unverified",
                        "pending",
                        "verified",
                        "rejected",
                        "expired"
                      ],
                      "description": "Derived current group KYB posture; always serialized."
                    },
                    "is_active": {
                      "type": "boolean"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (user, group, key, delegation token, wallet, provider).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "keypair": {
                    "summary": "Keypair UUID not found",
                    "value": {
                      "error": "keypair not found"
                    }
                  },
                  "kyc-provider": {
                    "summary": "Requested KYC provider not enabled in yaml",
                    "value": {
                      "error": "kyc provider not enabled"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/groups/{id} \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/groups/{id}', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/groups/{id}'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      },
      "put": {
        "operationId": "updateGroup",
        "summary": "Update group metadata",
        "tags": [
          "Groups"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Group UUID. (Auth uses `{id}` for group paths and `{user_id}` for user paths — kept distinct so the URL itself reads as the entity kind.)"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "group_type": {
                    "type": "string",
                    "description": "Group flavor. Drives default permissions, entity-scope hints, and\non-chain account naming.\n",
                    "enum": [
                      "Organization",
                      "Team",
                      "Project",
                      "Custom"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Group updated"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X PUT https://auth.yieldfabric.com/auth/groups/{id} \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"name\": \"<string>\",\n  \"description\": \"<string>\",\n  \"group_type\": \"<see GroupType>\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/groups/{id}', {\n  method: 'PUT',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"name\": \"<string>\",\n    \"description\": \"<string>\",\n    \"group_type\": \"<see GroupType>\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/groups/{id}'\nresp = httpx.put(url, headers=headers, json={\"name\": \"<string>\", \"description\": \"<string>\", \"group_type\": \"<see GroupType>\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      },
      "delete": {
        "operationId": "deleteGroup",
        "summary": "Delete a group (owner only)",
        "description": "Cascade-deletes group members, permissions, scope, and audit log.\nRefuses to delete groups with an active on-chain account, a pending\naccount operation, or any retained Head Co → Subsidiary ownership\nrelationship history. Deactivate a relationship-bearing group instead;\nrelationship generations remain as an authorization audit trail.\n",
        "tags": [
          "Groups"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Group UUID. (Auth uses `{id}` for group paths and `{user_id}` for user paths — kept distinct so the URL itself reads as the entity kind.)"
          }
        ],
        "responses": {
          "204": {
            "description": "Group deleted"
          },
          "409": {
            "description": "Request would violate a uniqueness or state invariant. Examples:\ncreating a user with an email that already exists; submitting a\nlockdown request while one is already pending; cancelling a\nlockdown that has already been cancelled.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "lockdown-already-cancelled": {
                    "value": {
                      "error": "lockdown_already_cancelled"
                    }
                  },
                  "cooldown-not-elapsed": {
                    "value": {
                      "error": "cooldown_not_elapsed"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X DELETE https://auth.yieldfabric.com/auth/groups/{id} \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/groups/{id}', {\n  method: 'DELETE',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/groups/{id}'\nresp = httpx.delete(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/groups/{id}/members": {
      "get": {
        "operationId": "listGroupMembers",
        "summary": "List group members",
        "tags": [
          "Groups"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Group UUID. (Auth uses `{id}` for group paths and `{user_id}` for user paths — kept distinct so the URL itself reads as the entity kind.)"
          }
        ],
        "responses": {
          "200": {
            "description": "Members",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "required": [
                      "user_id",
                      "role"
                    ],
                    "properties": {
                      "user_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "role": {
                        "type": "string",
                        "enum": [
                          "owner",
                          "admin",
                          "member"
                        ]
                      },
                      "added_at": {
                        "type": "string",
                        "format": "date-time"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/groups/{id}/members \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/groups/{id}/members', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/groups/{id}/members'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      },
      "post": {
        "operationId": "addGroupMember",
        "summary": "Add a user to a group with a role",
        "tags": [
          "Groups"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Group UUID. (Auth uses `{id}` for group paths and `{user_id}` for user paths — kept distinct so the URL itself reads as the entity kind.)"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "user_id",
                  "role"
                ],
                "properties": {
                  "user_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "owner",
                      "admin",
                      "member"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Member added"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/groups/{id}/members \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"user_id\": \"00000000-0000-0000-0000-000000000000\",\n  \"role\": \"owner\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/groups/{id}/members', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"user_id\": \"00000000-0000-0000-0000-000000000000\",\n    \"role\": \"owner\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/groups/{id}/members'\nresp = httpx.post(url, headers=headers, json={\"user_id\": \"00000000-0000-0000-0000-000000000000\", \"role\": \"owner\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/groups/{id}/members/{user_id}": {
      "put": {
        "operationId": "updateMemberRole",
        "summary": "Change a member's role",
        "tags": [
          "Groups"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Group UUID. (Auth uses `{id}` for group paths and `{user_id}` for user paths — kept distinct so the URL itself reads as the entity kind.)"
          },
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "User UUID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "role"
                ],
                "properties": {
                  "role": {
                    "type": "string",
                    "enum": [
                      "owner",
                      "admin",
                      "member"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Role updated"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X PUT https://auth.yieldfabric.com/auth/groups/{id}/members/{user_id} \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"role\": \"owner\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/groups/{id}/members/{user_id}', {\n  method: 'PUT',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"role\": \"owner\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/groups/{id}/members/{user_id}'\nresp = httpx.put(url, headers=headers, json={\"role\": \"owner\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      },
      "delete": {
        "operationId": "removeGroupMember",
        "summary": "Remove a user from a group",
        "tags": [
          "Groups"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Group UUID. (Auth uses `{id}` for group paths and `{user_id}` for user paths — kept distinct so the URL itself reads as the entity kind.)"
          },
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "User UUID."
          }
        ],
        "responses": {
          "204": {
            "description": "Member removed"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X DELETE https://auth.yieldfabric.com/auth/groups/{id}/members/{user_id} \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/groups/{id}/members/{user_id}', {\n  method: 'DELETE',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/groups/{id}/members/{user_id}'\nresp = httpx.delete(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/groups/{id}/keypairs": {
      "get": {
        "operationId": "listGroupKeypairs",
        "summary": "List a group's keypairs",
        "tags": [
          "Groups",
          "Keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Group UUID. (Auth uses `{id}` for group paths and `{user_id}` for user paths — kept distinct so the URL itself reads as the entity kind.)"
          }
        ],
        "responses": {
          "200": {
            "description": "Keypair list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/groups/{id}/keypairs \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/groups/{id}/keypairs', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/groups/{id}/keypairs'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      },
      "post": {
        "operationId": "createGroupKeypair",
        "summary": "Create a new group keypair",
        "tags": [
          "Groups",
          "Keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Group UUID. (Auth uses `{id}` for group paths and `{user_id}` for user paths — kept distinct so the URL itself reads as the entity kind.)"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "key_type": {
                    "type": "string",
                    "description": "Stored-keypair purpose. `Signing` = secp256k1 used for EIP-191\nsignatures and account ownership; `Encryption` = secp256k1 used as\nan ECIES recipient endpoint; `Hybrid` = doubles as both (legacy\nemail-user shape); `External` = wallet-led signup, no private key\nheld server-side.\n",
                    "enum": [
                      "Signing",
                      "Encryption",
                      "Hybrid",
                      "External"
                    ]
                  },
                  "name": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Keypair created"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/groups/{id}/keypairs \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"key_type\": \"<see KeyType>\",\n  \"name\": \"<string>\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/groups/{id}/keypairs', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"key_type\": \"<see KeyType>\",\n    \"name\": \"<string>\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/groups/{id}/keypairs'\nresp = httpx.post(url, headers=headers, json={\"key_type\": \"<see KeyType>\", \"name\": \"<string>\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/groups/{id}/entity-scope": {
      "get": {
        "operationId": "listEntityScope",
        "summary": "List the group's entity scope",
        "tags": [
          "Groups"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Group UUID. (Auth uses `{id}` for group paths and `{user_id}` for user paths — kept distinct so the URL itself reads as the entity kind.)"
          }
        ],
        "responses": {
          "200": {
            "description": "Entity-scope entries",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "entity_type": {
                        "type": "string",
                        "enum": [
                          "user",
                          "group",
                          "auction"
                        ]
                      },
                      "entity_id": {
                        "type": "string",
                        "format": "uuid"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/groups/{id}/entity-scope \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/groups/{id}/entity-scope', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/groups/{id}/entity-scope'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      },
      "post": {
        "operationId": "addEntityScope",
        "summary": "Add entries to the group's entity scope",
        "tags": [
          "Groups"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Group UUID. (Auth uses `{id}` for group paths and `{user_id}` for user paths — kept distinct so the URL itself reads as the entity kind.)"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "entries"
                ],
                "properties": {
                  "entries": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "entity_type": {
                          "type": "string"
                        },
                        "entity_id": {
                          "type": "string",
                          "format": "uuid"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Scope updated"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/groups/{id}/entity-scope \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"entries\": [\n    {\n      \"entity_type\": \"<string>\",\n      \"entity_id\": \"00000000-0000-0000-0000-000000000000\"\n    }\n  ]\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/groups/{id}/entity-scope', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"entries\": [\n      {\n        \"entity_type\": \"<string>\",\n        \"entity_id\": \"00000000-0000-0000-0000-000000000000\"\n      }\n    ]\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/groups/{id}/entity-scope'\nresp = httpx.post(url, headers=headers, json={\"entries\": [{\"entity_type\": \"<string>\", \"entity_id\": \"00000000-0000-0000-0000-000000000000\"}]})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/groups/{id}/audit-logs": {
      "get": {
        "operationId": "getGroupAuditLogs",
        "summary": "Read the group's audit log",
        "tags": [
          "Groups"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Group UUID. (Auth uses `{id}` for group paths and `{user_id}` for user paths — kept distinct so the URL itself reads as the entity kind.)"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 100
            },
            "description": "Max items to return."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Items to skip before returning."
          }
        ],
        "responses": {
          "200": {
            "description": "Audit entries",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "action": {
                        "type": "string"
                      },
                      "actor_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "occurred_at": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "payload": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/groups/{id}/audit-logs \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/groups/{id}/audit-logs', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/groups/{id}/audit-logs'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/groups/{id}/deploy-account": {
      "post": {
        "operationId": "deployGroupAccount",
        "summary": "Activate the group's account on the caller's chain (compatibility route)",
        "description": "Thin compatibility wrapper around canonical chain-account activation for\nthe authenticated JWT's `default_chain_id`. It returns the same tagged\nlifecycle and does not implement a second deployment path. New clients\nshould use\n`POST /entities/group/{id}/chain-accounts/{chain_id}/activation` so the\nchain is explicit. A normal user session requires `CreateGroup`; an\nacting-as session must target this exact group and carry\n`CryptoOperations` in its delegation scope. Active group Owner/Admin\nauthority is required in both cases.\n",
        "tags": [
          "Groups",
          "Accounts"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Group UUID. (Auth uses `{id}` for group paths and `{user_id}` for user paths — kept distinct so the URL itself reads as the entity kind.)"
          }
        ],
        "responses": {
          "200": {
            "description": "Account ready",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "chain_id",
                        "wallet_id",
                        "account_address",
                        "deployed"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "ready"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "wallet_id": {
                          "type": "string"
                        },
                        "account_address": {
                          "type": "string",
                          "pattern": "^0x[0-9a-fA-F]{40}$"
                        },
                        "deployed": {
                          "type": "boolean",
                          "description": "True only when this request completed a new deployment."
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "chain_id",
                        "message_id"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "provisioning"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "message_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "chain_id",
                        "message_id"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "pending_signature"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "message_id": {
                          "type": "string",
                          "format": "uuid"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "chain_id",
                        "error"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "failed_retryable"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "error": {
                          "type": "string"
                        }
                      }
                    }
                  ],
                  "discriminator": {
                    "propertyName": "status"
                  }
                }
              }
            }
          },
          "202": {
            "description": "Activation is provisioning or pending a signature.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "chain_id",
                        "wallet_id",
                        "account_address",
                        "deployed"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "ready"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "wallet_id": {
                          "type": "string"
                        },
                        "account_address": {
                          "type": "string",
                          "pattern": "^0x[0-9a-fA-F]{40}$"
                        },
                        "deployed": {
                          "type": "boolean",
                          "description": "True only when this request completed a new deployment."
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "chain_id",
                        "message_id"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "provisioning"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "message_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "chain_id",
                        "message_id"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "pending_signature"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "message_id": {
                          "type": "string",
                          "format": "uuid"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "chain_id",
                        "error"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "failed_retryable"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "error": {
                          "type": "string"
                        }
                      }
                    }
                  ],
                  "discriminator": {
                    "propertyName": "status"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller lacks the required ordinary/delegated permission",
            "exact group scope": null,
            "group authority": null,
            "or activation verification tier.": null
          },
          "404": {
            "description": "Group not found."
          },
          "409": {
            "description": "Activation reached `failed_retryable`, or the chain is disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "chain_id",
                        "wallet_id",
                        "account_address",
                        "deployed"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "ready"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "wallet_id": {
                          "type": "string"
                        },
                        "account_address": {
                          "type": "string",
                          "pattern": "^0x[0-9a-fA-F]{40}$"
                        },
                        "deployed": {
                          "type": "boolean",
                          "description": "True only when this request completed a new deployment."
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "chain_id",
                        "message_id"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "provisioning"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "message_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "chain_id",
                        "message_id"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "pending_signature"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "message_id": {
                          "type": "string",
                          "format": "uuid"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "chain_id",
                        "error"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "failed_retryable"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "error": {
                          "type": "string"
                        }
                      }
                    }
                  ],
                  "discriminator": {
                    "propertyName": "status"
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/groups/{id}/deploy-account \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/groups/{id}/deploy-account', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/groups/{id}/deploy-account'\nresp = httpx.post(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/groups/{id}/deploy-wallet-account": {
      "post": {
        "operationId": "deployGroupWalletAccount",
        "summary": "Deploy an additional wallet for an existing group",
        "description": "Reuses the group's existing ONCHAINID. The authenticated user must be a\ncurrent group Owner or Admin. When using an acting-as delegation, its\ngroup must exactly match `{id}` and its delegation scope must contain\n`CryptoOperations`. The activating user's direct Signing key—not a\npersonal smart-account address—is installed as the new account owner.\n",
        "tags": [
          "Groups",
          "Accounts"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Group UUID. (Auth uses `{id}` for group paths and `{user_id}` for user paths — kept distinct so the URL itself reads as the entity kind.)"
          }
        ],
        "responses": {
          "200": {
            "description": "Wallet account deployed"
          },
          "403": {
            "description": "Caller is not a group Owner/Admin, or delegation group/scope is invalid"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/groups/{id}/deploy-wallet-account \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/groups/{id}/deploy-wallet-account', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/groups/{id}/deploy-wallet-account'\nresp = httpx.post(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/groups/{id}/account-status": {
      "get": {
        "operationId": "getGroupAccountStatus",
        "summary": "Group on-chain account status",
        "tags": [
          "Groups",
          "Accounts"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Group UUID. (Auth uses `{id}` for group paths and `{user_id}` for user paths — kept distinct so the URL itself reads as the entity kind.)"
          }
        ],
        "responses": {
          "200": {
            "description": "Status on the authenticated JWT's chain.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "account_status"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "const": true
                    },
                    "account_status": {
                      "oneOf": [
                        {
                          "type": "object",
                          "required": [
                            "status",
                            "account_address",
                            "chain_id",
                            "group_id",
                            "group_name"
                          ],
                          "properties": {
                            "status": {
                              "type": "string",
                              "const": "deployed"
                            },
                            "account_address": {
                              "type": "string",
                              "pattern": "^0x[0-9a-fA-F]{40}$"
                            },
                            "chain_id": {
                              "type": "string"
                            },
                            "group_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "group_name": {
                              "type": "string"
                            }
                          }
                        },
                        {
                          "type": "object",
                          "required": [
                            "status",
                            "chain_id",
                            "group_id",
                            "group_name",
                            "needs_deployment"
                          ],
                          "properties": {
                            "status": {
                              "type": "string",
                              "const": "not_deployed"
                            },
                            "chain_id": {
                              "type": "string"
                            },
                            "group_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "group_name": {
                              "type": "string"
                            },
                            "needs_deployment": {
                              "type": "boolean",
                              "const": true
                            }
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/groups/{id}/account-status \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/groups/{id}/account-status', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/groups/{id}/account-status'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/groups/{id}/add-owner": {
      "post": {
        "operationId": "addGroupOwner",
        "summary": "Register a new owner on the group's on-chain account",
        "tags": [
          "Groups",
          "Accounts"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Group UUID. (Auth uses `{id}` for group paths and `{user_id}` for user paths — kept distinct so the URL itself reads as the entity kind.)"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "new_owner",
                  "chain_id"
                ],
                "properties": {
                  "new_owner": {
                    "type": "string",
                    "description": "Ethereum address, keypair UUID, or user UUID."
                  },
                  "chain_id": {
                    "type": "string",
                    "description": "Required assertion matching the JWT chain."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Durable operation envelope (`confirmed`, `pending_signature`, `retry_required`, or `failed`)."
          },
          "409": {
            "description": "The exact user/group has no smart account on the JWT-selected chain.\nThe handler checked this before durable mutation or MQ handoff. Activate\nthe returned entity/chain, refresh the same actor session, and retry once.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "code",
                    "entity_kind",
                    "entity_id",
                    "chain_id"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "const": "CHAIN_ACCOUNT_REQUIRED"
                    },
                    "entity_kind": {
                      "type": "string",
                      "enum": [
                        "user",
                        "group"
                      ]
                    },
                    "entity_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "chain_id": {
                      "type": "string"
                    },
                    "activation_url": {
                      "type": "string",
                      "description": "Exact recovery resource when the handler can name it."
                    },
                    "operation": {
                      "type": "string",
                      "description": "Stable operation label for diagnostics; clients route on `code` and the entity/chain fields."
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/groups/{id}/add-owner \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"new_owner\": \"<address-or-uuid>\",\n  \"chain_id\": \"153\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/groups/{id}/add-owner', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({ new_owner: '<address-or-uuid>', chain_id: '153' }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/groups/{id}/add-owner'\nresp = httpx.post(url, headers=headers, json={'new_owner': '<address-or-uuid>', 'chain_id': '153'})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/groups/{id}/remove-owner": {
      "post": {
        "operationId": "removeGroupOwner",
        "summary": "Remove an owner from the group's on-chain account",
        "tags": [
          "Groups",
          "Accounts"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Group UUID. (Auth uses `{id}` for group paths and `{user_id}` for user paths — kept distinct so the URL itself reads as the entity kind.)"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "old_owner",
                  "chain_id"
                ],
                "properties": {
                  "old_owner": {
                    "type": "string",
                    "pattern": "^0x[0-9a-fA-F]{40}$"
                  },
                  "chain_id": {
                    "type": "string",
                    "description": "Required assertion matching the JWT chain."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Durable operation envelope (`confirmed`, `pending_signature`, `retry_required`, or `failed`)."
          },
          "409": {
            "description": "The exact user/group has no smart account on the JWT-selected chain.\nThe handler checked this before durable mutation or MQ handoff. Activate\nthe returned entity/chain, refresh the same actor session, and retry once.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "code",
                    "entity_kind",
                    "entity_id",
                    "chain_id"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "const": "CHAIN_ACCOUNT_REQUIRED"
                    },
                    "entity_kind": {
                      "type": "string",
                      "enum": [
                        "user",
                        "group"
                      ]
                    },
                    "entity_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "chain_id": {
                      "type": "string"
                    },
                    "activation_url": {
                      "type": "string",
                      "description": "Exact recovery resource when the handler can name it."
                    },
                    "operation": {
                      "type": "string",
                      "description": "Stable operation label for diagnostics; clients route on `code` and the entity/chain fields."
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/groups/{id}/remove-owner \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"old_owner\": \"0x...\",\n  \"chain_id\": \"153\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/groups/{id}/remove-owner', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({ old_owner: '0x...', chain_id: '153' }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/groups/{id}/remove-owner'\nresp = httpx.post(url, headers=headers, json={'old_owner': '0x...', 'chain_id': '153'})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/groups/{id}/add-account-member": {
      "post": {
        "operationId": "addAccountMember",
        "summary": "Add an NFT- or address-gated account member",
        "description": "`obligation_address` is optional: when omitted, auth resolves it\nto the chain default by querying payments. The **resolved**\naddress is persisted on the account-member record and forwarded\nto the on-chain `AddMember` submission — auth never stores or\nforwards an empty string. See\n[guides/groups.md](../guides/groups.md) for the NFT-gating\nsemantics.\n",
        "tags": [
          "Groups",
          "Accounts"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Group UUID. (Auth uses `{id}` for group paths and `{user_id}` for user paths — kept distinct so the URL itself reads as the entity kind.)"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "obligation_id",
                  "chain_id"
                ],
                "properties": {
                  "obligation_id": {
                    "type": "string",
                    "description": "U256 token id or contract_id alias resolvable via payments."
                  },
                  "obligation_address": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Optional. Resolved to chain default if omitted."
                  },
                  "chain_id": {
                    "type": "string",
                    "description": "Required assertion matching the JWT chain."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Durable operation envelope (`confirmed`, `pending_signature`, `retry_required`, or `failed`)."
          },
          "409": {
            "description": "The exact user/group has no smart account on the JWT-selected chain.\nThe handler checked this before durable mutation or MQ handoff. Activate\nthe returned entity/chain, refresh the same actor session, and retry once.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "code",
                    "entity_kind",
                    "entity_id",
                    "chain_id"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "const": "CHAIN_ACCOUNT_REQUIRED"
                    },
                    "entity_kind": {
                      "type": "string",
                      "enum": [
                        "user",
                        "group"
                      ]
                    },
                    "entity_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "chain_id": {
                      "type": "string"
                    },
                    "activation_url": {
                      "type": "string",
                      "description": "Exact recovery resource when the handler can name it."
                    },
                    "operation": {
                      "type": "string",
                      "description": "Stable operation label for diagnostics; clients route on `code` and the entity/chain fields."
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/groups/{id}/add-account-member \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"obligation_id\": \"<string>\",\n  \"chain_id\": \"153\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/groups/{id}/add-account-member', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({ obligation_id: '<string>', chain_id: '153' }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/groups/{id}/add-account-member'\nresp = httpx.post(url, headers=headers, json={'obligation_id': '<string>', 'chain_id': '153'})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/groups/{id}/remove-account-member": {
      "post": {
        "operationId": "removeAccountMember",
        "summary": "Remove an account member",
        "tags": [
          "Groups",
          "Accounts"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Group UUID. (Auth uses `{id}` for group paths and `{user_id}` for user paths — kept distinct so the URL itself reads as the entity kind.)"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "obligation_id",
                  "chain_id"
                ],
                "properties": {
                  "obligation_id": {
                    "type": "string"
                  },
                  "obligation_address": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "chain_id": {
                    "type": "string",
                    "description": "Required assertion matching the JWT chain."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Durable operation envelope (`confirmed`, `pending_signature`, `retry_required`, or `failed`)."
          },
          "409": {
            "description": "The exact user/group has no smart account on the JWT-selected chain.\nThe handler checked this before durable mutation or MQ handoff. Activate\nthe returned entity/chain, refresh the same actor session, and retry once.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "code",
                    "entity_kind",
                    "entity_id",
                    "chain_id"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "const": "CHAIN_ACCOUNT_REQUIRED"
                    },
                    "entity_kind": {
                      "type": "string",
                      "enum": [
                        "user",
                        "group"
                      ]
                    },
                    "entity_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "chain_id": {
                      "type": "string"
                    },
                    "activation_url": {
                      "type": "string",
                      "description": "Exact recovery resource when the handler can name it."
                    },
                    "operation": {
                      "type": "string",
                      "description": "Stable operation label for diagnostics; clients route on `code` and the entity/chain fields."
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/groups/{id}/remove-account-member \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"obligation_id\": \"<string>\",\n  \"chain_id\": \"153\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/groups/{id}/remove-account-member', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({ obligation_id: '<string>', chain_id: '153' }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/groups/{id}/remove-account-member'\nresp = httpx.post(url, headers=headers, json={'obligation_id': '<string>', 'chain_id': '153'})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/groups/{id}/account-operations/{operation_id}": {
      "get": {
        "operationId": "getGroupAccountOperation",
        "summary": "Reconcile and read a durable group account mutation",
        "tags": [
          "Groups",
          "Accounts"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Group UUID. (Auth uses `{id}` for group paths and `{user_id}` for user paths — kept distinct so the URL itself reads as the entity kind.)"
          },
          {
            "in": "path",
            "name": "operation_id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Durable operation status; browser polling is optional because auth also reconciles in the background.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "status",
                    "operation_id",
                    "message_id",
                    "group_id",
                    "chain_id",
                    "account_address",
                    "resource",
                    "desired_present",
                    "target_address",
                    "target_id",
                    "requested_target_id",
                    "requested_target_ref",
                    "keypair_id",
                    "membership_changed",
                    "error",
                    "retry_required"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "confirmed",
                        "pending",
                        "retry_required",
                        "failed"
                      ]
                    },
                    "operation_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "message_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "group_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "chain_id": {
                      "type": "string"
                    },
                    "account_address": {
                      "type": "string"
                    },
                    "resource": {
                      "type": "string",
                      "enum": [
                        "owner",
                        "member"
                      ]
                    },
                    "desired_present": {
                      "type": "boolean"
                    },
                    "target_address": {
                      "type": "string"
                    },
                    "target_id": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "requested_target_id": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "requested_target_ref": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "keypair_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "membership_changed": {
                      "type": [
                        "boolean",
                        "null"
                      ]
                    },
                    "error": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "retry_required": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (user, group, key, delegation token, wallet, provider).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "keypair": {
                    "summary": "Keypair UUID not found",
                    "value": {
                      "error": "keypair not found"
                    }
                  },
                  "kyc-provider": {
                    "summary": "Requested KYC provider not enabled in yaml",
                    "value": {
                      "error": "kyc provider not enabled"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The exact user/group has no smart account on the JWT-selected chain.\nThe handler checked this before durable mutation or MQ handoff. Activate\nthe returned entity/chain, refresh the same actor session, and retry once.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "code",
                    "entity_kind",
                    "entity_id",
                    "chain_id"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "const": "CHAIN_ACCOUNT_REQUIRED"
                    },
                    "entity_kind": {
                      "type": "string",
                      "enum": [
                        "user",
                        "group"
                      ]
                    },
                    "entity_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "chain_id": {
                      "type": "string"
                    },
                    "activation_url": {
                      "type": "string",
                      "description": "Exact recovery resource when the handler can name it."
                    },
                    "operation": {
                      "type": "string",
                      "description": "Stable operation label for diagnostics; clients route on `code` and the entity/chain fields."
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/auth/groups/{id}/account-owners": {
      "get": {
        "operationId": "getGroupAccountOwners",
        "summary": "List on-chain owners of the group's account",
        "tags": [
          "Groups",
          "Accounts"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Group UUID. (Auth uses `{id}` for group paths and `{user_id}` for user paths — kept distinct so the URL itself reads as the entity kind.)"
          }
        ],
        "responses": {
          "200": {
            "description": "Owners (and the keys/users that map to each)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "key_address": {
                        "type": "string"
                      },
                      "user_id": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "format": "uuid"
                      },
                      "is_active": {
                        "type": "boolean"
                      }
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The exact user/group has no smart account on the JWT-selected chain.\nThe handler checked this before durable mutation or MQ handoff. Activate\nthe returned entity/chain, refresh the same actor session, and retry once.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "code",
                    "entity_kind",
                    "entity_id",
                    "chain_id"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "const": "CHAIN_ACCOUNT_REQUIRED"
                    },
                    "entity_kind": {
                      "type": "string",
                      "enum": [
                        "user",
                        "group"
                      ]
                    },
                    "entity_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "chain_id": {
                      "type": "string"
                    },
                    "activation_url": {
                      "type": "string",
                      "description": "Exact recovery resource when the handler can name it."
                    },
                    "operation": {
                      "type": "string",
                      "description": "Stable operation label for diagnostics; clients route on `code` and the entity/chain fields."
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/groups/{id}/account-owners \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/groups/{id}/account-owners', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/groups/{id}/account-owners'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/groups/{id}/account-members": {
      "get": {
        "operationId": "getGroupAccountMembers",
        "summary": "List account members (NFT- and address-gated)",
        "tags": [
          "Groups",
          "Accounts"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Group UUID. (Auth uses `{id}` for group paths and `{user_id}` for user paths — kept distinct so the URL itself reads as the entity kind.)"
          }
        ],
        "responses": {
          "200": {
            "description": "Members",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "member_type": {
                        "type": "string",
                        "enum": [
                          "nft",
                          "address"
                        ]
                      },
                      "obligation_address": {
                        "type": "string"
                      },
                      "obligation_id": {
                        "type": "string"
                      },
                      "is_active": {
                        "type": "boolean"
                      }
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The exact user/group has no smart account on the JWT-selected chain.\nThe handler checked this before durable mutation or MQ handoff. Activate\nthe returned entity/chain, refresh the same actor session, and retry once.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "code",
                    "entity_kind",
                    "entity_id",
                    "chain_id"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "const": "CHAIN_ACCOUNT_REQUIRED"
                    },
                    "entity_kind": {
                      "type": "string",
                      "enum": [
                        "user",
                        "group"
                      ]
                    },
                    "entity_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "chain_id": {
                      "type": "string"
                    },
                    "activation_url": {
                      "type": "string",
                      "description": "Exact recovery resource when the handler can name it."
                    },
                    "operation": {
                      "type": "string",
                      "description": "Stable operation label for diagnostics; clients route on `code` and the entity/chain fields."
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/groups/{id}/account-members \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/groups/{id}/account-members', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/groups/{id}/account-members'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/delegation/tokens": {
      "get": {
        "operationId": "listMyDelegations",
        "summary": "List delegation tokens for the current user",
        "description": "Requires a strict direct normal user session and `ViewDelegationTokens`; delegated, MCP/impersonation, and service credentials are rejected.",
        "tags": [
          "Delegation"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Delegation tokens",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "required": [
                      "id",
                      "user_id",
                      "group_id",
                      "delegation_scope",
                      "expires_at",
                      "created_at",
                      "is_active"
                    ],
                    "properties": {
                      "id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "user_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "group_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "delegation_scope": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "description": "Capability that may be scoped to one `acting_as` group. Delegation\nendpoints reject the entire request with `400` when a value is\nunknown, personal-user authority, or platform-global authority;\nthey do not silently drop unsupported values. `PolicyExecution` is\nvalid for direct policy-member delegation, but the A→B owned-group\nexchange rejects it. The ownership exchange further attenuates the\nremaining valid request to `OwnedGroupOperationalPermission`.\n",
                          "enum": [
                            "CreateEntity",
                            "ReadEntity",
                            "UpdateEntity",
                            "DeleteEntity",
                            "ApiRead",
                            "ApiWrite",
                            "BulkOperations",
                            "ReadGroup",
                            "UpdateGroup",
                            "DeleteGroup",
                            "ManageGroupMembers",
                            "ManageGroupPermissions",
                            "ManageGroupOwners",
                            "ManageGroupEntityScope",
                            "CreateDelegationToken",
                            "ViewDelegationTokens",
                            "RevokeDelegationToken",
                            "CryptoOperations",
                            "PolicyExecution",
                            "LlmInference"
                          ]
                        }
                      },
                      "expires_at": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "created_at": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "is_active": {
                        "type": "boolean"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Bearer is delegated/MCP/service or lacks `ViewDelegationTokens`."
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/delegation/tokens \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/delegation/tokens', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/delegation/tokens'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      },
      "post": {
        "operationId": "createDelegationToken",
        "summary": "Create a delegation token for the current user",
        "description": "Requires a direct normal user session,\n`Permission::CreateDelegationToken`, and live target-group\nauthority (active membership OR NFT-owner fallback). Scope accepts\nonly group-delegable capabilities; unknown, personal-user, and\nplatform-global permissions reject the whole request with 400.\n",
        "tags": [
          "Delegation"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "group_id",
                  "scope",
                  "duration_hours"
                ],
                "properties": {
                  "group_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "scope": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "string",
                      "description": "Capability that may be scoped to one `acting_as` group. Delegation\nendpoints reject the entire request with `400` when a value is\nunknown, personal-user authority, or platform-global authority;\nthey do not silently drop unsupported values. `PolicyExecution` is\nvalid for direct policy-member delegation, but the A→B owned-group\nexchange rejects it. The ownership exchange further attenuates the\nremaining valid request to `OwnedGroupOperationalPermission`.\n",
                      "enum": [
                        "CreateEntity",
                        "ReadEntity",
                        "UpdateEntity",
                        "DeleteEntity",
                        "ApiRead",
                        "ApiWrite",
                        "BulkOperations",
                        "ReadGroup",
                        "UpdateGroup",
                        "DeleteGroup",
                        "ManageGroupMembers",
                        "ManageGroupPermissions",
                        "ManageGroupOwners",
                        "ManageGroupEntityScope",
                        "CreateDelegationToken",
                        "ViewDelegationTokens",
                        "RevokeDelegationToken",
                        "CryptoOperations",
                        "PolicyExecution",
                        "LlmInference"
                      ]
                    }
                  },
                  "duration_hours": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Token lifetime in hours. Must be between 1 and the\nconfigured `auth.delegation.max_duration_hours`\ninclusive.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Delegation token created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "user_id",
                    "group_id",
                    "delegation_scope",
                    "expires_at",
                    "created_at",
                    "is_active"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "user_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "group_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "delegation_scope": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "description": "Capability that may be scoped to one `acting_as` group. Delegation\nendpoints reject the entire request with `400` when a value is\nunknown, personal-user authority, or platform-global authority;\nthey do not silently drop unsupported values. `PolicyExecution` is\nvalid for direct policy-member delegation, but the A→B owned-group\nexchange rejects it. The ownership exchange further attenuates the\nremaining valid request to `OwnedGroupOperationalPermission`.\n",
                        "enum": [
                          "CreateEntity",
                          "ReadEntity",
                          "UpdateEntity",
                          "DeleteEntity",
                          "ApiRead",
                          "ApiWrite",
                          "BulkOperations",
                          "ReadGroup",
                          "UpdateGroup",
                          "DeleteGroup",
                          "ManageGroupMembers",
                          "ManageGroupPermissions",
                          "ManageGroupOwners",
                          "ManageGroupEntityScope",
                          "CreateDelegationToken",
                          "ViewDelegationTokens",
                          "RevokeDelegationToken",
                          "CryptoOperations",
                          "PolicyExecution",
                          "LlmInference"
                        ]
                      }
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "is_active": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Empty/unsupported scope, zero duration, or duration beyond the configured cap."
          },
          "403": {
            "description": "Bearer is delegated/MCP/service, lacks `CreateDelegationToken`, or lacks live target-group authority."
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/delegation/tokens \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"group_id\": \"00000000-0000-0000-0000-000000000000\",\n  \"scope\": [\"ReadGroup\"],\n  \"duration_hours\": 1\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/delegation/tokens', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"group_id\": \"00000000-0000-0000-0000-000000000000\",\n    \"scope\": [\"ReadGroup\"],\n    \"duration_hours\": 1\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/delegation/tokens'\nresp = httpx.post(url, headers=headers, json={\"group_id\": \"00000000-0000-0000-0000-000000000000\", \"scope\": [\"ReadGroup\"], \"duration_hours\": 1})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/delegation/tokens/{id}": {
      "delete": {
        "operationId": "revokeDelegationToken",
        "summary": "Revoke a delegation token (irreversible)",
        "description": "Requires a strict direct normal user session and\n`Permission::RevokeDelegationToken`. Delegated,\nMCP/impersonation, and service credentials are rejected.\nIrreversible.\nThe database update sets only `is_active = false`; this table has\nno `revoked` or `revoked_at` field. The current\n`POST /auth/delegation/jwt` operation does not accept or consume a\npersistent token id, so revoking this record neither revokes an\nalready-issued JWT nor gates that live-authority mint.\n",
        "tags": [
          "Delegation"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Delegation token UUID."
          }
        ],
        "responses": {
          "204": {
            "description": "Token revoked"
          },
          "403": {
            "description": "Bearer is delegated/MCP/service or lacks `RevokeDelegationToken`."
          },
          "404": {
            "description": "Token does not exist or does not belong to the caller."
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X DELETE https://auth.yieldfabric.com/auth/delegation/tokens/{id} \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/delegation/tokens/{id}', {\n  method: 'DELETE',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/delegation/tokens/{id}'\nresp = httpx.delete(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/delegation/jwt": {
      "post": {
        "operationId": "createDelegationJwt",
        "summary": "Mint a chain-pinned delegation session",
        "description": "Issues a short-lived JWT carrying `acting_as` = the delegated\ngroup_id and `delegation_scope` = the authorized permission set.\nUse the issued\nJWT for any subsequent call that should run \"as the group\" within\nthe delegated scope.\n\nFull requested scope requires a live Owner/Admin role in the target\ngroup or the `ManageGroupPermissions` path. PolicyMembers receive\nonly the fixed `[PolicyExecution, CryptoOperations]` scope; the\nNFT-owner fallback and derived-policy path are evaluated on the\nselected chain. `chain_id` is optional: omitted\nrequests inherit the validated caller JWT's `default_chain_id`;\nlegacy chain-less callers must provide it. The selected chain must\nbe enabled in auth's payments topology. The human user may be\nwalletless, but the group must already have a real deployed default on\nit because the group is the active actor. The mint endpoint never\ndeploys as a side effect; an Owner/Admin should first call\n`POST /entities/group/{group_id}/chain-accounts/{chain_id}/activation`\nwhile their personal JWT is bound to that same chain.\n\nThis is a root mint and requires a strict direct normal user\nsession. Service, MCP/impersonation, and any delegated or\nprovenance-bearing credential are rejected with `403` code\n`direct_user_session_required`; use the dedicated owned-group\nexchange for A→B attenuation instead.\n\nThe scope accepts only the group-delegable capability enum below.\nUnknown, personal-user, and platform-global permissions reject the\nentire request with 400 rather than being silently ignored.\n",
        "tags": [
          "Delegation"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "group_id",
                  "delegation_scope",
                  "expiry_seconds"
                ],
                "properties": {
                  "group_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "delegation_scope": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "string",
                      "description": "Capability that may be scoped to one `acting_as` group. Delegation\nendpoints reject the entire request with `400` when a value is\nunknown, personal-user authority, or platform-global authority;\nthey do not silently drop unsupported values. `PolicyExecution` is\nvalid for direct policy-member delegation, but the A→B owned-group\nexchange rejects it. The ownership exchange further attenuates the\nremaining valid request to `OwnedGroupOperationalPermission`.\n",
                      "enum": [
                        "CreateEntity",
                        "ReadEntity",
                        "UpdateEntity",
                        "DeleteEntity",
                        "ApiRead",
                        "ApiWrite",
                        "BulkOperations",
                        "ReadGroup",
                        "UpdateGroup",
                        "DeleteGroup",
                        "ManageGroupMembers",
                        "ManageGroupPermissions",
                        "ManageGroupOwners",
                        "ManageGroupEntityScope",
                        "CreateDelegationToken",
                        "ViewDelegationTokens",
                        "RevokeDelegationToken",
                        "CryptoOperations",
                        "PolicyExecution",
                        "LlmInference"
                      ]
                    }
                  },
                  "expiry_seconds": {
                    "type": "integer",
                    "description": "Requested delegation JWT TTL. Every direct-role mint\nis clamped to 60–3600 seconds.\n"
                  },
                  "chain_id": {
                    "type": "string",
                    "description": "Optional target chain; omitted inherits the caller JWT's active `default_chain_id`."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Delegation JWT issued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "delegation_jwt",
                    "refresh_token",
                    "group_id",
                    "delegation_scope",
                    "expiry_seconds",
                    "chain_id"
                  ],
                  "properties": {
                    "delegation_jwt": {
                      "type": "string",
                      "description": "Short-lived delegation JWT (acting_as set)."
                    },
                    "refresh_token": {
                      "type": "string",
                      "description": "Isolated, paired delegation refresh token. Send it as\n`X-Refresh-Token` on requests bearing this delegation JWT to\nkeep the session alive past the JWT TTL — on redeem it\nre-mints ONLY a delegation JWT and never touches the user's\nregular session. Bounded by live group membership, live\npermissions (narrowed to current entitlement), and the\n`auth.delegation.max_duration_hours` lifetime cap.\n"
                    },
                    "group_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "delegation_scope": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "description": "Capability that may be scoped to one `acting_as` group. Delegation\nendpoints reject the entire request with `400` when a value is\nunknown, personal-user authority, or platform-global authority;\nthey do not silently drop unsupported values. `PolicyExecution` is\nvalid for direct policy-member delegation, but the A→B owned-group\nexchange rejects it. The ownership exchange further attenuates the\nremaining valid request to `OwnedGroupOperationalPermission`.\n",
                        "enum": [
                          "CreateEntity",
                          "ReadEntity",
                          "UpdateEntity",
                          "DeleteEntity",
                          "ApiRead",
                          "ApiWrite",
                          "BulkOperations",
                          "ReadGroup",
                          "UpdateGroup",
                          "DeleteGroup",
                          "ManageGroupMembers",
                          "ManageGroupPermissions",
                          "ManageGroupOwners",
                          "ManageGroupEntityScope",
                          "CreateDelegationToken",
                          "ViewDelegationTokens",
                          "RevokeDelegationToken",
                          "CryptoOperations",
                          "PolicyExecution",
                          "LlmInference"
                        ]
                      }
                    },
                    "expiry_seconds": {
                      "type": "integer",
                      "minimum": 60,
                      "maximum": 3600,
                      "description": "Effective clamped delegation JWT TTL in seconds (the refresh outlives this)."
                    },
                    "chain_id": {
                      "type": "string",
                      "description": "Effective chain pinned into the delegation JWT."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Empty/unknown/personal/global scope, or unsupported/missing\nchain. Flat `{ \"error\": \"...\" }` body.\n"
          },
          "403": {
            "description": "Caller lacks live target-group authority or the bearer is not a\nstrict direct normal user session. The latter returns the stable code\n`direct_user_session_required`.\n"
          },
          "409": {
            "description": "Configured-but-disabled chain, missing target-group account, or\na `chain_id` outside the caller session's `allowed_chains` when\nthat claim is present (`chain_not_allowed` — a delegation never\nwidens the session it is minted from; today only connector\nlineages carry the claim and those are refused by the\ndirect-session gate first). The personal user wallet is optional.\n",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "required": [
                        "error",
                        "code",
                        "chain_id"
                      ],
                      "properties": {
                        "error": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "const": "CHAIN_UNAVAILABLE"
                        },
                        "chain_id": {
                          "type": "string"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "error",
                        "code",
                        "entity_kind",
                        "entity_id",
                        "chain_id"
                      ],
                      "properties": {
                        "error": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "const": "CHAIN_ACCOUNT_REQUIRED"
                        },
                        "entity_kind": {
                          "type": "string",
                          "enum": [
                            "user",
                            "group"
                          ]
                        },
                        "entity_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "activation_url": {
                          "type": "string",
                          "description": "Exact recovery resource when the handler can name it."
                        },
                        "operation": {
                          "type": "string",
                          "description": "Stable operation label for diagnostics; clients route on `code` and the entity/chain fields."
                        }
                      }
                    },
                    {
                      "type": "object",
                      "description": "409 from `/auth/api-key` (connector exchange), `/auth/refresh` (connector\nlineage) and the delegation mints: the requested chain is outside the\ncredential's / caller session's `allowed_chains`. The token or key is\nnot consumed — retry on an allowed chain.\n",
                      "required": [
                        "error",
                        "code",
                        "chain_id",
                        "allowed_chains"
                      ],
                      "properties": {
                        "error": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "const": "chain_not_allowed"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "allowed_chains": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/delegation/jwt \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"group_id\": \"00000000-0000-0000-0000-000000000000\",\n  \"delegation_scope\": [\"CryptoOperations\"],\n  \"expiry_seconds\": 600,\n  \"chain_id\": \"153\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/delegation/jwt', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"group_id\": \"00000000-0000-0000-0000-000000000000\",\n    \"delegation_scope\": [\"CryptoOperations\"],\n    \"expiry_seconds\": 600,\n    \"chain_id\": \"153\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/delegation/jwt'\nresp = httpx.post(url, headers=headers, json={\"group_id\": \"00000000-0000-0000-0000-000000000000\", \"delegation_scope\": [\"CryptoOperations\"], \"expiry_seconds\": 600, \"chain_id\": \"153\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/delegation/jwt/assume-owned-group": {
      "post": {
        "operationId": "assumeOwnedGroup",
        "summary": "Assume a group owned by the current acting group",
        "description": "Exchanges an existing `acting_as = A` delegation JWT for a\nshort-lived `acting_as = B` JWT when A's immutable credential NFT\nis an exact member of B's account on the parent session chain.\n\nAuth re-checks that the human `sub` is active and still an\nOwner/Admin of A. The fallback requires current global\n`ManageGroupPermissions`, that exact parent scope, and live A\nmembership/NFT access. Auth rejects PolicyMember authority,\nrequires both groups to be active, resolves the active relationship\ngeneration, and validates the exact human `signer_key_id`. The key\nmust be owned by `sub` and support the account ECDSA65 signature\nrail. Both custodial and external keys qualify: custody selects the\nexisting Automatic or Manual path; it does not change authority.\n\nBefore minting, Auth verifies that the configured immutable credential\nis still owned by A, remains B's exact NFT member, and the selected\nhuman address has live contract access through both A and B. B's\nmember-verification, entity-KYB, billing, and deployed-wallet gates\nare then applied.\nOn a billing-enabled deployment, a child retaining\n`CryptoOperations`, `PolicyExecution`, or `LlmInference`\nadditionally requires an active direct Owner/Admin member of B.\nA memberless B may receive only nonbillable scope.\n\nThe child scope is the requested scope intersected with the parent\nA scope and the ownership-operational allow-list, followed by B's\nnormal gates. It never carries `UpdateGroup`, `DeleteGroup`, a\n`ManageGroup*` capability, or delegation-management authority.\nConsequently, ownership cannot be converted into persistent B\nmembership, account topology, credentials, or delegated authority.\nThe child is capped at 900 seconds and may not outlive the parent.\nV1 permits exactly one A→B hop and emits no refresh token. Tokens\ncarrying ownership provenance are validated with zero expiry leeway,\nso the signed `exp` is the hard deadline. The child is a B identity\nfor auth/key/payment consumers: `acting_as` and the active group\nwallet are B. Auth's group boundary accepts the exact signed\n`delegation_path = [A, B]`,\n`authority_source = nft_membership`, exact `membership_edge`, and\ncomplete human signer tuple after target/scope/active checks. No\noff-chain membership row is created.\n\nB remains the auth, account, and encryption context: `acting_as`,\nthe active wallet/account, and vault main-key contact all resolve\nto B. MQ resolves exactly the selected human key. A custodial human\nkey must use Automatic; an external human key must use Manual. No A\ngroup key signs for B.\n\nRemoving the exact NFT member does not revoke the JWT signature, but\nmakes B reject transaction signatures immediately. Deactivating the\nselected human key, transferring the credential, or changing the\nbound account generation also fails closed.\n\nBilling, membership, group configuration, credentials, delegation,\nand wallet topology remain separate administration boundaries.\nOwnership-child JWTs cannot call billing endpoints or mutate B\nmembers, roles, owners, account members, entity scope, group\nkeypairs, wallets, or delegation records. The exchange does not\npersist a billing administrator, and cached `billing_admin_user_id`\nis routing/contact metadata rather than authorization. Those\nadministration operations require the existing direct-authority\nceremony for that surface.\n",
        "tags": [
          "Delegation"
        ],
        "externalDocs": {
          "description": "Head Co → Subsidiary setup and operation guide",
          "url": "guides/group-account-ownership.md"
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "group_id",
                  "delegation_scope",
                  "expiry_seconds",
                  "signer_key_id"
                ],
                "properties": {
                  "group_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Target Group B."
                  },
                  "delegation_scope": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "string",
                      "description": "Capability that may be scoped to one `acting_as` group. Delegation\nendpoints reject the entire request with `400` when a value is\nunknown, personal-user authority, or platform-global authority;\nthey do not silently drop unsupported values. `PolicyExecution` is\nvalid for direct policy-member delegation, but the A→B owned-group\nexchange rejects it. The ownership exchange further attenuates the\nremaining valid request to `OwnedGroupOperationalPermission`.\n",
                      "enum": [
                        "CreateEntity",
                        "ReadEntity",
                        "UpdateEntity",
                        "DeleteEntity",
                        "ApiRead",
                        "ApiWrite",
                        "BulkOperations",
                        "ReadGroup",
                        "UpdateGroup",
                        "DeleteGroup",
                        "ManageGroupMembers",
                        "ManageGroupPermissions",
                        "ManageGroupOwners",
                        "ManageGroupEntityScope",
                        "CreateDelegationToken",
                        "ViewDelegationTokens",
                        "RevokeDelegationToken",
                        "CryptoOperations",
                        "PolicyExecution",
                        "LlmInference"
                      ]
                    },
                    "description": "Requested group-delegable capabilities. The response is\nfurther attenuated to the ownership-operational allow-list;\nadministrative group/delegation capabilities are omitted.\nA request whose intersection is empty fails with\n`owned_group_scope_required`. `PolicyExecution` rejects the\nwhole ownership exchange.\n"
                  },
                  "expiry_seconds": {
                    "type": "integer",
                    "description": "Requested TTL; server floors ordinary requests to 60s, caps at 900s, then clamps to the parent JWT's remaining lifetime."
                  },
                  "signer_key_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Exact active account-compatible key owned by the human `sub`."
                  },
                  "chain_id": {
                    "type": "string",
                    "description": "Optional parent-session chain. If supplied, it must exactly match the bearer JWT's active chain."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Non-refreshable ownership delegation JWT issued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "delegation_jwt",
                    "group_id",
                    "delegation_scope",
                    "expiry_seconds",
                    "chain_id",
                    "delegation_path",
                    "authority_source",
                    "membership_edge",
                    "signer_key_id",
                    "signer_address",
                    "signer_scheme",
                    "signer_custody"
                  ],
                  "properties": {
                    "delegation_jwt": {
                      "type": "string",
                      "description": "Short-lived JWT with `acting_as` set to Group B."
                    },
                    "group_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "delegation_scope": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "description": "Capability that may survive into an A→B ownership-derived child after\nintersection with the requested and parent scopes. Administrative group,\nmembership, owner, account-topology, credential, entity-scope, and\ndelegation-management capabilities are deliberately excluded.\n",
                        "enum": [
                          "CreateEntity",
                          "ReadEntity",
                          "UpdateEntity",
                          "DeleteEntity",
                          "ApiRead",
                          "ApiWrite",
                          "BulkOperations",
                          "ReadGroup",
                          "CryptoOperations",
                          "LlmInference"
                        ]
                      },
                      "description": "Actual signed scope after parent attenuation and B's verification/billing gates."
                    },
                    "expiry_seconds": {
                      "type": "integer",
                      "maximum": 900
                    },
                    "chain_id": {
                      "type": "string"
                    },
                    "delegation_path": {
                      "type": "array",
                      "minItems": 2,
                      "maxItems": 2,
                      "items": {
                        "type": "string",
                        "format": "uuid"
                      }
                    },
                    "authority_source": {
                      "type": "string",
                      "const": "nft_membership"
                    },
                    "membership_edge": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "parent_group_id",
                        "child_group_id",
                        "chain_id",
                        "credential_contract",
                        "token_id"
                      ],
                      "properties": {
                        "parent_group_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "child_group_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "credential_contract": {
                          "type": "string",
                          "pattern": "^0x[0-9a-f]{40}$"
                        },
                        "token_id": {
                          "type": "string",
                          "pattern": "^[1-9][0-9]*$",
                          "description": "Canonical non-zero decimal uint256."
                        }
                      }
                    },
                    "signer_key_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "signer_address": {
                      "type": "string",
                      "pattern": "^0x[0-9a-f]{40}$"
                    },
                    "signer_scheme": {
                      "type": "string",
                      "const": "account_ecdsa65_v1"
                    },
                    "signer_custody": {
                      "type": "string",
                      "enum": [
                        "custodial",
                        "external"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid/empty scope, unsupported chain, cycle, or parent-chain\nmismatch. Coded variants are `delegation_cycle` and\n`delegation_chain_mismatch`; an incompatible selected key uses\n`unsupported_signer_scheme`.\n"
          },
          "401": {
            "description": "Missing, expired, inactive-user, or non-user parent credential.\nA structurally wrong user credential may carry\n`owned_group_parent_required`; generic bearer failures remain flat.\n"
          },
          "403": {
            "description": "Live A admin authority, the exact NFT edge, human signer, parent shape/scope,\ndepth, group activity, or B verification requirements were not\nsatisfied. `PolicyExecution` is forbidden on an ownership\nchild. Stable coded variants are\n`owned_group_parent_required`, `delegation_depth_exceeded`,\n`owned_group_scope_required`, `owner_authority_required`,\n`owned_group_signer_required`, `unsupported_signer_scheme`,\n`inactive_group`, `owned_group_delegation_state_changed`, and\n`verification_required` (when the configured profile can be\nevaluated). A dangling verification-profile binding returns a flat\n403.\n"
          },
          "404": {
            "description": "Source Group A or target Group B does not exist. Flat error body."
          },
          "409": {
            "description": "Configured-but-disabled chain, missing Group B account, B's active\naccount/credential generation changed during the exchange, the parent session's\nsponsorship ended, or billable scope without an active direct B\nOwner/Admin. Stable codes are `CHAIN_UNAVAILABLE`,\n`CHAIN_ACCOUNT_REQUIRED`, `OWNED_GROUP_TARGET_ACCOUNT_CHANGED`, and\n`owned_group_relationship_changed`, `sponsorship_ended`, plus\n`billing_admin_required`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "required": [
                        "error",
                        "code",
                        "chain_id"
                      ],
                      "properties": {
                        "error": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "const": "CHAIN_UNAVAILABLE"
                        },
                        "chain_id": {
                          "type": "string"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "error",
                        "code",
                        "entity_kind",
                        "entity_id",
                        "chain_id"
                      ],
                      "properties": {
                        "error": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "const": "CHAIN_ACCOUNT_REQUIRED"
                        },
                        "entity_kind": {
                          "type": "string",
                          "enum": [
                            "user",
                            "group"
                          ]
                        },
                        "entity_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "activation_url": {
                          "type": "string",
                          "description": "Exact recovery resource when the handler can name it."
                        },
                        "operation": {
                          "type": "string",
                          "description": "Stable operation label for diagnostics; clients route on `code` and the entity/chain fields."
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "error",
                        "code",
                        "entity_kind",
                        "entity_id",
                        "chain_id"
                      ],
                      "properties": {
                        "error": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "const": "OWNED_GROUP_TARGET_ACCOUNT_CHANGED"
                        },
                        "entity_kind": {
                          "type": "string",
                          "const": "group"
                        },
                        "entity_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "chain_id": {
                          "type": "string"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "error",
                        "code"
                      ],
                      "properties": {
                        "error": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "const": "sponsorship_ended"
                        },
                        "sponsor_group_id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "Present when the stale sponsor id could be parsed or was resolved during mint."
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "error",
                        "code"
                      ],
                      "properties": {
                        "error": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "const": "billing_admin_required"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Per-user owned-group exchange budget exceeded. Stable code\n`owned_group_delegation_rate_limited`; response includes\n`retry_after_seconds`.\n"
          },
          "502": {
            "description": "A safety-critical NFT owner/member/access read was unavailable; auth\nfails closed with code `owner_verification_unavailable`.\n"
          }
        }
      }
    },
    "/auth/signature/register": {
      "post": {
        "operationId": "registerSignatureKey",
        "summary": "Register a new public key for the current user",
        "description": "Registers the key so it can be used for `/auth/signature*`\nsign-in and account-ownership operations.\n",
        "tags": [
          "Signatures",
          "Keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "public_key",
                  "key_type"
                ],
                "properties": {
                  "public_key": {
                    "type": "string"
                  },
                  "key_type": {
                    "type": "string",
                    "description": "Stored-keypair purpose. `Signing` = secp256k1 used for EIP-191\nsignatures and account ownership; `Encryption` = secp256k1 used as\nan ECIES recipient endpoint; `Hybrid` = doubles as both (legacy\nemail-user shape); `External` = wallet-led signup, no private key\nheld server-side.\n",
                    "enum": [
                      "Signing",
                      "Encryption",
                      "Hybrid",
                      "External"
                    ]
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "metadata": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Key registered",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "user_id",
                    "public_key",
                    "key_type",
                    "is_active",
                    "created_at"
                  ],
                  "description": "Registered public key in `signature_keys`. Used for signature-based auth and account ownership.",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "user_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "public_key": {
                      "type": "string",
                      "description": "Uncompressed or compressed hex, with or without 0x prefix."
                    },
                    "key_type": {
                      "type": "string",
                      "description": "Stored-keypair purpose. `Signing` = secp256k1 used for EIP-191\nsignatures and account ownership; `Encryption` = secp256k1 used as\nan ECIES recipient endpoint; `Hybrid` = doubles as both (legacy\nemail-user shape); `External` = wallet-led signup, no private key\nheld server-side.\n",
                      "enum": [
                        "Signing",
                        "Encryption",
                        "Hybrid",
                        "External"
                      ]
                    },
                    "address": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Derived EVM address (lowercased)."
                    },
                    "is_active": {
                      "type": "boolean"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "metadata": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/signature/register \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"public_key\": \"<string>\",\n  \"key_type\": \"<see KeyType>\",\n  \"metadata\": {}\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/signature/register', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"public_key\": \"<string>\",\n    \"key_type\": \"<see KeyType>\",\n    \"metadata\": {}\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/signature/register'\nresp = httpx.post(url, headers=headers, json={\"public_key\": \"<string>\", \"key_type\": \"<see KeyType>\", \"metadata\": {}})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/signature/keys": {
      "get": {
        "operationId": "listSignatureKeys",
        "summary": "List the current user's signature keys",
        "tags": [
          "Signatures",
          "Keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Keys",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "required": [
                      "id",
                      "user_id",
                      "public_key",
                      "key_type",
                      "is_active",
                      "created_at"
                    ],
                    "description": "Registered public key in `signature_keys`. Used for signature-based auth and account ownership.",
                    "properties": {
                      "id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "user_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "name": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "public_key": {
                        "type": "string",
                        "description": "Uncompressed or compressed hex, with or without 0x prefix."
                      },
                      "key_type": {
                        "type": "string",
                        "description": "Stored-keypair purpose. `Signing` = secp256k1 used for EIP-191\nsignatures and account ownership; `Encryption` = secp256k1 used as\nan ECIES recipient endpoint; `Hybrid` = doubles as both (legacy\nemail-user shape); `External` = wallet-led signup, no private key\nheld server-side.\n",
                        "enum": [
                          "Signing",
                          "Encryption",
                          "Hybrid",
                          "External"
                        ]
                      },
                      "address": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Derived EVM address (lowercased)."
                      },
                      "is_active": {
                        "type": "boolean"
                      },
                      "created_at": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "metadata": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/signature/keys \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/signature/keys', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/signature/keys'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/signature/keys/{key_id}": {
      "get": {
        "operationId": "getSignatureKey",
        "summary": "Get a specific signature key",
        "tags": [
          "Signatures",
          "Keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "key_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Key UUID (signature key or API key, depending on the route)."
          }
        ],
        "responses": {
          "200": {
            "description": "Key detail",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "user_id",
                    "public_key",
                    "key_type",
                    "is_active",
                    "created_at"
                  ],
                  "description": "Registered public key in `signature_keys`. Used for signature-based auth and account ownership.",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "user_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "public_key": {
                      "type": "string",
                      "description": "Uncompressed or compressed hex, with or without 0x prefix."
                    },
                    "key_type": {
                      "type": "string",
                      "description": "Stored-keypair purpose. `Signing` = secp256k1 used for EIP-191\nsignatures and account ownership; `Encryption` = secp256k1 used as\nan ECIES recipient endpoint; `Hybrid` = doubles as both (legacy\nemail-user shape); `External` = wallet-led signup, no private key\nheld server-side.\n",
                      "enum": [
                        "Signing",
                        "Encryption",
                        "Hybrid",
                        "External"
                      ]
                    },
                    "address": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Derived EVM address (lowercased)."
                    },
                    "is_active": {
                      "type": "boolean"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "metadata": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (user, group, key, delegation token, wallet, provider).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "keypair": {
                    "summary": "Keypair UUID not found",
                    "value": {
                      "error": "keypair not found"
                    }
                  },
                  "kyc-provider": {
                    "summary": "Requested KYC provider not enabled in yaml",
                    "value": {
                      "error": "kyc provider not enabled"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/signature/keys/{key_id} \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/signature/keys/{key_id}', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/signature/keys/{key_id}'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      },
      "delete": {
        "operationId": "deleteSignatureKey",
        "summary": "Delete (deactivate) a signature key",
        "description": "Soft-deleted; the row is retained for audit.",
        "tags": [
          "Signatures",
          "Keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "key_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Key UUID (signature key or API key, depending on the route)."
          }
        ],
        "responses": {
          "204": {
            "description": "Key deactivated"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X DELETE https://auth.yieldfabric.com/auth/signature/keys/{key_id} \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/signature/keys/{key_id}', {\n  method: 'DELETE',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/signature/keys/{key_id}'\nresp = httpx.delete(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/api-key": {
      "post": {
        "operationId": "authenticateApiKey",
        "summary": "Authenticate with an API key",
        "description": "Exchanges an existing API key for a standard JWT carrying the\nAPI-key user's identity. The API key is consumed in the request\nbody (not the bearer slot) on this single endpoint; downstream\ncalls then use the returned JWT. This is the existing API-key-to-JWT\nexchange, not a separate service-to-service authentication scheme.\n\n`chain_id` optionally selects the JWT session's active chain. If\nomitted, the exchange preserves its existing system-default-chain\nbehavior. A supplied chain is processed through the standard\nsession refresh/mint path. It does not deploy and does not require a\nwallet: the resulting user JWT may carry null wallet/address claims\nuntil explicit activation.\n\n**Connector keys** (`kind: connector`, see `/auth/api-key/generate`)\nmint a CONNECTOR session instead of the person's ordinary one:\n`CryptoOperations` and `PolicyExecution` are stripped\nunconditionally, the JWT carries `session_kind: connector` and the\nkey's `allowed_chains`, and the session is minted directly on the\nchosen chain — `chain_id` when it is inside `allowed_chains`, else\nthe key's first allowed chain, else the system default. The same\nbinding is persisted on the refresh row, so `/auth/refresh` re-mints\na connector session on an allowed chain only. A `chain_id` outside\n`allowed_chains` is refused with the structured 409 below and the\nkey stays usable. Personal keys are unchanged.\n\n**Live chains need the user's opt-in.** When the chain\nthe connector session would land on is LIVE (not in\n`auth.modes.test_chain_ids`) — the requested `chain_id`, the key's\nfirst allowed chain, or the system default an unrestricted key with\nno `chain_id` lands on — the key's owner must have enabled live\nconnectors (`PUT /auth/users/me/live-connectors`), else the exchange\nis refused with the structured 403 below. Re-read on every exchange\nand every `/auth/refresh` of the lineage, so opting out ends live\nconnector sessions within one access-token cycle.\n",
        "tags": [
          "API keys"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "api_key"
                ],
                "properties": {
                  "api_key": {
                    "type": "string"
                  },
                  "chain_id": {
                    "type": "string",
                    "description": "Optional active chain for the minted JWT session. Omit\nto use the system default (a connector key: its first\nallowed chain). Must be configured and enabled; for a\nconnector key it must also be inside `allowed_chains`.\nNo pre-existing wallet is required.\n",
                    "example": "153"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Authenticated, JWT issued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "token",
                    "refresh_token",
                    "token_type",
                    "expires_in",
                    "user"
                  ],
                  "description": "Returned by `POST /auth/login` and `POST /auth/login/with-services`.",
                  "properties": {
                    "token": {
                      "type": "string",
                      "description": "Short-lived access token (default 15 min)."
                    },
                    "refresh_token": {
                      "type": "string",
                      "description": "Long-lived refresh token (30 days, single-use, rotated on each refresh)."
                    },
                    "token_type": {
                      "type": "string",
                      "const": "Bearer"
                    },
                    "expires_in": {
                      "type": "integer",
                      "description": "Access-token lifetime in seconds."
                    },
                    "user": {
                      "type": "object",
                      "required": [
                        "id",
                        "email",
                        "role",
                        "is_active"
                      ],
                      "description": "User record shape returned by `/auth/users/me` and friends.\n`password_hash` is always returned as an empty string (never\nexposed). See `yieldfabric-auth/src/handlers/types.rs::UserInfo`.\n",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "email": {
                          "type": "string",
                          "format": "email"
                        },
                        "password_hash": {
                          "type": "string",
                          "description": "Always empty in responses."
                        },
                        "role": {
                          "type": "string",
                          "description": "Built-in role assigned to every user. Role + Permission grants make\nup the RBAC matrix; see `auth.roles` in `config/system.yaml` for\nrole overrides. SuperAdmin always has every permission.\n",
                          "enum": [
                            "SuperAdmin",
                            "Admin",
                            "Manager",
                            "Operator",
                            "User",
                            "Viewer",
                            "ApiClient"
                          ]
                        },
                        "is_active": {
                          "type": "boolean"
                        },
                        "must_change_password": {
                          "type": "boolean"
                        },
                        "password_changed_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "failed_login_count": {
                          "type": "integer"
                        },
                        "last_failed_login": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "is_external_signer": {
                          "type": "boolean",
                          "description": "Derived. **True** when the user has no active `KeyType: Signing`\nkeypair with `encrypted_private_key` — i.e. they sign exclusively\nvia an external wallet (Averer / Dynamic / MetaMask /\nWebAuthn passkey). The frontend trusts this flag verbatim and\nsets `requireManualSignature: true` on every payment mutation\nwhen it's true, so the backend routes the message into the\nmanual-sign drawer instead of attempting server-side signing.\nRecomputed on every call from a case-insensitive\n`LOWER(key_type) = 'signing'` SQL filter. See the architecture\ndoc for the full chain and the cache-invalidation gotcha.\n"
                        },
                        "live_connectors_enabled": {
                          "type": "boolean",
                          "description": "The person's LIVE-connector opt-in (`users.live_connectors_enabled`,\nagent surface spec §5.4). While false, none of their connector\ncredentials can be generated for, minted on, or refreshed onto a\nlive chain. Set via `PUT /auth/users/me/live-connectors`\n(direct session + live verification floor). Returned by\n`GET /auth/users/me` only; best-effort read.\n"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired, or wrong-audience JWT (user or service).\nIncludes invalid API keys on the exchange endpoints, failed wallet\nsignatures on the signin endpoints, and failed Sumsub HMAC on the\nwebhook.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "no-token": {
                    "summary": "Missing Authorization header",
                    "value": {
                      "error": "missing bearer token"
                    }
                  },
                  "invalid-token": {
                    "summary": "JWT signature / structure invalid",
                    "value": {
                      "error": "invalid token"
                    }
                  },
                  "invalid-subject": {
                    "summary": "JWT sub field unparseable",
                    "value": {
                      "error": "invalid token subject"
                    }
                  },
                  "webhook-signature": {
                    "summary": "Sumsub HMAC verification failed",
                    "value": {
                      "error": "invalid webhook signature or payload"
                    }
                  },
                  "averer-reverify": {
                    "summary": "Averer flagged a re-verification requirement",
                    "value": {
                      "error": "averer_reverify_required"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Connector keys only: the session would land on a LIVE chain and\nthe key's owner has not enabled live connectors —\n`{\"error\": \"...\", \"code\": \"live_connector_opt_in_required\",\n\"chain_id\": \"151\"}`. The same body `/auth/refresh` answers for a\nconnector session and `/auth/api-key/generate` answers for a\nconnector key naming a live chain. Not fixable by retrying on\nanother live chain: the person opts in from a direct session\n(`PUT /auth/users/me/live-connectors`), or the key uses a test\nchain.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "403 from `/auth/api-key/generate` (a connector key naming a live\nchain), `/auth/api-key` (a connector session landing on a live chain)\nand `/auth/refresh` (a connector lineage rotating onto a live chain):\nthe user has not enabled live connectors\n(`users.live_connectors_enabled`, agent surface spec §5.4). Not a\nretry-on-another-chain refusal — the person opts in from a direct\nsession via `PUT /auth/users/me/live-connectors`, or the credential\nuses a test chain. On `/auth/refresh` the token is not consumed.\n",
                  "required": [
                    "error",
                    "code",
                    "chain_id"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "const": "live_connector_opt_in_required"
                    },
                    "chain_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The supplied chain cannot be selected. For a personal key\n(unsupported or operator-disabled chain) the handler returns an\nempty 409 body. For a connector key whose `chain_id` is outside\nits `allowed_chains` the body is structured:\n`{\"error\": \"...\", \"code\": \"chain_not_allowed\", \"chain_id\": \"151\",\n\"allowed_chains\": [\"153\"]}` — the same body `/auth/refresh`\nanswers for a connector session.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "enum": [
                        "chain_not_allowed"
                      ]
                    },
                    "chain_id": {
                      "type": "string"
                    },
                    "allowed_chains": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "Connector keys only: the live-connector opt-in could not be read\n(store unavailable). Says nothing about the key — retry.\n"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/api-key \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"api_key\": \"<string>\",\n  \"chain_id\": \"153\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/api-key', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"api_key\": \"<string>\",\n    \"chain_id\": \"153\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/api-key'\nresp = httpx.post(url, headers=headers, json={\"api_key\": \"<string>\", \"chain_id\": \"153\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/api-key/generate": {
      "post": {
        "operationId": "generateApiKey",
        "summary": "Generate a new API key for the current user",
        "description": "Returns the plaintext key **exactly once** — clients must store\nit securely. The DB record stores only the hash + name + metadata.\nRequires a direct personal session. Agent-bound API-key, MCP and\nconnector sessions are refused with 403; they cannot mint a child key.\n\n`kind` decides the credential kind ONCE, on the row:\n`personal` (default) exchanges into the person's ordinary session;\n`connector` exchanges into a connector session — read/propose only\n(`CryptoOperations`/`PolicyExecution` stripped), `session_kind:\nconnector` on the JWT, chain bound to `allowed_chains`, never a\ndirect session (no delegation mint, no key management). A connector\nkey is what an MCP client presents to `/mcp`.\n\nA connector key naming a LIVE chain (any chain not in\n`auth.modes.test_chain_ids`) requires the caller's live-connector\nopt-in (`PUT /auth/users/me/live-connectors`, spec §5.4) — for every\nrole, since the flag is about a program-held credential on live\nmoney. Note the default for a staff caller who omits\n`allowed_chains` is the SYSTEM default chain, which is live on\nproduction: opt in first or name the test chain explicitly.\n",
        "tags": [
          "API keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "service_name"
                ],
                "properties": {
                  "service_name": {
                    "type": "string",
                    "description": "What the key is for — shown in listings."
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "expires_in_seconds": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "description": "Optional lifetime. Omitted = never expires. Must be positive."
                  },
                  "agent_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 100,
                    "description": "Bind the key to a registered agent (an assistant credential). Tokens exchanged from it carry an `agent_id` claim, echoed by /protected/jwt, so a consumer can tell the assistant's calls from the person's. Attribution only — the key's permissions stay the person's."
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "personal",
                      "connector"
                    ],
                    "default": "personal",
                    "description": "Credential kind, decided once here. `connector` keys exchange into connector sessions (see /auth/api-key)."
                  },
                  "allowed_chains": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string"
                    },
                    "description": "Connector keys only: the chains sessions may run on. Each\nmust be a configured AND enabled chain (400 otherwise).\nOmitted ⇒ the deployment's test chain\n(`auth.modes.test_chain_ids[0]`) for a consumer-role\ncaller, the system default otherwise. 400 when supplied\non a personal key.\n",
                    "example": [
                      "153"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Key generated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "api_key",
                    "service_name",
                    "created_at",
                    "kind"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "The new key's id — persist it to revoke later via POST /auth/api-keys/{id}/revoke."
                    },
                    "api_key": {
                      "type": "string",
                      "description": "Plaintext key — returned once."
                    },
                    "service_name": {
                      "type": "string"
                    },
                    "description": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "expires_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "agent_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Present when the key is agent-bound."
                    },
                    "kind": {
                      "type": "string",
                      "enum": [
                        "personal",
                        "connector"
                      ],
                      "description": "Echo of the persisted kind."
                    },
                    "allowed_chains": {
                      "type": [
                        "array",
                        "null"
                      ],
                      "items": {
                        "type": "string"
                      },
                      "description": "Connector keys only: the persisted chain restriction (absent on personal keys)."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Flat `{\"error\": \"...\"}` — unknown `kind`, an `allowed_chains`\nentry this deployment does not serve or has not enabled, or\n`allowed_chains` supplied on a personal key. (Pre-existing\nvalidation failures keep their empty 400 body.)\n"
          },
          "403": {
            "description": "Not a direct personal session (empty body, as before), or — for\na connector key — `allowed_chains` names a LIVE chain and the\ncaller has not enabled live connectors:\n`{\"error\": \"...\", \"code\": \"live_connector_opt_in_required\",\n\"chain_id\": \"151\"}`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "403 from `/auth/api-key/generate` (a connector key naming a live\nchain), `/auth/api-key` (a connector session landing on a live chain)\nand `/auth/refresh` (a connector lineage rotating onto a live chain):\nthe user has not enabled live connectors\n(`users.live_connectors_enabled`, agent surface spec §5.4). Not a\nretry-on-another-chain refusal — the person opts in from a direct\nsession via `PUT /auth/users/me/live-connectors`, or the credential\nuses a test chain. On `/auth/refresh` the token is not consumed.\n",
                  "required": [
                    "error",
                    "code",
                    "chain_id"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "const": "live_connector_opt_in_required"
                    },
                    "chain_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/api-key/generate \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"service_name\": \"<string>\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/api-key/generate', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"service_name\": \"<string>\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/api-key/generate'\nresp = httpx.post(url, headers=headers, json={\"service_name\": \"<string>\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/api-keys": {
      "get": {
        "operationId": "listApiKeys",
        "summary": "List the current user's API keys (metadata only)",
        "tags": [
          "API keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "API key metadata",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "required": [
                      "id",
                      "user_id",
                      "name",
                      "is_active",
                      "created_at"
                    ],
                    "description": "API-key metadata as returned by `GET /auth/api-keys`. The plaintext key is returned **only** on creation and stored hashed.",
                    "properties": {
                      "id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "user_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "name": {
                        "type": "string"
                      },
                      "is_active": {
                        "type": "boolean"
                      },
                      "created_at": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "last_used_at": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "format": "date-time"
                      },
                      "revoked_at": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "format": "date-time"
                      },
                      "kind": {
                        "type": "string",
                        "enum": [
                          "personal",
                          "connector"
                        ],
                        "description": "Credential kind decided at mint (`POST /auth/api-key/generate {kind}`)."
                      },
                      "allowed_chains": {
                        "type": [
                          "array",
                          "null"
                        ],
                        "items": {
                          "type": "string"
                        },
                        "description": "Connector keys only — the chains their sessions may run on; absent = unrestricted or a personal key."
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/api-keys \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/api-keys', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/api-keys'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/api-keys/{key_id}": {
      "get": {
        "operationId": "getApiKey",
        "summary": "Get a specific API key (metadata only)",
        "tags": [
          "API keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "key_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Key UUID (signature key or API key, depending on the route)."
          }
        ],
        "responses": {
          "200": {
            "description": "Key info",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "user_id",
                    "name",
                    "is_active",
                    "created_at"
                  ],
                  "description": "API-key metadata as returned by `GET /auth/api-keys`. The plaintext key is returned **only** on creation and stored hashed.",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "user_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "is_active": {
                      "type": "boolean"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "last_used_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "revoked_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "kind": {
                      "type": "string",
                      "enum": [
                        "personal",
                        "connector"
                      ],
                      "description": "Credential kind decided at mint (`POST /auth/api-key/generate {kind}`)."
                    },
                    "allowed_chains": {
                      "type": [
                        "array",
                        "null"
                      ],
                      "items": {
                        "type": "string"
                      },
                      "description": "Connector keys only — the chains their sessions may run on; absent = unrestricted or a personal key."
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/api-keys/{key_id} \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/api-keys/{key_id}', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/api-keys/{key_id}'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/api-keys/{key_id}/revoke": {
      "post": {
        "operationId": "revokeApiKey",
        "summary": "Revoke an API key (irreversible)",
        "tags": [
          "API keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "key_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Key UUID (signature key or API key, depending on the route)."
          }
        ],
        "responses": {
          "204": {
            "description": "Key revoked"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/api-keys/{key_id}/revoke \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/api-keys/{key_id}/revoke', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/api-keys/{key_id}/revoke'\nresp = httpx.post(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/api/v1/encrypt": {
      "post": {
        "operationId": "encryptData",
        "summary": "Encrypt a payload with a registered keypair",
        "description": "ECIES-style — the ciphertext is bound to the recipient's public\nkey and is independently decryptable only by the recipient via\n`/api/v1/decrypt`.\n",
        "tags": [
          "Keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "recipient_public_key",
                  "plaintext"
                ],
                "properties": {
                  "recipient_public_key": {
                    "type": "string"
                  },
                  "plaintext": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Encrypted blob",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ciphertext"
                  ],
                  "properties": {
                    "ciphertext": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/api/v1/encrypt \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"recipient_public_key\": \"<string>\",\n  \"plaintext\": \"<string>\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/api/v1/encrypt', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"recipient_public_key\": \"<string>\",\n    \"plaintext\": \"<string>\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/api/v1/encrypt'\nresp = httpx.post(url, headers=headers, json={\"recipient_public_key\": \"<string>\", \"plaintext\": \"<string>\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/api/v1/decrypt": {
      "post": {
        "operationId": "decryptData",
        "summary": "Decrypt a payload with the caller's encryption keypair",
        "tags": [
          "Keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "ciphertext"
                ],
                "properties": {
                  "ciphertext": {
                    "type": "string"
                  },
                  "key_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid",
                    "description": "Specific key to use; defaults to the active encryption key."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Decrypted blob",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "plaintext"
                  ],
                  "properties": {
                    "plaintext": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/api/v1/decrypt \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"ciphertext\": \"<string>\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/api/v1/decrypt', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"ciphertext\": \"<string>\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/api/v1/decrypt'\nresp = httpx.post(url, headers=headers, json={\"ciphertext\": \"<string>\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/api/v1/sign": {
      "post": {
        "operationId": "signData",
        "summary": "Sign data with the caller's signing keypair",
        "tags": [
          "Keys",
          "Signatures"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "message"
                ],
                "properties": {
                  "message": {
                    "type": "string"
                  },
                  "key_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signature",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "signature"
                  ],
                  "properties": {
                    "signature": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/api/v1/sign \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"message\": \"<string>\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/api/v1/sign', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"message\": \"<string>\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/api/v1/sign'\nresp = httpx.post(url, headers=headers, json={\"message\": \"<string>\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/api/v1/verify": {
      "post": {
        "operationId": "verifySignature",
        "summary": "Verify a signature against a registered public key",
        "tags": [
          "Keys",
          "Signatures"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "public_key",
                  "signature",
                  "message"
                ],
                "properties": {
                  "public_key": {
                    "type": "string"
                  },
                  "signature": {
                    "type": "string"
                  },
                  "message": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verification result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "valid"
                  ],
                  "properties": {
                    "valid": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/api/v1/verify \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"public_key\": \"<string>\",\n  \"signature\": \"<string>\",\n  \"message\": \"<string>\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/api/v1/verify', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"public_key\": \"<string>\",\n    \"signature\": \"<string>\",\n    \"message\": \"<string>\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/api/v1/verify'\nresp = httpx.post(url, headers=headers, json={\"public_key\": \"<string>\", \"signature\": \"<string>\", \"message\": \"<string>\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/api/v1/keys/{key_id}/info": {
      "get": {
        "operationId": "getKeyInfo",
        "summary": "Get metadata about a specific key",
        "tags": [
          "Keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "key_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Key UUID (signature key or API key, depending on the route)."
          }
        ],
        "responses": {
          "200": {
            "description": "Key metadata",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Keypair metadata."
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/api/v1/keys/{key_id}/info \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/api/v1/keys/{key_id}/info', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/api/v1/keys/{key_id}/info'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/api/v1/public-key/{contact_id}": {
      "get": {
        "operationId": "getPublicKeyByContact",
        "summary": "Get a contact's public key by hashed contact id",
        "description": "Returns the public key for a contact (user or group) identified by\n`contact_id`. The `purpose` query parameter selects which of the\ncontact's keys is returned:\n\n- **`purpose=encryption`** (default): returns the pubkey the ECIES\n  decrypt path will use — `KeyType::Encryption` if present,\n  falling back to any active key bearing an encrypted private\n  key (legacy email-user shape). This is the right key for\n  sender flows constructing encrypted payment payloads, because\n  the recipient's `decrypt_for_vault` resolves the matching\n  keypair via the same preference order.\n\n- **`purpose=signing`**: returns the contact's custodial\n  `KeyType::Signing` pubkey (External excluded). Use this when\n  deriving the signer EOA (`keccak256(uncompressed_pubkey)[12:]`)\n  — the EOA derived from this pubkey matches the EOA that\n  `ecrecover` produces from any signature `POST /api/v1/vault/sign`\n  generates for the same contact, because both apply the same\n  Signing-typed filter. This is what `Vault::signer_ethereum_address`\n  reads, and the EOA flows into on-chain factory lookups that\n  map `signer → contract` (e.g. `ClaimIssuerFactory.getClaimIssuer`).\n  Auth authorizes this lookup with signing intent. Consequently an\n  ordinary direct group delegation can resolve its human actor's\n  custodial signing pubkey under the actor-key sign-only exception;\n  it does not gain generic access to the actor's encryption material.\n  Nested group credentials remain bound to the exact selected human key:\n  custodial keys use Automatic execution through Auth's existing\n  custodial human-key signing primitive, while external ECDSA keys use\n  the ordinary Manual rail. No child-group Vault/key material is created\n  or used, and no group key is selected.\n  For any contact that has BOTH a Signing and an Encryption\n  keypair, the default (`purpose=encryption`) returns the wrong\n  key for EOA derivation — the recovered EOA from a signature\n  would not match. The two purposes were conflated in early\n  builds; explicit `purpose=signing` is the correctness fix.\n\n`contact_id` is the UUID of the user or group.\n",
        "tags": [
          "Keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "contact_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Hashed contact identifier (typically `keccak256(email_or_group_name)`); used by `/api/v1/public-key/{contact_id}` for ECIES recipient lookup."
          },
          {
            "name": "purpose",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "signing",
                "encryption"
              ],
              "default": "encryption"
            },
            "description": "Which key to return — `signing` for signer-EOA derivation,\n`encryption` (default) for ECIES recipient lookup. See the\nlong-form description above.\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Public key",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "public_key"
                  ],
                  "properties": {
                    "public_key": {
                      "type": "string"
                    },
                    "provider_type": {
                      "type": "string"
                    },
                    "contact_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (user, group, key, delegation token, wallet, provider).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "keypair": {
                    "summary": "Keypair UUID not found",
                    "value": {
                      "error": "keypair not found"
                    }
                  },
                  "kyc-provider": {
                    "summary": "Requested KYC provider not enabled in yaml",
                    "value": {
                      "error": "kyc provider not enabled"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET 'https://auth.yieldfabric.com/api/v1/public-key/{contact_id}?purpose=signing' \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/api/v1/public-key/{contact_id}?purpose=signing', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/api/v1/public-key/{contact_id}'\nresp = httpx.get(url, headers=headers, params={'purpose': 'signing'})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/api/v1/generate-keypair": {
      "post": {
        "operationId": "generateKeypairForContact",
        "summary": "Generate an encryption keypair for a contact id",
        "description": "Provisioned for wallet-led signups (Averer, MetaMask, WebAuthn)\nthat don't carry a server-custodied keypair from email signup.\nServer holds the private key under master-key encryption; the\npublic key is what gets registered on-chain.\n",
        "tags": [
          "Keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "contact_id"
                ],
                "properties": {
                  "contact_id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Keypair created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "public_key"
                  ],
                  "properties": {
                    "public_key": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/api/v1/generate-keypair \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"contact_id\": \"<string>\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/api/v1/generate-keypair', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"contact_id\": \"<string>\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/api/v1/generate-keypair'\nresp = httpx.post(url, headers=headers, json={\"contact_id\": \"<string>\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/api/v1/vault/decrypt": {
      "post": {
        "operationId": "vaultDecrypt",
        "summary": "Decrypt a vault payload on behalf of the caller's wallet",
        "description": "Server-side decryption of a ciphertext sent to the caller's\non-chain ECIES recipient. Used by sister services that have the\nciphertext but not the recipient's private key.\n",
        "tags": [
          "Keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "ciphertext",
                  "account_address"
                ],
                "properties": {
                  "ciphertext": {
                    "type": "string"
                  },
                  "account_address": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Decrypted blob",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "plaintext"
                  ],
                  "properties": {
                    "plaintext": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/api/v1/vault/decrypt \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"ciphertext\": \"<string>\",\n  \"account_address\": \"<string>\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/api/v1/vault/decrypt', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"ciphertext\": \"<string>\",\n    \"account_address\": \"<string>\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/api/v1/vault/decrypt'\nresp = httpx.post(url, headers=headers, json={\"ciphertext\": \"<string>\", \"account_address\": \"<string>\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/api/v1/vault/sign": {
      "post": {
        "operationId": "vaultSign",
        "summary": "Sign a vault meta-transaction on behalf of the caller's wallet",
        "description": "Server-side signing helper for sister services that need the\ncaller's wallet to authorise a vault operation but can't\nsurface the prompt to the user (e.g. server-driven workflows\nunder explicit user delegation).\n\nThe picker selects the oldest active `KeyType: Signing` keypair\nthat is **not** `KeyType: External`. External keys cannot be\nserver-signed by design — the private half lives on the user's\nwallet / device — so a 422 is returned at the picker if the\nonly candidates are external. This matches the cross-site rule\ndocumented in\n[`architecture/key-storage.md`](../../architecture/key-storage.md#auto-sign-key-selection--external-is-excluded-everywhere).\n",
        "tags": [
          "Keys",
          "Signatures"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "message",
                  "account_address"
                ],
                "properties": {
                  "message": {
                    "type": "string"
                  },
                  "account_address": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signature",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "signature"
                  ],
                  "properties": {
                    "signature": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "No custodial signing key available for this account — the\nonly candidates are `KeyType: External` and cannot be\nserver-signed. Caller should route the signature request\nto the user's wallet instead.\n"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/api/v1/vault/sign \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"message\": \"<string>\",\n  \"account_address\": \"<string>\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/api/v1/vault/sign', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"message\": \"<string>\",\n    \"account_address\": \"<string>\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/api/v1/vault/sign'\nresp = httpx.post(url, headers=headers, json={\"message\": \"<string>\", \"account_address\": \"<string>\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/api/v1/vault/create-salt": {
      "post": {
        "operationId": "vaultCreateSalt",
        "summary": "Derive a deterministic private salt for a vault account context",
        "description": "Server-side salt derivation helper for sister services that generate\nvault proofs. The salt is derived from the caller's custodial\nencryption key and the supplied chain, vault, and account addresses.\nThe returned value is private witness data for proof generation.\n",
        "tags": [
          "Keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "chain_id",
                  "vault_address",
                  "account_address"
                ],
                "properties": {
                  "chain_id": {
                    "type": "string"
                  },
                  "vault_address": {
                    "type": "string"
                  },
                  "account_address": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Derived salt",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "salt",
                    "salt_hex",
                    "version"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "salt": {
                      "type": "string"
                    },
                    "salt_hex": {
                      "type": "string"
                    },
                    "version": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or malformed input",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No active custodial encryption key exists for the caller",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "The caller's encryption key is external and cannot derive server-side salts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/api/v1/vault/create-salt \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"chain_id\": \"84532\",\n  \"vault_address\": \"0x0000000000000000000000000000000000000000\",\n  \"account_address\": \"0x0000000000000000000000000000000000000000\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/api/v1/vault/create-salt', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    chain_id: '84532',\n    vault_address: '0x0000000000000000000000000000000000000000',\n    account_address: '0x0000000000000000000000000000000000000000'\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/api/v1/vault/create-salt'\nresp = httpx.post(url, headers=headers, json={\"chain_id\": \"84532\", \"vault_address\": \"0x0000000000000000000000000000000000000000\", \"account_address\": \"0x0000000000000000000000000000000000000000\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/deploy-account": {
      "post": {
        "operationId": "deployAccount",
        "summary": "Deploy an additional typed account for the active actor",
        "description": "Legacy typed-wallet endpoint. Today the only accepted `type` is\n`auction`. The owner is the authenticated actor (the user, or the\n`acting_as` group); `entityId` is the new account's contact/category id.\nThe operation runs on the JWT's `default_chain_id` and requires the actor\nto have activated its canonical default account first. It is not the\nfirst-account activation endpoint. When the actor is a group, the\nacting-as token must carry `CryptoOperations` in its delegation scope.\n",
        "tags": [
          "Accounts"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "type",
                  "entityId"
                ],
                "additionalProperties": false,
                "properties": {
                  "type": {
                    "type": "string",
                    "const": "auction"
                  },
                  "entityId": {
                    "type": "string",
                    "description": "Contact/category id associated with the new auction wallet."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Deployment completed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "status",
                    "account_address",
                    "wallet_id",
                    "message"
                  ],
                  "properties": {
                    "status": {
                      "type": "string",
                      "const": "success"
                    },
                    "account_address": {
                      "type": "string",
                      "pattern": "^0x[0-9a-fA-F]{40}$"
                    },
                    "wallet_id": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or unsupported `type`."
          },
          "402": {
            "description": "Deploy billing pre-flight refused the relayer-gas work (card-less payer under `require_card_before_use`, delinquent, capped, …).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "402 refusal from the deploy billing pre-flight. Fresh account deploys\nare relayer-signed (no `CryptoOperations` involved), so the JWT-mint\nbilling gate's permission strips cannot reach them — the deploy\nendpoints instead re-run the payer health check server-side before\nsubmitting MQ work. `code` and `payer` mirror the JWT `billing_gate`\nand `billing_gate_payer` claim vocabulary so clients reuse the same\nrecovery routing (`card_required` opens inline card capture aimed at\n`payer_group_id` when the payer is a group).\n",
                  "required": [
                    "error",
                    "code",
                    "payer"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "enum": [
                        "card_required",
                        "delinquent",
                        "canceled",
                        "spend_capped",
                        "credit_plan_required",
                        "credit_exhausted",
                        "sponsor_capped"
                      ]
                    },
                    "payer": {
                      "type": "string",
                      "enum": [
                        "self",
                        "group"
                      ]
                    },
                    "payer_group_id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "Present when `payer` is `group` — the exact billing group to fix."
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Group acting-as token lacks `CryptoOperations` in its delegation scope."
          },
          "409": {
            "description": "Active actor has no canonical account on the JWT chain.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "code",
                    "entity_kind",
                    "entity_id",
                    "chain_id"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "const": "CHAIN_ACCOUNT_REQUIRED"
                    },
                    "entity_kind": {
                      "type": "string",
                      "enum": [
                        "user",
                        "group"
                      ]
                    },
                    "entity_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "chain_id": {
                      "type": "string"
                    },
                    "activation_url": {
                      "type": "string",
                      "description": "Exact recovery resource when the handler can name it."
                    },
                    "operation": {
                      "type": "string",
                      "description": "Stable operation label for diagnostics; clients route on `code` and the entity/chain fields."
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/deploy-account \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"type\":\"auction\",\"entityId\":\"auction-123\"}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/deploy-account', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({ type: 'auction', entityId: 'auction-123' }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/deploy-account'\nresp = httpx.post(url, headers=headers, json={'type': 'auction', 'entityId': 'auction-123'})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/users/{user_id}/deploy-account": {
      "post": {
        "operationId": "deployUserAccount",
        "summary": "Deploy an additional wallet for a user",
        "description": "Legacy additional-wallet endpoint. It deploys a new wallet on the\nauthenticated JWT's chain and reuses the user's existing ONCHAINID; it\ndoes not create or replace the canonical first account. Activate the\nuser's chain account through the activation resource before calling it.\n",
        "tags": [
          "Accounts",
          "Users"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "User UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "Deployment completed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "user_id",
                    "new_account_address",
                    "status"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "user_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "new_account_address": {
                      "type": "string",
                      "pattern": "^0x[0-9a-fA-F]{40}$"
                    },
                    "status": {
                      "type": "string",
                      "const": "success"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "Deploy billing pre-flight refused the relayer-gas work (card-less payer under `require_card_before_use`, delinquent, capped, …).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "402 refusal from the deploy billing pre-flight. Fresh account deploys\nare relayer-signed (no `CryptoOperations` involved), so the JWT-mint\nbilling gate's permission strips cannot reach them — the deploy\nendpoints instead re-run the payer health check server-side before\nsubmitting MQ work. `code` and `payer` mirror the JWT `billing_gate`\nand `billing_gate_payer` claim vocabulary so clients reuse the same\nrecovery routing (`card_required` opens inline card capture aimed at\n`payer_group_id` when the payer is a group).\n",
                  "required": [
                    "error",
                    "code",
                    "payer"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "enum": [
                        "card_required",
                        "delinquent",
                        "canceled",
                        "spend_capped",
                        "credit_plan_required",
                        "credit_exhausted",
                        "sponsor_capped"
                      ]
                    },
                    "payer": {
                      "type": "string",
                      "enum": [
                        "self",
                        "group"
                      ]
                    },
                    "payer_group_id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "Present when `payer` is `group` — the exact billing group to fix."
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller may not deploy an additional wallet for this user."
          },
          "409": {
            "description": "User has no canonical account on the JWT chain.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "code",
                    "entity_kind",
                    "entity_id",
                    "chain_id"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "const": "CHAIN_ACCOUNT_REQUIRED"
                    },
                    "entity_kind": {
                      "type": "string",
                      "enum": [
                        "user",
                        "group"
                      ]
                    },
                    "entity_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "chain_id": {
                      "type": "string"
                    },
                    "activation_url": {
                      "type": "string",
                      "description": "Exact recovery resource when the handler can name it."
                    },
                    "operation": {
                      "type": "string",
                      "description": "Stable operation label for diagnostics; clients route on `code` and the entity/chain fields."
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/users/{user_id}/deploy-account \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/users/{user_id}/deploy-account', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/users/{user_id}/deploy-account'\nresp = httpx.post(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/entities/{entity_type}/{entity_id}/default-chain": {
      "post": {
        "operationId": "swapDefaultChain",
        "summary": "Switch an entity's default chain",
        "description": "Compatibility selector for an account that is already deployed on the\ntarget chain. It never submits chain work. The matching wallet becomes\nthe entity's default for that chain. A missing wallet returns\n`CHAIN_ACCOUNT_REQUIRED`; use the explicit activation resource first.\nA user may select only their own entity; a group selection requires an\nactive Owner or Admin.\n\nThis endpoint prepares identity-store state but does not rewrite an\nalready-issued JWT. A regular Test/Live mode switch is simply a refresh\nwith the target `chain_id` and does not require this endpoint.\n",
        "tags": [
          "Accounts"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "entity_type",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "user",
                "group"
              ]
            },
            "description": "Activatable identity kind for the `/entities/**` routes."
          },
          {
            "name": "entity_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Entity UUID for the `/entities/**` routes."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "chain_id"
                ],
                "properties": {
                  "chain_id": {
                    "type": "string",
                    "description": "Target chain configured in the payments topology."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Default chain swapped",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "status",
                    "new_chain_id",
                    "wallet_id",
                    "account_address",
                    "deployed"
                  ],
                  "properties": {
                    "status": {
                      "type": "string",
                      "const": "ready"
                    },
                    "previous_wallet_id": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "new_chain_id": {
                      "type": "string"
                    },
                    "wallet_id": {
                      "type": "string"
                    },
                    "account_address": {
                      "type": "string"
                    },
                    "deployed": {
                      "type": "boolean",
                      "const": false
                    }
                  }
                }
              }
            }
          },
          "202": {
            "description": "An activation attempt already exists and is still `provisioning` or\n`pending_signature`. This selector remains read-only; poll the\nactivation resource rather than treating this response as a deploy.\n",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "new_chain_id",
                        "message_id"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "provisioning"
                        },
                        "new_chain_id": {
                          "type": "string"
                        },
                        "message_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "new_chain_id",
                        "message_id"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "pending_signature"
                        },
                        "new_chain_id": {
                          "type": "string"
                        },
                        "message_id": {
                          "type": "string",
                          "format": "uuid"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Unsupported/missing chain or invalid entity selector."
          },
          "403": {
            "description": "Another user's entity, or a group for which the caller is not Owner/Admin."
          },
          "409": {
            "description": "The entity has no deployed account (`CHAIN_ACCOUNT_REQUIRED`) or the configured chain is disabled (`CHAIN_UNAVAILABLE`).",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "code",
                        "new_chain_id",
                        "activation_url",
                        "error"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "chain_account_required"
                        },
                        "code": {
                          "type": "string",
                          "const": "CHAIN_ACCOUNT_REQUIRED"
                        },
                        "new_chain_id": {
                          "type": "string"
                        },
                        "activation_url": {
                          "type": "string"
                        },
                        "error": {
                          "type": "string"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "error",
                        "code",
                        "chain_id"
                      ],
                      "properties": {
                        "error": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "const": "CHAIN_UNAVAILABLE"
                        },
                        "chain_id": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/entities/{entity_type}/{entity_id}/default-chain \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"chain_id\": \"153\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/entities/{entity_type}/{entity_id}/default-chain', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"chain_id\": \"153\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/entities/{entity_type}/{entity_id}/default-chain'\nresp = httpx.post(url, headers=headers, json={\"chain_id\": \"153\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/entities/{entity_type}/{entity_id}/chain-accounts/{chain_id}/activation": {
      "post": {
        "operationId": "activateChainAccount",
        "summary": "Activate an entity account on one explicit chain",
        "description": "The canonical first-account deployment operation. The durable resource\nis single-flight per entity and chain. Ambiguous retries reuse the same\nattempt; a terminal failure is exposed as `failed_retryable`, and the\nnext POST advances to a fresh attempt. Wallet persistence occurs only\nafter confirmed execution. Existing wallets are checked for bytecode\nduring POST reconciliation so a reset chain can be healed safely.\n",
        "tags": [
          "Accounts"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "entity_type",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "user",
                "group"
              ]
            },
            "description": "Activatable identity kind for the `/entities/**` routes."
          },
          {
            "name": "entity_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Entity UUID for the `/entities/**` routes."
          },
          {
            "name": "chain_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "An enabled chain with an explicit payments route."
          }
        ],
        "responses": {
          "200": {
            "description": "Account ready",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "chain_id",
                        "wallet_id",
                        "account_address",
                        "deployed"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "ready"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "wallet_id": {
                          "type": "string"
                        },
                        "account_address": {
                          "type": "string",
                          "pattern": "^0x[0-9a-fA-F]{40}$"
                        },
                        "deployed": {
                          "type": "boolean",
                          "description": "True only when this request completed a new deployment."
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "chain_id",
                        "message_id"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "provisioning"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "message_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "chain_id",
                        "message_id"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "pending_signature"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "message_id": {
                          "type": "string",
                          "format": "uuid"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "chain_id",
                        "error"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "failed_retryable"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "error": {
                          "type": "string"
                        }
                      }
                    }
                  ],
                  "discriminator": {
                    "propertyName": "status"
                  }
                }
              }
            }
          },
          "202": {
            "description": "Deployment is still provisioning or defensively requires a signature.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "chain_id",
                        "wallet_id",
                        "account_address",
                        "deployed"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "ready"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "wallet_id": {
                          "type": "string"
                        },
                        "account_address": {
                          "type": "string",
                          "pattern": "^0x[0-9a-fA-F]{40}$"
                        },
                        "deployed": {
                          "type": "boolean",
                          "description": "True only when this request completed a new deployment."
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "chain_id",
                        "message_id"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "provisioning"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "message_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "chain_id",
                        "message_id"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "pending_signature"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "message_id": {
                          "type": "string",
                          "format": "uuid"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "chain_id",
                        "error"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "failed_retryable"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "error": {
                          "type": "string"
                        }
                      }
                    }
                  ],
                  "discriminator": {
                    "propertyName": "status"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or unsupported chain."
          },
          "402": {
            "description": "Deploy billing pre-flight refused a FRESH activation (card-less\npayer under `require_card_before_use`, delinquent, capped, …).\nOnly fresh starts are gated: an in-flight or legacy activation\n(existing journal row or wallet) stays reconcilable by a gated\npayer because reconciliation burns no new relayer gas. Group\nactivations gate the group's payer under its billing policy.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "402 refusal from the deploy billing pre-flight. Fresh account deploys\nare relayer-signed (no `CryptoOperations` involved), so the JWT-mint\nbilling gate's permission strips cannot reach them — the deploy\nendpoints instead re-run the payer health check server-side before\nsubmitting MQ work. `code` and `payer` mirror the JWT `billing_gate`\nand `billing_gate_payer` claim vocabulary so clients reuse the same\nrecovery routing (`card_required` opens inline card capture aimed at\n`payer_group_id` when the payer is a group).\n",
                  "required": [
                    "error",
                    "code",
                    "payer"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "enum": [
                        "card_required",
                        "delinquent",
                        "canceled",
                        "spend_capped",
                        "credit_plan_required",
                        "credit_exhausted",
                        "sponsor_capped"
                      ]
                    },
                    "payer": {
                      "type": "string",
                      "enum": [
                        "self",
                        "group"
                      ]
                    },
                    "payer_group_id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "Present when `payer` is `group` — the exact billing group to fix."
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Another user's entity, a group for which the caller is not Owner/Admin, or the caller is below the mode-aware activation verification floor.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "required": [
                        "error",
                        "code",
                        "chain_id",
                        "required_tier",
                        "current_tier"
                      ],
                      "properties": {
                        "error": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "const": "ACCOUNT_ACTIVATION_VERIFICATION_REQUIRED"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "required_tier": {
                          "type": "string",
                          "enum": [
                            "EmailVerified",
                            "KycVerified"
                          ]
                        },
                        "current_tier": {
                          "type": "string",
                          "enum": [
                            "Unverified",
                            "EmailVerified",
                            "KycVerified"
                          ]
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "error"
                      ],
                      "properties": {
                        "error": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Terminal attempt failure, disabled chain, or path/session chain mismatch.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "oneOf": [
                        {
                          "type": "object",
                          "required": [
                            "status",
                            "chain_id",
                            "wallet_id",
                            "account_address",
                            "deployed"
                          ],
                          "properties": {
                            "status": {
                              "type": "string",
                              "const": "ready"
                            },
                            "chain_id": {
                              "type": "string"
                            },
                            "wallet_id": {
                              "type": "string"
                            },
                            "account_address": {
                              "type": "string",
                              "pattern": "^0x[0-9a-fA-F]{40}$"
                            },
                            "deployed": {
                              "type": "boolean",
                              "description": "True only when this request completed a new deployment."
                            }
                          }
                        },
                        {
                          "type": "object",
                          "required": [
                            "status",
                            "chain_id",
                            "message_id"
                          ],
                          "properties": {
                            "status": {
                              "type": "string",
                              "const": "provisioning"
                            },
                            "chain_id": {
                              "type": "string"
                            },
                            "message_id": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "uuid"
                            }
                          }
                        },
                        {
                          "type": "object",
                          "required": [
                            "status",
                            "chain_id",
                            "message_id"
                          ],
                          "properties": {
                            "status": {
                              "type": "string",
                              "const": "pending_signature"
                            },
                            "chain_id": {
                              "type": "string"
                            },
                            "message_id": {
                              "type": "string",
                              "format": "uuid"
                            }
                          }
                        },
                        {
                          "type": "object",
                          "required": [
                            "status",
                            "chain_id",
                            "error"
                          ],
                          "properties": {
                            "status": {
                              "type": "string",
                              "const": "failed_retryable"
                            },
                            "chain_id": {
                              "type": "string"
                            },
                            "error": {
                              "type": "string"
                            }
                          }
                        }
                      ],
                      "discriminator": {
                        "propertyName": "status"
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "error",
                        "code",
                        "chain_id"
                      ],
                      "properties": {
                        "error": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "const": "CHAIN_UNAVAILABLE"
                        },
                        "chain_id": {
                          "type": "string"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "error",
                        "code",
                        "chain_id",
                        "session_chain_id"
                      ],
                      "properties": {
                        "error": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "const": "CHAIN_CONTEXT_MISMATCH"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "session_chain_id": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Per-user activation-attempt rate limit exceeded (12 per user and chain per hour).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "code",
                    "retry_after_seconds"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "const": "CHAIN_ACCOUNT_ACTIVATION_RATE_LIMITED"
                    },
                    "retry_after_seconds": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/entities/user/{entity_id}/chain-accounts/153/activation \\\n  -H \"Authorization: Bearer $TOKEN\""
          }
        ]
      },
      "get": {
        "operationId": "getChainAccountActivation",
        "summary": "Read chain-account activation status",
        "description": "RPC-independent durable readiness view. It never probes bytecode, calls\npayments, submits MQ work, mutates identity state, or advances the\nattempt sequence. Re-POST boundedly to reconcile an ambiguous attempt or\nstale wallet; POST owns the strict bytecode check.\n",
        "tags": [
          "Accounts"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "entity_type",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "user",
                "group"
              ]
            },
            "description": "Activatable identity kind for the `/entities/**` routes."
          },
          {
            "name": "entity_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Entity UUID for the `/entities/**` routes."
          },
          {
            "name": "chain_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current tagged activation state (`ready`, `provisioning`, `pending_signature`, or `failed_retryable`).",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "chain_id",
                        "wallet_id",
                        "account_address",
                        "deployed"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "ready"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "wallet_id": {
                          "type": "string"
                        },
                        "account_address": {
                          "type": "string",
                          "pattern": "^0x[0-9a-fA-F]{40}$"
                        },
                        "deployed": {
                          "type": "boolean",
                          "description": "True only when this request completed a new deployment."
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "chain_id",
                        "message_id"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "provisioning"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "message_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "chain_id",
                        "message_id"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "pending_signature"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "message_id": {
                          "type": "string",
                          "format": "uuid"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "status",
                        "chain_id",
                        "error"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "const": "failed_retryable"
                        },
                        "chain_id": {
                          "type": "string"
                        },
                        "error": {
                          "type": "string"
                        }
                      }
                    }
                  ],
                  "discriminator": {
                    "propertyName": "status"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or unsupported chain."
          },
          "403": {
            "description": "Caller cannot inspect this entity's activation."
          },
          "409": {
            "description": "Configured chain is not enabled (`CHAIN_UNAVAILABLE`)."
          }
        }
      }
    },
    "/entities/{entity_type}/{entity_id}/chain-accounts": {
      "get": {
        "operationId": "listChainAccounts",
        "summary": "List an entity's deployed accounts across chains",
        "tags": [
          "Accounts"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "entity_type",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "user",
                "group"
              ]
            },
            "description": "Activatable identity kind for the `/entities/**` routes."
          },
          {
            "name": "entity_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Entity UUID for the `/entities/**` routes."
          }
        ],
        "responses": {
          "200": {
            "description": "Accounts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "required": [
                      "entity_id",
                      "entity_type",
                      "chain_id",
                      "account_address",
                      "wallet_id",
                      "is_default"
                    ],
                    "properties": {
                      "entity_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "entity_type": {
                        "type": "string",
                        "enum": [
                          "user",
                          "group"
                        ]
                      },
                      "chain_id": {
                        "type": "string"
                      },
                      "account_address": {
                        "type": "string"
                      },
                      "wallet_id": {
                        "type": "string"
                      },
                      "is_default": {
                        "type": "boolean"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/entities/{entity_type}/{entity_id}/chain-accounts \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/entities/{entity_type}/{entity_id}/chain-accounts', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/entities/{entity_type}/{entity_id}/chain-accounts'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/sumsub/access-token": {
      "get": {
        "operationId": "createSumsubAccessToken",
        "summary": "Mint a Sumsub WebSDK access token",
        "description": "Mints a short-lived token for one configured Sumsub provider instance\nand pre-records the subject's verification as `pending`.\n\n`level` is a backwards-compatible equality assertion only. The\ninstance's server-configured `level_name` (or\n`id-and-liveness` fallback) is authoritative; a different requested\nlevel is rejected with 400. A prior `FINAL` Sumsub rejection is not\nretryable and returns 409. The instance must have its app token,\nsecret key, and webhook secret configured or the endpoint returns 503.\n\nWithout `group_id`, the caller must present a direct user session and\nthe instance must declare `subject: user`. With `group_id`, the caller\nmust manage the group and the instance must declare `subject: group`.\n",
        "tags": [
          "Verifications"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "provider",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Sumsub provider instance id; defaults to the user-subject Sumsub instance."
          },
          {
            "name": "level",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Optional equality assertion against the configured level; never selects a different level."
          },
          {
            "name": "group_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Group subject for KYB; omitted for personal KYC."
          }
        ],
        "responses": {
          "200": {
            "description": "Access token minted and pending state recorded",
            "headers": {
              "Cache-Control": {
                "description": "Sensitive token responses are never cacheable.",
                "schema": {
                  "type": "string",
                  "example": "no-store, private"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "token",
                    "provider",
                    "level"
                  ],
                  "properties": {
                    "token": {
                      "type": "string"
                    },
                    "provider": {
                      "type": "string"
                    },
                    "level": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Level mismatch or provider/subject mismatch"
          },
          "401": {
            "description": "Missing or invalid user session"
          },
          "403": {
            "description": "Caller cannot manage the requested group"
          },
          "404": {
            "description": "Unknown Sumsub instance"
          },
          "409": {
            "description": "Sumsub marked the prior review FINAL"
          },
          "503": {
            "description": "Sumsub instance is disabled or incompletely configured"
          }
        }
      }
    },
    "/auth/identity/status": {
      "get": {
        "operationId": "getIdentityStatus",
        "summary": "Read identity-verification status",
        "description": "Reads the latest non-revoked verification for the caller or an\nadmin-managed group. Active flows should always pass `provider` and\n`credential_type`; this prevents evidence from a different vendor,\nSumsub app, or verification level from completing the current flow.\n\nSumsub `RED` outcomes are normalized to wire status `rejected` with\n`rejection_type: RETRY|FINAL` and `retryable: true|false`. Pending\nSumsub rows are reconciled against the applicant API using a\ndatabase-claimed exponential backoff, so frequent client polling does\nnot produce frequent upstream calls.\n",
        "tags": [
          "Verifications"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "group_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "provider",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Exact KYC provider instance id."
          },
          {
            "name": "credential_type",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Native or canonical credential name; canonicalized when provider is present."
          }
        ],
        "responses": {
          "200": {
            "description": "Current status (including `unverified` when no matching row exists)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "status"
                  ],
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "unverified",
                        "pending",
                        "verified",
                        "rejected",
                        "expired"
                      ]
                    },
                    "provider": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "credential_type": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "session_id": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "verified_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "created_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "rejection_type": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "RETRY",
                        "FINAL",
                        null
                      ]
                    },
                    "retryable": {
                      "type": [
                        "boolean",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Provider/subject mismatch"
          },
          "401": {
            "description": "Missing or invalid user session"
          },
          "403": {
            "description": "Caller cannot read the requested group"
          },
          "404": {
            "description": "Unknown or disabled provider instance"
          }
        }
      }
    },
    "/auth/verifications/credential-issued": {
      "post": {
        "operationId": "credentialIssued",
        "summary": "Record a credential-issuance event",
        "description": "Records that a KYC credential was issued (e.g. ONCHAINID claim).\nUsed by the frontend after a provider flow completes to update\nthe user's verification record.\n",
        "tags": [
          "Verifications"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "provider",
                  "credential_type"
                ],
                "properties": {
                  "provider": {
                    "type": "string"
                  },
                  "credential_type": {
                    "type": "string"
                  },
                  "proof_reference": {
                    "type": "string"
                  },
                  "proof_chain_id": {
                    "type": "integer"
                  },
                  "attributes_disclosed": {
                    "type": "object"
                  },
                  "group_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Credential record",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "credential_type": {
                      "type": "string"
                    },
                    "provider": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "proof_reference": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "proof_chain_id": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "issued_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "expires_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/verifications/credential-issued \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"provider\": \"sumsub\",\n  \"credential_type\": \"id-and-liveness\",\n  \"proof_reference\": \"<applicant-id>\",\n  \"attributes_disclosed\": {}\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/verifications/credential-issued', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    provider: 'sumsub',\n    credential_type: 'id-and-liveness',\n    proof_reference: '<applicant-id>',\n    attributes_disclosed: {},\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/verifications/credential-issued'\nresp = httpx.post(url, headers=headers, json={'provider': 'sumsub', 'credential_type': 'id-and-liveness', 'proof_reference': '<applicant-id>', 'attributes_disclosed': {}})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/verifications/me": {
      "get": {
        "operationId": "listMyVerifications",
        "summary": "List the current user's verification records",
        "tags": [
          "Verifications"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Verifications",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "credentials"
                  ],
                  "properties": {
                    "credentials": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "provider": {
                            "type": "string"
                          },
                          "credential_type": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          },
                          "proof_reference": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "proof_chain_id": {
                            "type": [
                              "integer",
                              "null"
                            ]
                          },
                          "issued_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          },
                          "expires_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/verifications/me \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/verifications/me', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/verifications/me'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/webhooks/kyc/{instance}": {
      "post": {
        "operationId": "kycInstanceWebhook",
        "summary": "Instance-addressed KYC provider callback (HMAC-validated)",
        "description": "Public endpoint — the canonical webhook receiver for EVERY\nconfigured KYC provider instance. KYC providers are\nmulti-instance (`auth.kyc_providers.enabled` may list several\nSumsub apps / Stripe accounts, each a settings block with its own\nwebhook secret); point each vendor app's dashboard at\n`/webhooks/kyc/<instance-id>`. The delivery is validated against\nthe NAMED instance's secret — a payload signed by one vendor app\ncan never land under another instance's id. Unknown instances\nreturn 404.\n\n**Not authenticated via JWT** — the HMAC signature is the only\nauth boundary. Treat the entire request body as untrusted until\nvalidated. Sumsub accepts `HMAC_SHA256_HEX` (default) and\n`HMAC_SHA512_HEX` from `X-Payload-Digest-Alg`; SHA-1 and unknown\nalgorithms are rejected. Signature-valid event types other than\n`applicantReviewed` are acknowledged and ignored. Delivery ordering\nuses the provider's signed `createdAtMs` event time (UTC text or epoch\nmilliseconds), not receipt order or a wall-clock freshness window.\n",
        "tags": [
          "Verifications"
        ],
        "security": [],
        "parameters": [
          {
            "name": "instance",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "KYC provider instance id — a key under `auth.kyc_providers`\nin system.yaml (e.g. `sumsub`, `sumsub_platform_a`,\n`stripe_identity`).\n"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Vendor-specific webhook payload (Sumsub / Stripe)."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Webhook accepted"
          },
          "401": {
            "description": "HMAC signature verification failed"
          },
          "404": {
            "description": "Unknown provider instance"
          },
          "502": {
            "description": "Valid delivery could not be processed; provider should retry"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/webhooks/kyc/sumsub_platform_a \\\n  -H 'Content-Type: application/json' \\\n  -H 'X-Payload-Digest-Alg: HMAC_SHA256_HEX' \\\n  -H \"X-Payload-Digest: $DIGEST_OVER_EXACT_BODY\" \\\n  --data-binary \"$BODY\""
          }
        ]
      }
    },
    "/webhooks/sumsub": {
      "post": {
        "operationId": "sumsubWebhook",
        "summary": "Sumsub KYC callback (HMAC-validated)",
        "description": "Public endpoint — authentication is HMAC over the request body\nusing a shared secret configured per deployment. Fixed-path\nconvenience form of `/webhooks/kyc/{instance}` for the\nconventionally-named `sumsub` instance (the sibling\n`/webhooks/stripe-identity` serves the `stripe_identity`\ninstance). Additional instances of either vendor use their\ninstance-addressed URL. Records the KYC outcome and may trigger\ndownstream flows.\n\n**Not authenticated via JWT** — the HMAC signature is the only\nauth boundary. Treat the entire request body as untrusted until\nvalidated. See the instance-addressed operation for digest algorithms,\nevent filtering, timestamp ordering, and retry behavior.\n",
        "tags": [
          "Verifications"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Sumsub-specific webhook payload (see Sumsub docs)."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Webhook accepted"
          },
          "401": {
            "description": "HMAC signature verification failed"
          },
          "502": {
            "description": "Valid delivery could not be processed; provider should retry"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/webhooks/sumsub \\\n  -H 'Content-Type: application/json' \\\n  -H 'X-Payload-Digest-Alg: HMAC_SHA256_HEX' \\\n  -H \"X-Payload-Digest: $DIGEST_OVER_EXACT_BODY\" \\\n  --data-binary \"$BODY\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "import { createHmac } from 'node:crypto';\nconst body = JSON.stringify({ type: 'applicantReviewed', applicantId: 'app_…', externalUserId: 'user:<uuid>', createdAtMs: '2026-04-30 08:04:23.379', reviewResult: { reviewAnswer: 'GREEN' } });\nconst digest = createHmac('sha256', process.env.SUMSUB_WEBHOOK_SECRET!).update(body).digest('hex');\nconst resp = await fetch('https://auth.yieldfabric.com/webhooks/sumsub', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json', 'X-Payload-Digest-Alg': 'HMAC_SHA256_HEX', 'X-Payload-Digest': digest },\n  body,\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import hashlib, hmac, httpx, json, os\nbody = json.dumps({'type': 'applicantReviewed', 'applicantId': 'app_…', 'externalUserId': 'user:<uuid>', 'createdAtMs': '2026-04-30 08:04:23.379', 'reviewResult': {'reviewAnswer': 'GREEN'}}, separators=(',', ':')).encode()\ndigest = hmac.new(os.environ['SUMSUB_WEBHOOK_SECRET'].encode(), body, hashlib.sha256).hexdigest()\nheaders = {'Content-Type': 'application/json', 'X-Payload-Digest-Alg': 'HMAC_SHA256_HEX', 'X-Payload-Digest': digest}\nresp = httpx.post('https://auth.yieldfabric.com/webhooks/sumsub', headers=headers, content=body)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/groups/{group_id}/onboarding-policy": {
      "get": {
        "operationId": "getGroupOnboardingPolicy",
        "summary": "Read a platform room's onboarding policy",
        "description": "Manager-gated (group owner/admin). Returns the policy document\n(`null` when the room has none — deployment defaults apply) plus\nthe room's bound `verification_profile` name for a one-call\nowner-UI read.\n",
        "tags": [
          "OnboardingPolicy"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "group_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The room's onboarding configuration.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "group_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "verification_profile": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Catalog profile name bound to the room."
                    },
                    "policy": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "require_email": {
                          "type": "boolean"
                        },
                        "auth_providers": {
                          "type": [
                            "array",
                            "null"
                          ],
                          "items": {
                            "type": "string"
                          }
                        },
                        "auth_primary": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "kyc_primary": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "ux_steps": {
                          "type": "object"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Caller is not an owner/admin of the group."
          }
        }
      },
      "put": {
        "operationId": "putGroupOnboardingPolicy",
        "summary": "Set a platform room's onboarding policy",
        "description": "Manager-gated. FULL-DOCUMENT semantics: the policy row becomes\nexactly the body (absent knobs reset to defaults). Every provider\nreference is validated against the deployment's enabled sets — a\npolicy can narrow the deployment menu, never extend it.\n\n`verification_profile` is the one write-through key, targeting\n`groups.required_verification_profile`: key ABSENT = leave\nunchanged, `null` = clear, a name = set (must exist in the\ndeployment catalog — see `GET /auth/verification-profiles`).\n",
        "tags": [
          "OnboardingPolicy"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "group_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "require_email": {
                    "type": "boolean",
                    "default": false
                  },
                  "auth_providers": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string"
                    },
                    "description": "Non-empty subset of the deployment's enabled sign-in providers; omit to inherit."
                  },
                  "auth_primary": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "kyc_primary": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "ux_steps": {
                    "type": "object",
                    "description": "Free-form wizard hints, ≤ 2 KB serialized."
                  },
                  "verification_profile": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Write-through to the room's required verification profile (absent = unchanged)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated configuration (same shape as GET)."
          },
          "400": {
            "description": "Unknown provider / profile",
            "empty subset": null,
            "or oversized ux_steps.": null
          },
          "403": {
            "description": "Caller is not an owner/admin of the group."
          },
          "404": {
            "description": "Group not found (profile write-through only)."
          }
        }
      }
    },
    "/auth/verification-profiles": {
      "get": {
        "operationId": "listVerificationProfiles",
        "summary": "List the deployment's verification-profile catalog",
        "description": "The named KYC requirement profiles this deployment defines\n(`auth.verification_profiles` in system.yaml) — names plus their\n`any_of` / `all_of` requirement shapes (canonical credential\nslugs, optional provider pins). Powers the owner UI's profile\npicker. Authenticated; not manager-gated (deployment config, not\nper-group data). Empty catalog ⇒ `{ \"profiles\": [] }`.\n",
        "tags": [
          "OnboardingPolicy"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The catalog.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "profiles": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "any_of": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "credential": {
                                  "type": "string"
                                },
                                "providers": {
                                  "type": "array",
                                  "items": {
                                    "type": "string"
                                  }
                                }
                              }
                            }
                          },
                          "all_of": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "credential": {
                                  "type": "string"
                                },
                                "providers": {
                                  "type": "array",
                                  "items": {
                                    "type": "string"
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/auth/billing/catalog": {
      "get": {
        "operationId": "getPublicBillingCatalog",
        "summary": "Read the public prepaid plan catalog",
        "description": "Unauthenticated, non-secret pricing shown before account creation. It\nreturns every valid user- and group-audience plan, but never returns a\nStripe Price id. `catalog_version` is a deterministic SHA-256 fingerprint\nover every purchase-relevant term, including private Stripe topology,\nand is also returned as the response ETag.\n\nThis is an in-memory manifest read: it performs no database or Stripe\nrequest. It always returns 200. When billing or prepaid mode is disabled,\n`prepaid_credits_enabled` is false and `credit_plans` is empty so a billing\noutage or staged rollout cannot block signup.\n",
        "security": [],
        "responses": {
          "200": {
            "description": "Current public plan catalog or a stable disabled view.",
            "headers": {
              "ETag": {
                "description": "Quoted `catalog_version` for cache revalidation.",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "description": "Public five-minute cache policy with mandatory revalidation.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "prepaid_credits_enabled",
                    "catalog_version",
                    "credit_plans"
                  ],
                  "properties": {
                    "prepaid_credits_enabled": {
                      "type": "boolean"
                    },
                    "catalog_version": {
                      "type": "string",
                      "pattern": "^sha256:[0-9a-f]{64}$"
                    },
                    "credit_plans": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "id",
                          "display_name",
                          "owner_type",
                          "monthly_price_usd",
                          "granted_allowance_usd",
                          "promotional_bonus_usd"
                        ],
                        "properties": {
                          "id": {
                            "type": "string",
                            "maxLength": 64
                          },
                          "display_name": {
                            "type": "string",
                            "maxLength": 80
                          },
                          "owner_type": {
                            "type": "string",
                            "enum": [
                              "user",
                              "group"
                            ]
                          },
                          "monthly_price_usd": {
                            "type": "number",
                            "minimum": 0.5
                          },
                          "granted_allowance_usd": {
                            "type": "number",
                            "minimum": 0.01
                          },
                          "promotional_bonus_usd": {
                            "type": "number",
                            "minimum": 0
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/auth/users/me/onboarding-plan-intent": {
      "get": {
        "operationId": "getMyOnboardingPlanIntent",
        "summary": "Resume the current user's pre-payment plan selection",
        "description": "Returns the durable plan choice made during onboarding. A missing choice\nis a successful `intent: null`, including while prepaid mode is disabled.\n`selected_plan` is the exact safe commercial snapshot the user selected;\n`current_plan` is the current public SKU or null when it was removed.\n`catalog_current` compares the selected SKU's complete snapshot,\nincluding the server-only Stripe Price id, without exposing that id.\nChanges to unrelated plans do not invalidate this intent.\n\nThis resource records intent only. It does not create a Stripe Customer,\nopen Checkout, bind a subscription, grant credit, or complete onboarding.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Current catalog version and optional durable selection.",
            "headers": {
              "Cache-Control": {
                "description": "`private, no-store`; authenticated intent responses must not be cached.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "catalog_version",
                    "intent"
                  ],
                  "properties": {
                    "catalog_version": {
                      "type": "string",
                      "pattern": "^sha256:[0-9a-f]{64}$"
                    },
                    "intent": {
                      "nullable": true,
                      "type": "object",
                      "required": [
                        "plan_id",
                        "owner_type",
                        "catalog_current",
                        "selected_at",
                        "updated_at",
                        "selected_plan",
                        "current_plan"
                      ],
                      "properties": {
                        "plan_id": {
                          "type": "string",
                          "maxLength": 64
                        },
                        "owner_type": {
                          "type": "string",
                          "enum": [
                            "user",
                            "group"
                          ]
                        },
                        "catalog_current": {
                          "type": "boolean"
                        },
                        "selected_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "selected_plan": {
                          "type": "object",
                          "required": [
                            "id",
                            "display_name",
                            "owner_type",
                            "monthly_price_usd",
                            "granted_allowance_usd",
                            "promotional_bonus_usd"
                          ],
                          "properties": {
                            "id": {
                              "type": "string",
                              "maxLength": 64
                            },
                            "display_name": {
                              "type": "string",
                              "maxLength": 80
                            },
                            "owner_type": {
                              "type": "string",
                              "enum": [
                                "user",
                                "group"
                              ]
                            },
                            "monthly_price_usd": {
                              "type": "number"
                            },
                            "granted_allowance_usd": {
                              "type": "number"
                            },
                            "promotional_bonus_usd": {
                              "type": "number"
                            }
                          }
                        },
                        "current_plan": {
                          "nullable": true,
                          "type": "object",
                          "required": [
                            "id",
                            "display_name",
                            "owner_type",
                            "monthly_price_usd",
                            "granted_allowance_usd",
                            "promotional_bonus_usd"
                          ],
                          "properties": {
                            "id": {
                              "type": "string",
                              "maxLength": 64
                            },
                            "display_name": {
                              "type": "string",
                              "maxLength": 80
                            },
                            "owner_type": {
                              "type": "string",
                              "enum": [
                                "user",
                                "group"
                              ]
                            },
                            "monthly_price_usd": {
                              "type": "number"
                            },
                            "granted_allowance_usd": {
                              "type": "number"
                            },
                            "promotional_bonus_usd": {
                              "type": "number"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing",
            "invalid": null,
            "expired": null,
            "or non-user bearer token.": null
          },
          "500": {
            "description": "Plan selection could not be read."
          }
        }
      },
      "put": {
        "operationId": "putMyOnboardingPlanIntent",
        "summary": "Select or replace the current user's onboarding plan intent",
        "description": "Resolves `plan_id` exclusively from the effective configured catalog and\natomically snapshots the owner audience, hidden Stripe Price id, charge,\nbase allowance, and promotional bonus as integer micro-USD. The first\n`selected_at` is preserved; subsequent PUTs replace the full snapshot and\nadvance `updated_at`. The response has the same shape as GET and never\nreturns the Stripe Price id.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "plan_id"
                ],
                "properties": {
                  "plan_id": {
                    "type": "string",
                    "maxLength": 64
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Selection persisted; response shape is identical to GET.",
            "headers": {
              "Cache-Control": {
                "description": "`private, no-store`; authenticated intent responses must not be cached.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "catalog_version",
                    "intent"
                  ],
                  "properties": {
                    "catalog_version": {
                      "type": "string",
                      "pattern": "^sha256:[0-9a-f]{64}$"
                    },
                    "intent": {
                      "nullable": true,
                      "type": "object",
                      "required": [
                        "plan_id",
                        "owner_type",
                        "catalog_current",
                        "selected_at",
                        "updated_at",
                        "selected_plan",
                        "current_plan"
                      ],
                      "properties": {
                        "plan_id": {
                          "type": "string",
                          "maxLength": 64
                        },
                        "owner_type": {
                          "type": "string",
                          "enum": [
                            "user",
                            "group"
                          ]
                        },
                        "catalog_current": {
                          "type": "boolean"
                        },
                        "selected_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "selected_plan": {
                          "type": "object",
                          "required": [
                            "id",
                            "display_name",
                            "owner_type",
                            "monthly_price_usd",
                            "granted_allowance_usd",
                            "promotional_bonus_usd"
                          ],
                          "properties": {
                            "id": {
                              "type": "string",
                              "maxLength": 64
                            },
                            "display_name": {
                              "type": "string",
                              "maxLength": 80
                            },
                            "owner_type": {
                              "type": "string",
                              "enum": [
                                "user",
                                "group"
                              ]
                            },
                            "monthly_price_usd": {
                              "type": "number"
                            },
                            "granted_allowance_usd": {
                              "type": "number"
                            },
                            "promotional_bonus_usd": {
                              "type": "number"
                            }
                          }
                        },
                        "current_plan": {
                          "nullable": true,
                          "type": "object",
                          "required": [
                            "id",
                            "display_name",
                            "owner_type",
                            "monthly_price_usd",
                            "granted_allowance_usd",
                            "promotional_bonus_usd"
                          ],
                          "properties": {
                            "id": {
                              "type": "string",
                              "maxLength": 64
                            },
                            "display_name": {
                              "type": "string",
                              "maxLength": 80
                            },
                            "owner_type": {
                              "type": "string",
                              "enum": [
                                "user",
                                "group"
                              ]
                            },
                            "monthly_price_usd": {
                              "type": "number"
                            },
                            "granted_allowance_usd": {
                              "type": "number"
                            },
                            "promotional_bonus_usd": {
                              "type": "number"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or unknown/unavailable plan id."
          },
          "401": {
            "description": "Missing",
            "invalid": null,
            "expired": null,
            "or non-user bearer token.": null
          },
          "409": {
            "description": "Prepaid plan selection is disabled on this deployment."
          },
          "429": {
            "description": "Per-user plan-selection rate limit exceeded."
          },
          "500": {
            "description": "Plan selection could not be saved."
          }
        }
      }
    },
    "/auth/billing/gas-pricing": {
      "get": {
        "operationId": "getGasPricing",
        "summary": "Effective gas pricing (public — no auth)",
        "description": "The pricing the platform bills gas with, served publicly so\ncustomer-facing transaction surfaces (wallet action-details\ndrawer, activity feeds) can display the SAME billed amount as the\nUsage & billing page, computed with the same formula — instead of\nhardcoding a second pricebook client-side. Per chain, exactly one\nmode is populated: `usd_per_gas` (protocol-USD-pegged chains like\nRedbelly — billed = gas_used × usd_per_gas × gas_markup) or\n`native_usd` (floating chains — billed = cost_wei ÷ 1e18 ×\nnative_usd × gas_markup). Non-secret product pricing.\n\nAlso carries `agent_minute_usd` — the consumer-facing work unit\nAI usage displays in (`minutes = billed_usd / agent_minute_usd`).\nDisplay-layer only: Stripe meters keep billing integer micro-USD;\nthe rate just names the unit customers reason in. Null = display\nraw tokens.\n",
        "responses": {
          "200": {
            "description": "Current pricebook gas parameters.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "billing_enabled",
                    "gas_markup",
                    "chains"
                  ],
                  "properties": {
                    "billing_enabled": {
                      "type": "boolean"
                    },
                    "gas_markup": {
                      "type": "number"
                    },
                    "agent_minute_usd": {
                      "type": "number",
                      "nullable": true,
                      "description": "USD per agent-minute — the display unit for AI usage. Null = unit disabled."
                    },
                    "chains": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "chain_id"
                        ],
                        "properties": {
                          "chain_id": {
                            "type": "string"
                          },
                          "usd_per_gas": {
                            "type": "number",
                            "nullable": true,
                            "description": "USD per gas unit (USD-pegged chains)."
                          },
                          "native_usd": {
                            "type": "number",
                            "nullable": true,
                            "description": "USD per whole native token (floating chains)."
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Billing not configured on this deployment."
          }
        }
      }
    },
    "/auth/billing/me": {
      "get": {
        "operationId": "getMyBilling",
        "summary": "Own billing account status + current-period usage totals",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Account summary. `status` is one of active | past_due | delinquent | canceled | reactivating; `reactivating` remains gated until Stripe reconciliation finishes. `period_totals` sums the calendar month per meter in integer micro-USD (1000000 = $1). `period_totals`, `daily`, and `breakdown` aggregate EVERYTHING the caller pays — their personal account plus every group account routed to them as billing contact for which they remain a live direct Owner/Admin — so the payer's charts include group costs; the personal-only figure is `spend.period_usd_micros` (the cap meter's basis; the spend cap gates each billing account separately). `recent_line_items` stays personal — group items are served by the group line-items endpoint. Aggregate rows also carry `agent_ms` (AI work quantity in milliseconds, null when the agent-minute unit is disabled) — authoritative for time display because per-room / per-agent unit-rate overrides scale the DOLLARS of a line item, never its minutes; line items priced under an override carry `detail.unit_usd_per_minute`, `detail.rate_source` (`agent`|`room`), `detail.rate_clamped`, and `detail.agent_ms`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "billed_groups": {
                      "type": "array",
                      "description": "Groups whose bill lands on this user as the resolved billing contact and for which the user remains a live direct Owner/Admin. Group-attributed usage lives on the group account, not the payer's personal one.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "account_id": {
                            "type": "string",
                            "format": "uuid",
                            "description": "The group's billing-account id."
                          },
                          "group_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "group_name": {
                            "type": "string"
                          },
                          "policy": {
                            "type": "string",
                            "enum": [
                              "admin_pays",
                              "member_pays"
                            ]
                          },
                          "status": {
                            "type": "string"
                          },
                          "period_usd_micros": {
                            "type": "integer",
                            "format": "int64"
                          },
                          "line_count": {
                            "type": "integer",
                            "format": "int64"
                          }
                        }
                      }
                    },
                    "spend": {
                      "type": "object",
                      "description": "Spend-cap meter (bill-shock brake). `capped` mirrors the JWT-mint gate — billable actions pause until the month rolls over. `cap_usd_micros` null = cap disabled.",
                      "properties": {
                        "period_usd_micros": {
                          "type": "integer",
                          "format": "int64"
                        },
                        "cap_usd_micros": {
                          "type": "integer",
                          "format": "int64",
                          "nullable": true
                        },
                        "capped": {
                          "type": "boolean"
                        }
                      }
                    },
                    "owner_type": {
                      "type": "string",
                      "enum": [
                        "user"
                      ]
                    },
                    "owner_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "active",
                        "past_due",
                        "delinquent",
                        "canceled",
                        "reactivating"
                      ]
                    },
                    "delinquent_since": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    },
                    "has_payment_customer": {
                      "type": "boolean"
                    },
                    "has_payment_method": {
                      "type": "boolean",
                      "description": "Card actually on file (synced from Stripe via webhooks + the payment-method sync endpoint). Distinct from has_payment_customer, which is ~always true once anything metered (customers are auto-created)."
                    },
                    "require_card_before_use": {
                      "type": "boolean",
                      "description": "Deployment runs the card-required-before-paid-use model (auth.billing.require_card_before_use + AUTH_BILLING_REQUIRE_CARD override + the gate kill-switch)."
                    },
                    "card_required": {
                      "type": "boolean",
                      "description": "This payer is live-gated on a missing card (mirrors the JWT-mint decision, staff exemption included). The wallet-SDK branches its blocking 'add a card to continue' banner on this."
                    },
                    "prepaid_credits_enabled": {
                      "type": "boolean",
                      "description": "Deployment exposes prepaid credit purchases. Independent of the emergency billing-gate kill-switch, which can pause permission stripping without hiding the purchase surface."
                    },
                    "credit_balance_micros": {
                      "type": "integer",
                      "format": "int64",
                      "description": "Auth-owned spend-authorization balance in micro-USD (1000000 = $1), transactionally debited with usage ingestion."
                    },
                    "stripe_credit_balance_micros": {
                      "type": "integer",
                      "format": "int64",
                      "description": "Stripe customer-credit reconciliation mirror. It is not used as spend authorization."
                    },
                    "credit_plan_id": {
                      "type": "string",
                      "nullable": true,
                      "description": "Bound public plan SKU from credit_plans; null for a post-paid or not-yet-bound account."
                    },
                    "credit_plan_renews_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true,
                      "description": "Funded plan cycle end from the paid Stripe invoice. Null until the first paid invoice synchronizes the cycle."
                    },
                    "prepaid_plan_required": {
                      "type": "boolean",
                      "description": "Persisted prepaid creation-cohort marker. A planless owner is gated only when this is true; legacy post-paid owners remain false."
                    },
                    "prepaid_repurchase_required": {
                      "type": "boolean",
                      "description": "The canceled account has prepaid purchase history and must recover through plan Checkout rather than the generic meter-only reactivation route."
                    },
                    "credit_plans": {
                      "type": "array",
                      "description": "Client-visible, non-secret subscription catalog. Returned even before a billing account exists; empty when prepaid mode is off. Stripe price ids are intentionally excluded.",
                      "items": {
                        "type": "object",
                        "required": [
                          "id",
                          "display_name",
                          "owner_type",
                          "monthly_price_usd",
                          "granted_allowance_usd",
                          "promotional_bonus_usd"
                        ],
                        "properties": {
                          "id": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 64,
                            "description": "Public SKU accepted by POST /auth/billing/credit/plan."
                          },
                          "display_name": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 80,
                            "description": "Human-readable plan label; the stable id remains the purchase key."
                          },
                          "owner_type": {
                            "type": "string",
                            "enum": [
                              "user",
                              "group"
                            ],
                            "description": "Server-enforced billing-account audience for this SKU."
                          },
                          "monthly_price_usd": {
                            "type": "number",
                            "minimum": 0.5,
                            "multipleOf": 0.01
                          },
                          "granted_allowance_usd": {
                            "type": "number",
                            "minimum": 0.01,
                            "multipleOf": 0.01
                          },
                          "promotional_bonus_usd": {
                            "type": "number",
                            "minimum": 0,
                            "multipleOf": 0.01
                          }
                        }
                      }
                    },
                    "credit_topups": {
                      "type": "array",
                      "description": "Client-visible, non-secret top-up catalog. Returned even before a billing account exists; empty when prepaid mode is off. Stripe price ids are intentionally excluded.",
                      "items": {
                        "type": "object",
                        "required": [
                          "id",
                          "price_usd",
                          "granted_allowance_usd"
                        ],
                        "properties": {
                          "id": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 64,
                            "description": "Public SKU accepted by POST /auth/billing/credit/topup."
                          },
                          "price_usd": {
                            "type": "number",
                            "minimum": 0.5,
                            "multipleOf": 0.01
                          },
                          "granted_allowance_usd": {
                            "type": "number",
                            "minimum": 0.01,
                            "multipleOf": 0.01
                          }
                        }
                      }
                    },
                    "billing_enabled": {
                      "type": "boolean"
                    },
                    "period_start": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "period_totals": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "meter": {
                            "type": "string"
                          },
                          "amount_usd_micros": {
                            "type": "integer",
                            "format": "int64"
                          },
                          "line_count": {
                            "type": "integer",
                            "format": "int64"
                          }
                        }
                      }
                    },
                    "daily": {
                      "type": "array",
                      "description": "Per-day, per-meter sums for the period (drives the daily chart).",
                      "items": {
                        "type": "object",
                        "properties": {
                          "day": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "meter": {
                            "type": "string"
                          },
                          "amount_usd_micros": {
                            "type": "integer",
                            "format": "int64"
                          },
                          "line_count": {
                            "type": "integer",
                            "format": "int64"
                          }
                        }
                      }
                    },
                    "breakdown": {
                      "type": "array",
                      "description": "Per-category aggregates — LLM rows keyed by metering feature, gas rows by on-chain message type. Ordered by spend, capped at 24.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "meter": {
                            "type": "string"
                          },
                          "key": {
                            "type": "string"
                          },
                          "amount_usd_micros": {
                            "type": "integer",
                            "format": "int64"
                          },
                          "line_count": {
                            "type": "integer",
                            "format": "int64"
                          }
                        }
                      }
                    },
                    "recent_line_items": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing/invalid bearer token."
          },
          "404": {
            "description": "Billing not configured on this deployment."
          }
        }
      }
    },
    "/auth/billing/me/line-items": {
      "get": {
        "operationId": "getMyBillingLineItems",
        "summary": "Keyset page of own line items (newest first)",
        "description": "\"Load more\" pagination behind the billing page's line-item table.\nStrict keyset over `(usage_at, id)` descending: pass the previous\npage's `next_cursor` fields to continue; omit both for page one.\n`next_cursor: null` means no further rows. Row shape matches\n`recent_line_items` on `/auth/billing/me`.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "before_usage_at",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "before_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page of line items.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "rows",
                    "next_cursor"
                  ],
                  "properties": {
                    "rows": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "next_cursor": {
                      "type": "object",
                      "nullable": true,
                      "properties": {
                        "usage_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing/invalid bearer token."
          },
          "404": {
            "description": "Billing not configured."
          }
        }
      }
    },
    "/auth/billing/ops": {
      "get": {
        "operationId": "getBillingOps",
        "summary": "Billing pipeline health snapshot (staff / service token)",
        "description": "The numbers an operator alerts on, shaped for curl + threshold:\nper-source ingest watermark age, unpushed meter-event backlog and\nits oldest age, poisoned line items (retry cap reached — manual\nredrive needed), unattributed platform-bucket usage over 24h,\n`pending_credit_reversals`, and separate sticky manual-review counts\nfor cash reversals, purchase operations, credit-note scope, and held\nrevenue shares, plus `billing_enabled` /\n`stripe_push_configured` / `price_version`.\nSuggested alerts: watermark `age_secs` > 900; `poisoned_line_items`\n> 0; `oldest_unpushed_at` older than 1h once Stripe is wired; any\nmanual-review count > 0; or a pending reversal that does not clear\nunder normal Stripe webhook redelivery.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Health snapshot."
          },
          "401": {
            "description": "Missing/invalid bearer token."
          },
          "403": {
            "description": "Caller is neither staff nor a service token."
          },
          "404": {
            "description": "Billing not configured."
          }
        }
      }
    },
    "/auth/billing/portal-session": {
      "post": {
        "operationId": "createBillingPortalSession",
        "summary": "Mint a Stripe Customer Portal session (payment methods, invoices)",
        "description": "Creates the Stripe customer lazily on first use. The returned URL is a short-lived hosted-portal session.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "return_url": {
                    "type": "string",
                    "description": "Where Stripe sends the user back after the portal."
                  },
                  "group_id": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true,
                    "description": "Manage a GROUP's payment profile instead of the personal one (group owner/admin only) — how an admin_pays room's admin adds the card that un-gates the room under require_card_before_use."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Portal session created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "url"
                  ],
                  "properties": {
                    "url": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing/invalid bearer token."
          },
          "404": {
            "description": "Billing not configured."
          },
          "503": {
            "description": "Stripe secret key not configured on this deployment."
          }
        }
      }
    },
    "/auth/billing/setup-intent": {
      "post": {
        "operationId": "createBillingSetupIntent",
        "summary": "Mint a Stripe SetupIntent for inline card capture (Stripe Elements)",
        "description": "Card-required gate support. Creates the Stripe customer lazily (same idempotent path as the portal), then mints a card-only, off-session SetupIntent. The frontend mounts Stripe Elements with `client_secret` + `publishable_key`, confirms, then calls `/auth/billing/payment-method/sync` so `has_payment_method` flips without waiting on webhook delivery. `publishable_key` is null when the deployment hasn't set the env named by `auth.billing.stripe.publishable_key_env` — frontends then fall back to the hosted Customer Portal.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "group_id": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true,
                    "description": "Add the card to a GROUP's payment profile (group owner/admin only)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "SetupIntent minted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "client_secret"
                  ],
                  "properties": {
                    "client_secret": {
                      "type": "string"
                    },
                    "publishable_key": {
                      "type": "string",
                      "nullable": true
                    },
                    "has_payment_method": {
                      "type": "boolean",
                      "description": "Current card-on-file state before this capture."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing/invalid bearer token."
          },
          "403": {
            "description": "group_id given but caller is not an owner/admin."
          },
          "404": {
            "description": "Billing not configured."
          },
          "409": {
            "description": "No billing email on the account (group without admin)."
          },
          "502": {
            "description": "Stripe call failed."
          },
          "503": {
            "description": "Stripe secret key not configured on this deployment."
          }
        }
      }
    },
    "/auth/billing/payment-method/sync": {
      "post": {
        "operationId": "syncBillingPaymentMethod",
        "summary": "Re-derive the card-on-file flag from Stripe now (authoritative)",
        "description": "Re-queries Stripe for the customer's invoice-default payment method plus attached cards, repairs a missing invoice default (promotes the newest card so metered invoices auto-charge), and persists `has_payment_method`. Called by the frontend right after Elements confirms a SetupIntent (or after a portal round-trip) so the UI and the next JWT mint never race webhook delivery. The webhook arms (`payment_method.attached`/`.detached`, `setup_intent.succeeded`, `customer.updated`) run the same sync as the durable backstop.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "group_id": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true,
                    "description": "Sync a GROUP's flag (group owner/admin only)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Fresh card-on-file state.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "has_payment_method"
                  ],
                  "properties": {
                    "has_payment_method": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing/invalid bearer token."
          },
          "403": {
            "description": "group_id given but caller is not an owner/admin."
          },
          "404": {
            "description": "Billing not configured."
          },
          "502": {
            "description": "Stripe call failed."
          },
          "503": {
            "description": "Stripe secret key not configured (only reachable when a customer exists)."
          }
        }
      }
    },
    "/auth/billing/credit/plan": {
      "post": {
        "operationId": "createBillingCreditPlanCheckout",
        "summary": "Open Stripe Checkout for a prepaid credit plan",
        "description": "Creates a subscription-mode Checkout for one public SKU from\n`credit_plans` on `GET /auth/billing/me`. The returned URL is the\nonly client redirect target. Plan binding is reconciled server-side\nfrom Stripe only by the verified paid webhook path; recurring\ngrants are created only by the paid-invoice webhook. A positive paid\nplan invoice, not Checkout completion, owns the allowance and\nauthoritative cycle end.\n\nBoth return URLs are required, caller-supplied absolute `http` or\n`https` URLs whose parsed origins exactly match\n`auth.billing.credit_checkout_return_origins`. Configured entries are\nexact origins; non-loopback entries must use HTTPS and plain HTTP is\naccepted only for localhost/loopback development. The server has no\nimplicit localhost fallback.\nAuth appends the reserved\n`yf_credit_checkout_session={CHECKOUT_SESSION_ID}` query parameter to\n`success_url`. Stripe substitutes the exact Session id; clients pass it\nto `/auth/billing/credit/checkout-status` instead of inferring purchase\nsuccess from a balance delta.\n\nThe account-wide Checkout idempotency key is deliberately independent\nof `plan_id`, so concurrent requests selecting different plans\nconverge instead of creating multiple subscriptions. An existing\ndurable plan or live Stripe plan returns 409. The steady state is one\ncanonical subscription carrying the plan and all metered prices. The\nreservation persists the exact return URLs, a full variable-parameter\nfingerprint, and the resulting hosted URL. Exact retries return the\nstored URL; route or catalog drift returns 409 without sending changed\nparameters under the same Stripe idempotency key.\n\nOnboarding callers send the optional `expected_catalog_version` shown on\nthe final confirmation screen. It must be exactly\n`sha256:<64 lowercase hex>`. A stale value returns 409 before billing\naccount resolution or any Stripe/customer/session interaction; malformed\nvalues return 400. Omission remains supported for legacy BillingCenter\ncallers.\n\nCard capture is also fail-closed behind required identity verification.\nWhen the effective enabled primary KYC provider advertises\n`required_for_completion: true` through `/auth/providers/config`, every\nnon-staff caller must already resolve to the canonical `KycVerified`\ncapability tier. This applies to both personal and group-plan Checkout\nand returns 403 before billing-account or Stripe side effects. When the\nprimary flow is optional/not configured, existing behavior is preserved;\nSuperAdmin, Admin, and Manager use the capability gate's existing\nclosed-plane exemption.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "plan_id",
                  "success_url",
                  "cancel_url"
                ],
                "properties": {
                  "plan_id": {
                    "type": "string",
                    "description": "Public SKU from `GET /auth/billing/catalog.credit_plans[].id` or the authenticated account catalog."
                  },
                  "expected_catalog_version": {
                    "type": "string",
                    "minLength": 71,
                    "maxLength": 71,
                    "pattern": "^sha256:[0-9a-f]{64}$",
                    "description": "Optional optimistic precondition copied from the final-confirmation catalog response. Omit only for legacy account-management flows."
                  },
                  "group_id": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true,
                    "description": "Subscribe a group account instead of the caller's personal account (owner/admin only)."
                  },
                  "success_url": {
                    "type": "string",
                    "format": "uri",
                    "pattern": "^https?://"
                  },
                  "cancel_url": {
                    "type": "string",
                    "format": "uri",
                    "pattern": "^https?://"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Hosted Checkout session created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "url"
                  ],
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unknown plan SKU",
            "malformed expected catalog version": null,
            "missing/invalid return URL": null,
            "or return origin not approved by the deployment.": null
          },
          "401": {
            "description": "Missing/invalid bearer token."
          },
          "403": {
            "description": "Required identity verification is incomplete",
            "or group_id was given but the caller is not a group owner/admin.": null
          },
          "404": {
            "description": "Billing or prepaid credit plans are not enabled."
          },
          "409": {
            "description": "Another plan Checkout or recovery is active (including a retry with different return URLs or immutable terms), the account has no billing email, a plan is already bound, or Stripe already has a live plan awaiting synchronization. Also returned when `expected_catalog_version` is valid but no longer current; no billing account or Stripe side effect occurs in that case."
          },
          "502": {
            "description": "Stripe call failed."
          },
          "503": {
            "description": "Stripe secret key or the selected plan's Stripe price is not configured."
          }
        }
      }
    },
    "/auth/billing/credit/topup": {
      "post": {
        "operationId": "createBillingCreditTopupCheckout",
        "summary": "Open Stripe Checkout for a prepaid credit top-up",
        "description": "Creates a payment-mode Checkout for one public SKU from\n`credit_topups` on `GET /auth/billing/me`. The account must already\nhave a bound plan and synchronized renewal time. Each completed\nCheckout session is an intentional separate top-up.\n\nBoth return URLs follow the same required absolute-http(s),\ndeployment-origin-allow-list contract as plan Checkout. New Sessions\nare card-only. Admission pauses near renewal and bounds the hosted\nSession to the earlier of 23 hours from creation or one hour before the\ncurrent cycle ends. Historical delayed-method Sessions remain\nreconcilable through `checkout.session.async_payment_succeeded`.\n\nThe purchase snapshots its exact plan, subscription, and cycle. If the\npayment later belongs to a canceled, replaced, or otherwise ineligible\nplan, Auth suppresses/removes the entitlement, expires any recovered\nStripe grant, and fully refunds the PaymentIntent. A same-plan renewal\nrace can join that plan's newly paid current cycle; it can never move to\nanother plan or subscription.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "topup_id",
                  "success_url",
                  "cancel_url"
                ],
                "properties": {
                  "topup_id": {
                    "type": "string",
                    "description": "Public SKU from `GET /auth/billing/me.credit_topups[].id`."
                  },
                  "group_id": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true,
                    "description": "Top up a group account instead of the caller's personal account (owner/admin only)."
                  },
                  "success_url": {
                    "type": "string",
                    "format": "uri",
                    "pattern": "^https?://"
                  },
                  "cancel_url": {
                    "type": "string",
                    "format": "uri",
                    "pattern": "^https?://"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Hosted Checkout session created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "url"
                  ],
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unknown top-up SKU",
            "missing/invalid return URL": null,
            "or return origin not approved by the deployment.": null
          },
          "401": {
            "description": "Missing/invalid bearer token."
          },
          "403": {
            "description": "group_id given but caller is not a group owner/admin."
          },
          "404": {
            "description": "Billing or prepaid credit plans are not enabled."
          },
          "409": {
            "description": "Account has no billing email or no active",
            "synchronized prepaid plan": null,
            "or the current cycle is too close to renewal for a safely bounded Checkout Session.": null
          },
          "502": {
            "description": "Stripe call failed."
          },
          "503": {
            "description": "Stripe secret key or the selected top-up's Stripe price is not configured."
          }
        }
      }
    },
    "/auth/billing/credit/checkout-status": {
      "post": {
        "operationId": "getBillingCreditCheckoutStatus",
        "summary": "Verify durable application of an exact prepaid Checkout purchase",
        "description": "Read-only authenticated return check. Auth live-retrieves the exact\nStripe Checkout Session, verifies its customer and immutable billing\naccount metadata against the caller-authorized personal/group target,\nthen joins it to the durable logical purchase root in\n`billing_credit_operations`. It never creates a grant, binds a plan, or\nchanges authorization.\n\n`applied: true` means the payment is live-paid, the correct logical\noperation completed without a reversal, and (for a plan) the exact\nCheckout subscription is the account's canonical bound plan. Clients\nmay poll the transient `processing` / `payment_pending` states with a\nbounded retry policy. `superseded` is terminal for an exact historical\nplan Session whose entitlement was later canceled or replaced;\n`retired` is terminal for an applied top-up whose cycle entitlement has\nsince expired. `failed` is terminal after Stripe's signed\n`checkout.session.async_payment_failed` notification, after a paid\ninitial purchase is proven unfulfillable and durably compensated, or\nwhen that compensation is quarantined for operator review. `expired` is\nterminal after either the live Session or its signed expiry notification\nreaches that state. A balance increase is deliberately not used as proof\nbecause concurrent usage can consume valid credit before return.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "session_id"
                ],
                "properties": {
                  "session_id": {
                    "type": "string",
                    "pattern": "^cs_[A-Za-z0-9_]+$",
                    "maxLength": 255
                  },
                  "group_id": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true,
                    "description": "Verify a group purchase instead of the caller's personal purchase (owner/admin only)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Exact Checkout and durable application state.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "session_id",
                    "kind",
                    "checkout_status",
                    "payment_status",
                    "state",
                    "applied",
                    "owner_type",
                    "owner_id",
                    "credit_balance_micros",
                    "stripe_credit_balance_micros"
                  ],
                  "properties": {
                    "session_id": {
                      "type": "string"
                    },
                    "kind": {
                      "type": "string",
                      "enum": [
                        "plan",
                        "topup"
                      ]
                    },
                    "checkout_status": {
                      "type": "string",
                      "enum": [
                        "open",
                        "complete",
                        "expired"
                      ]
                    },
                    "payment_status": {
                      "type": "string",
                      "enum": [
                        "paid",
                        "unpaid",
                        "no_payment_required"
                      ]
                    },
                    "state": {
                      "type": "string",
                      "enum": [
                        "processing",
                        "payment_pending",
                        "applied",
                        "reversing",
                        "reversed",
                        "retired",
                        "superseded",
                        "failed",
                        "expired"
                      ]
                    },
                    "applied": {
                      "type": "boolean"
                    },
                    "owner_type": {
                      "type": "string",
                      "enum": [
                        "user",
                        "group"
                      ]
                    },
                    "owner_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "credit_balance_micros": {
                      "type": "integer",
                      "format": "int64"
                    },
                    "stripe_credit_balance_micros": {
                      "type": "integer",
                      "format": "int64"
                    },
                    "credit_plan_id": {
                      "type": "string",
                      "nullable": true
                    },
                    "credit_plan_renews_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Malformed",
            "missing": null,
            "foreign": null,
            "or non-prepaid Checkout Session. This is terminal client input.": null
          },
          "401": {
            "description": "Missing/invalid bearer token."
          },
          "403": {
            "description": "group_id given but caller is not a group owner/admin."
          },
          "404": {
            "description": "Billing or prepaid credit plans are not enabled."
          },
          "409": {
            "description": "Billing payment-profile binding changed or does not exist."
          },
          "429": {
            "description": "Per-user live Stripe status-read budget exhausted; retry later."
          },
          "502": {
            "description": "Live Stripe Checkout retrieval failed transiently."
          },
          "503": {
            "description": "Stripe secret key is not configured."
          }
        }
      }
    },
    "/auth/billing/credit/sync": {
      "post": {
        "operationId": "syncBillingCreditBalance",
        "summary": "Refresh the prepaid Stripe reconciliation mirror",
        "description": "Refreshes `stripe_credit_balance_micros` from Stripe's customer\ncredit-balance summary and returns the auth-owned transactional\nauthorization ledger as `credit_balance_micros`. It never discovers,\nadopts, or binds a plan; only the paid token-bound Checkout/invoice\nwebhook path may do that.\n\nThis endpoint is optional display/reconciliation after an exact\n`/credit/checkout-status` response reports `applied: true`; it is not\npurchase proof. Then refresh the YieldFabric JWT so\n`credit_plan_required` or `credit_exhausted` is re-evaluated.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "group_id": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true,
                    "description": "Reconcile a group account instead of the caller's personal account (owner/admin only)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Local authorization plus the refreshed Stripe reconciliation mirror.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "credit_balance_micros",
                    "stripe_credit_balance_micros"
                  ],
                  "properties": {
                    "credit_balance_micros": {
                      "type": "integer",
                      "format": "int64"
                    },
                    "stripe_credit_balance_micros": {
                      "type": "integer",
                      "format": "int64"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing/invalid bearer token."
          },
          "403": {
            "description": "group_id given but caller is not a group owner/admin."
          },
          "404": {
            "description": "Billing or prepaid credit plans are not enabled."
          },
          "502": {
            "description": "Stripe balance reconciliation failed."
          },
          "503": {
            "description": "Stripe secret key is not configured (only reachable when a customer exists)."
          }
        }
      }
    },
    "/auth/billing/reactivate": {
      "post": {
        "operationId": "reactivateBilling",
        "summary": "Recover a canceled billing account",
        "description": "Creates a fresh metered subscription on the account's existing\nStripe customer and restores `active` status. The counterpart of\nthe canceled-status gate (canceled accounts are stripped of\nbillable capabilities like delinquent ones) — canceling must\nnever be a permanent lock-out. Personal account by default; pass\n`group_id` to reactivate a group account (billing-manager gated).\n\nA first call moves `canceled` to the internal `reactivating`\nstate; retries resume that same operation. The state remains\nbilling-gated until Stripe confirms a live subscription containing\nevery configured meter price and the final DB transition commits.\nBefore creating, auth discovers any already-live customer\nsubscription (crash recovery), and creation uses a stable Stripe\nIdempotency-Key, so concurrent/retried calls converge on one live\nsubscription rather than double-billing customer-scoped events.\n\n409 when the account is neither canceled nor reactivating (a delinquent account\nrecovers by paying its open invoice from the portal — this is\nnot a dunning bypass), when a concurrent reactivation is in\nflight, or when the account has no payment profile yet (use the\nportal \"Add a payment method\" flow first); 503 when Stripe or\nthe metered prices aren't configured on this deployment.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "group_id": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Reactivated: `{status: active, stripe_subscription_id}`."
          },
          "404": {
            "description": "No billing account."
          },
          "409": {
            "description": "Not canceled/reactivating",
            "or no payment profile yet.": null
          },
          "502": {
            "description": "Stripe subscription creation or reconciliation failed; the account remains gated in reactivating and the same request is retry-safe."
          },
          "503": {
            "description": "Stripe / prices not configured."
          }
        }
      }
    },
    "/auth/billing/earnings": {
      "get": {
        "operationId": "getMyBillingEarnings",
        "summary": "Revenue-share earnings dashboard (owner side of billing)",
        "description": "Ledger-first owner earnings: rooms whose managers price an AI rate above the platform base, and rooms whose knowledge graphs carry page fees, earn the margin minus the platform take (`revenue_share.platform_fee_pct`). Returns own-beneficiary totals (accrued = earned but consumer's invoice not yet collected; payable = collected and owed, includes in-flight payouts; settled = lifetime paid out), the recent ledger, per-managed-group totals, Stripe Connect cash-out state, and the deployment's terms. Earnings accrue with or without Connect — onboarding only unlocks the payout sweep.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "`{own, own_recent_shares, groups, connect: {onboarded, payouts_enabled}, terms: {platform_fee_pct, payout_threshold_usd, hold_days}}` — amounts integer micro-USD."
          },
          "401": {
            "description": "Missing/invalid bearer token."
          },
          "404": {
            "description": "Billing or revenue share not enabled."
          }
        }
      }
    },
    "/auth/billing/connect/onboard": {
      "post": {
        "operationId": "createConnectOnboarding",
        "summary": "Mint the caller's Stripe Connect Express account + hosted onboarding link",
        "description": "Cash-out rail for revenue-share earnings. Creates the Express account once (transfers capability, idempotent per user) and returns a fresh short-lived onboarding link. Group earnings settle to the group's billing admin's account.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "return_url": {
                    "type": "string",
                    "description": "Where Stripe sends the owner back."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Onboarding link minted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "url"
                  ],
                  "properties": {
                    "url": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing/invalid bearer token."
          },
          "404": {
            "description": "Billing or revenue share not enabled."
          },
          "409": {
            "description": "No billing email on the account."
          },
          "502": {
            "description": "Stripe call failed."
          },
          "503": {
            "description": "Stripe secret key not configured."
          }
        }
      }
    },
    "/auth/billing/connect/status": {
      "get": {
        "operationId": "getConnectStatus",
        "summary": "The caller's revenue-share cash-out state",
        "description": "`{onboarded, payouts_enabled}`. When an account exists, reads Stripe live and persists the capability (return-leg read-back; the `account.updated` webhook is the durable sync).",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "`{onboarded: bool, payouts_enabled: bool}`."
          },
          "401": {
            "description": "Missing/invalid bearer token."
          },
          "404": {
            "description": "Billing not configured."
          }
        }
      }
    },
    "/auth/billing/groups/{group_id}/earnings": {
      "get": {
        "operationId": "getGroupBillingEarnings",
        "summary": "One group's revenue-share earnings ledger (billing managers only)",
        "description": "Requires an active personal JWT and live direct Owner/Admin membership; delegation JWTs are rejected.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "group_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`{group_id, totals, recent_shares, terms}`."
          },
          "403": {
            "description": "Bearer is delegated",
            "or the personal caller is not a live direct Owner/Admin.": null
          },
          "404": {
            "description": "Billing or revenue share not enabled."
          }
        }
      }
    },
    "/auth/billing/groups/{group_id}": {
      "get": {
        "operationId": "getGroupBilling",
        "summary": "Group billing account summary (billing managers only)",
        "description": "Requires an active personal user JWT and live direct group\nOwner/Admin membership. `billing_admin_user_id` is payer-routing\nand contact metadata only; it never grants access. An\nownership-child JWT is rejected.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "group_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Same summary shape as /auth/billing/me plus `group_billing_policy` and `billing_admin_user_id` (payer/contact routing metadata, never authorization). `card_required` here gates purely on the GROUP account's card (no role exemption) — under admin_pays it blocks every member's billable actions until a manager adds a card. Also carries the group's billing-sponsorship settings as `sponsorship {mode, tnc_version, member_cap_usd, group_spend_cap_usd}` — ALWAYS present on this manager-gated summary (a group with no billing-account row gets the defaults `mode \"off\"`, `tnc_version 1`, null caps), so the block's presence feature-detects the DEPLOYMENT, not the group; only a settings read error omits it."
          },
          "403": {
            "description": "Bearer is delegated",
            "or the personal caller is not a live direct Owner/Admin.": null
          },
          "404": {
            "description": "Billing not configured."
          }
        }
      }
    },
    "/auth/billing/groups/{group_id}/line-items": {
      "get": {
        "operationId": "getGroupBillingLineItems",
        "summary": "Keyset page of a group's line items (billing managers only)",
        "description": "Same cursor contract as `/auth/billing/me/line-items`, scoped to\nthe group's billing account. Backs the group detail page's\nbilling section — where \"Groups you pay for\" on the personal\npage links to. Requires an active personal user JWT and live\ndirect Owner/Admin membership; delegated/ownership-child JWTs are\nrejected. A cached `billing_admin_user_id` is not authorization.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "group_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "before_usage_at",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "before_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page of the group's line items (rows + next_cursor)."
          },
          "403": {
            "description": "Bearer is delegated",
            "or the personal caller is not a live direct Owner/Admin.": null
          },
          "404": {
            "description": "Billing not configured."
          }
        }
      }
    },
    "/auth/billing/groups/{group_id}/policy": {
      "get": {
        "operationId": "getGroupBillingPolicy",
        "summary": "Read the group's billing policy",
        "description": "Requires an active personal JWT and live direct Owner/Admin membership; delegation JWTs are rejected.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "group_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current policy (defaults to admin_pays before any explicit set).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "group_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "policy": {
                      "type": "string",
                      "enum": [
                        "admin_pays",
                        "member_pays"
                      ]
                    },
                    "billing_admin_user_id": {
                      "type": "string",
                      "format": "uuid",
                      "nullable": true,
                      "description": "Cached payer/contact route; it does not grant access and is cleared when no live Owner/Admin remains."
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Bearer is delegated",
            "or the personal caller is not a live direct Owner/Admin.": null
          },
          "404": {
            "description": "Billing not configured."
          }
        }
      },
      "put": {
        "operationId": "setGroupBillingPolicy",
        "summary": "Set admin_pays | member_pays for the group",
        "description": "`admin_pays` (default): every group-attributed action lands on the\ngroup's bill, paid by the resolved billing admin. `member_pays`:\neach acting member's own billing account is charged; actions whose\nactor can't be identified fall back to the group with an\n`actor_fallback` flag on the line item.\n\nRequires an active personal JWT and live direct Owner/Admin\nmembership; delegation JWTs are rejected.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "group_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "policy"
                ],
                "properties": {
                  "policy": {
                    "type": "string",
                    "enum": [
                      "admin_pays",
                      "member_pays"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Policy updated."
          },
          "400": {
            "description": "Unknown policy value."
          },
          "403": {
            "description": "Bearer is delegated",
            "or the personal caller is not a live direct Owner/Admin.": null
          },
          "404": {
            "description": "Billing not configured."
          }
        }
      }
    },
    "/auth/groups/{group_id}/sponsorship": {
      "get": {
        "operationId": "getGroupSponsorship",
        "summary": "Inspect a platform group's sponsorship offer and caller consent",
        "description": "Authenticated probe used before platform-sponsored login. Returns\n`mode: off` when the group has no billing settings. Otherwise it\nalso reports the current T&C version and whether the caller is a\ngroup member with a matching acceptance. This read does not grant\nmembership or sponsorship.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "group_id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current sponsorship offer and caller state.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "group_id",
                    "mode"
                  ],
                  "properties": {
                    "group_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "mode": {
                      "type": "string",
                      "enum": [
                        "off",
                        "invite_only",
                        "open"
                      ]
                    },
                    "tnc_version": {
                      "type": "integer",
                      "nullable": true
                    },
                    "member": {
                      "type": "boolean",
                      "nullable": true
                    },
                    "tnc_accepted": {
                      "type": "boolean",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Billing is not configured on this deployment."
          }
        }
      }
    },
    "/auth/groups/{group_id}/sponsorship/accept-tnc": {
      "post": {
        "operationId": "acceptGroupSponsorshipTnc",
        "summary": "Accept the current sponsorship terms",
        "description": "Records explicit consent for the current T&C version. In `open`\nmode this also creates minimal group membership when needed;\n`invite_only` still requires existing membership. The submitted\nversion must equal the group's current version.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "group_id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "tnc_version"
                ],
                "properties": {
                  "tnc_version": {
                    "type": "integer",
                    "minimum": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Consent recorded: `{accepted, tnc_version, membership_created}`."
          },
          "403": {
            "description": "Sponsorship is off or invite-only membership is missing."
          },
          "404": {
            "description": "Billing is not configured on this deployment."
          },
          "409": {
            "description": "T&C version changed; reload and accept the current version."
          }
        }
      }
    },
    "/auth/groups/{group_id}/sponsorship/session": {
      "post": {
        "operationId": "bindGroupSponsorshipSession",
        "summary": "Rotate the current personal session to an exact group payer",
        "description": "Performs an explicit, authenticated self-pay to sponsored-session\ntransition after the caller has accepted the platform's current terms.\nThe bearer access JWT and refresh token must belong to the same user and\nboth must describe a regular personal session: delegation, OAuth/MCP,\npassword-change-only, and already-sponsored contexts are refused. The\nsubmitted `chain_id` must equal the bearer JWT's current\n`default_chain_id`; switch chains later through ordinary `/auth/refresh`.\n\nAuth revalidates exact membership, sponsorship mode, current T&C\nacceptance, and (for a prepaid-cohort sponsor) an active group plan\ninside its paid cycle before atomically claiming the single-use refresh\ntoken. On\nsuccess the old refresh token is revoked and the returned access JWT has\nboth `billing_sponsor` and `billing_gate_payer_id` equal to the path\n`group_id`; the actor remains the user. This endpoint never creates a\ngrant and never clears the user's persisted personal prepaid cohort.\n\nA successful call is intentionally one-time, not replay-idempotent. If a\nclient installs the returned pair but later bootstrap work fails, it must\nrecognize the exact installed sponsor/payer claims and retry bootstrap,\nnot replay this transition.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "group_id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "refresh_token",
                  "chain_id"
                ],
                "properties": {
                  "refresh_token": {
                    "type": "string",
                    "minLength": 64,
                    "maxLength": 64,
                    "writeOnly": true
                  },
                  "chain_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A rotated access/refresh pair bound to the exact group payer.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "access_token",
                    "refresh_token",
                    "token_type",
                    "expires_in",
                    "billing_sponsor"
                  ],
                  "properties": {
                    "access_token": {
                      "type": "string",
                      "writeOnly": true
                    },
                    "refresh_token": {
                      "type": "string",
                      "writeOnly": true
                    },
                    "token_type": {
                      "type": "string",
                      "const": "Bearer"
                    },
                    "expires_in": {
                      "type": "integer",
                      "const": 900
                    },
                    "billing_sponsor": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or unsupported chain id."
          },
          "401": {
            "description": "Invalid bearer",
            "invalid refresh token": null,
            "or bearer/refresh subject mismatch.": null
          },
          "403": {
            "description": "`sponsorship_unavailable`: the selected group cannot sponsor this user. The envelope carries the canonical `sponsor_group_id`."
          },
          "404": {
            "description": "Billing is not configured on this deployment."
          },
          "409": {
            "description": "`sponsorship_tnc_required`, `sponsorship_transition_denied`, `sponsorship_transition_consumed`, or `CHAIN_UNAVAILABLE`. Sponsorship errors carry `sponsor_group_id` (and T&C errors carry `tnc_version`). T&C/unavailable/denied validation is pre-claim and leaves the self-pay refresh usable; `sponsorship_transition_consumed` is terminal and requires clearing the local pair plus normal re-authentication."
          },
          "429": {
            "description": "Too many sponsor-session transition attempts."
          },
          "500": {
            "description": "Internal post-claim invariant failure. When the refresh lineage was consumed, the envelope carries `code: sponsorship_transition_consumed`; clients must clear the pair and re-authenticate."
          }
        }
      }
    },
    "/auth/billing/groups/{group_id}/sponsorship": {
      "put": {
        "operationId": "setGroupSponsorship",
        "summary": "Configure platform-paid sessions for a group",
        "description": "Group-manager operation. Sets enrollment mode, the current T&C\nversion, and optional per-member and group spend caps. Updating the\nT&C version invalidates older acceptances at the next token mint.\nRequires an active personal JWT and live direct Owner/Admin\nmembership; delegation JWTs are rejected.\n",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "group_id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "mode",
                  "tnc_version"
                ],
                "properties": {
                  "mode": {
                    "type": "string",
                    "enum": [
                      "off",
                      "invite_only",
                      "open"
                    ]
                  },
                  "tnc_version": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "member_cap_usd": {
                    "type": "number",
                    "nullable": true
                  },
                  "group_spend_cap_usd": {
                    "type": "number",
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated settings: `{group_id, mode, tnc_version}`."
          },
          "400": {
            "description": "Invalid mode",
            "version": null,
            "or cap value.": null
          },
          "403": {
            "description": "Bearer is delegated",
            "or the personal caller is not a live direct Owner/Admin.": null
          },
          "404": {
            "description": "Billing is not configured on this deployment."
          }
        }
      }
    },
    "/webhooks/stripe-billing": {
      "post": {
        "operationId": "stripeBillingWebhook",
        "summary": "Stripe Billing webhook (usage-billing lifecycle)",
        "description": "Signature-verified (`stripe-signature`, HMAC over `{t}.{body}`,\n5-minute replay window) against the secret named by\n`auth.billing.stripe.webhook_secret_env` — a DIFFERENT endpoint\nsecret from the Stripe Identity KYC webhook. Event ids are\ndeduplicated in `billing_webhook_events`; status transitions are\nidempotent, so Stripe redeliveries are harmless.\n\ninvoice.payment_failed → past_due (grace clock starts);\ninvoice.paid → active; subscription updated/deleted and\ncustomer.deleted → synced. After `auth.billing.grace_days` in\npast_due, an hourly worker promotes the account to `delinquent`,\nwhich strips billable capabilities at the next JWT mint/refresh.\n\nPayment-method arms (card-required gate): setup_intent.succeeded,\npayment_method.attached, payment_method.detached, and\ncustomer.updated re-sync `billing_accounts.has_payment_method`\nfrom Stripe (re-query based, so \"detached but another card\nremains\" resolves correctly). Register these on the live endpoint\nalongside the lifecycle events.\n\nPrepaid-credit arms use a durable logical-operation journal in\naddition to event-id dedup: paid plan cycles key on Stripe invoice id,\nand top-ups key on Checkout session id. Money-valued failures are not\nacknowledged/recorded, so Stripe redelivery repairs them; recovery\nreconciles Stripe grant metadata before creating another grant. Plan,\nbonus, and top-up grants reset together at the funded cycle boundary\n(with a four-day finalization safety expiry), and plan adoption\nconverges the license plus metered prices onto one canonical\nsubscription before usage is pushed.\n\nRevenue-share arms: exact `invoice.paid` can additionally flip only\ncanonically subscribed, invoice-period usage pushed no later than the\nlive invoice finalization cutoff from accrued to payable. Refunds,\ndisputes, and credit notes serialize against that transition: verified\nfull-invoice loss voids/claws exposure, exact internal plan-license\ncompensation leaves metered-usage share intact, and ambiguous/partial\nevidence creates a payout-excluded manual-review hold. `account.updated`\n(a CONNECT event — register the\nendpoint with \"listen to events on connected accounts\") syncs\n`connect_payouts_enabled` for the payout sweep.\n",
        "responses": {
          "200": {
            "description": "Event acknowledged (processed or deduplicated)."
          },
          "401": {
            "description": "Signature verification failed."
          },
          "404": {
            "description": "Billing not configured."
          }
        }
      }
    },
    "/auth/connections/request": {
      "post": {
        "operationId": "createConnectionRequest",
        "summary": "Send a connection request to another entity",
        "tags": [
          "Connections"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "target_entity_type",
                  "target_entity_id"
                ],
                "properties": {
                  "target_entity_type": {
                    "type": "string",
                    "enum": [
                      "user",
                      "group"
                    ]
                  },
                  "target_entity_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "message": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Optional note to the recipient."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Request created (status `pending`)"
          },
          "409": {
            "description": "Request would violate a uniqueness or state invariant. Examples:\ncreating a user with an email that already exists; submitting a\nlockdown request while one is already pending; cancelling a\nlockdown that has already been cancelled.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "lockdown-already-cancelled": {
                    "value": {
                      "error": "lockdown_already_cancelled"
                    }
                  },
                  "cooldown-not-elapsed": {
                    "value": {
                      "error": "cooldown_not_elapsed"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/connections/request \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"target_entity_type\": \"user\",\n  \"target_entity_id\": \"00000000-0000-0000-0000-000000000000\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/connections/request', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"target_entity_type\": \"user\",\n    \"target_entity_id\": \"00000000-0000-0000-0000-000000000000\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/connections/request'\nresp = httpx.post(url, headers=headers, json={\"target_entity_type\": \"user\", \"target_entity_id\": \"00000000-0000-0000-0000-000000000000\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/connections/requests": {
      "get": {
        "operationId": "getConnectionRequests",
        "summary": "List the current user's connection requests (sent + received)",
        "tags": [
          "Connections"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "sent",
                "received",
                "all"
              ],
              "default": "all"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "accepted",
                "rejected",
                "blocked"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Connection requests",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "requester_entity_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "target_entity_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "status": {
                        "type": "string",
                        "enum": [
                          "pending",
                          "accepted",
                          "rejected",
                          "blocked"
                        ]
                      },
                      "created_at": {
                        "type": "string",
                        "format": "date-time"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/connections/requests \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/connections/requests', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/connections/requests'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/connections/requests/{id}": {
      "get": {
        "operationId": "getConnectionRequest",
        "summary": "Get a specific connection request",
        "tags": [
          "Connections"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Connection request detail"
          },
          "404": {
            "description": "Resource not found (user, group, key, delegation token, wallet, provider).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "keypair": {
                    "summary": "Keypair UUID not found",
                    "value": {
                      "error": "keypair not found"
                    }
                  },
                  "kyc-provider": {
                    "summary": "Requested KYC provider not enabled in yaml",
                    "value": {
                      "error": "kyc provider not enabled"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/connections/requests/{id} \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/connections/requests/{id}', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/connections/requests/{id}'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/connections/requests/{id}/accept": {
      "post": {
        "operationId": "acceptConnectionRequest",
        "summary": "Accept a connection request",
        "tags": [
          "Connections"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Request accepted"
          },
          "403": {
            "description": "JWT valid (signature + audience accepted) but the caller is not\nauthorised for this operation. Examples: `role` lacks the required\npermission; trying to act on another user's resource without\nSuperAdmin; trying to use a delegation token outside its\n`delegation_scope`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "permission-required": {
                    "summary": "Caller lacks the permission the handler requires",
                    "value": {
                      "error": "permission 'ManageGroupMembers' required"
                    }
                  },
                  "not-account-member": {
                    "summary": "Caller is not a member or NFT-owner of the group",
                    "value": {
                      "error": "not a member of group"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/connections/requests/{id}/accept \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/connections/requests/{id}/accept', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/connections/requests/{id}/accept'\nresp = httpx.post(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/connections/requests/{id}/reject": {
      "post": {
        "operationId": "rejectConnectionRequest",
        "summary": "Reject a connection request",
        "tags": [
          "Connections"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Request rejected"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/connections/requests/{id}/reject \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/connections/requests/{id}/reject', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/connections/requests/{id}/reject'\nresp = httpx.post(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/connections/requests/{id}/block": {
      "post": {
        "operationId": "blockConnectionRequest",
        "summary": "Block a connection request (rejects + prevents further requests from the sender)",
        "tags": [
          "Connections"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Sender blocked"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/connections/requests/{id}/block \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/connections/requests/{id}/block', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/connections/requests/{id}/block'\nresp = httpx.post(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/connections/requests/{id}/preferences": {
      "post": {
        "operationId": "createSharingPreferences",
        "summary": "Set sharing preferences for an accepted connection",
        "description": "Records what the calling entity is willing to share with the\nconnected entity (profile fields, account info, etc.).\n",
        "tags": [
          "Connections"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Sharing preference shape."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Preferences saved"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/connections/requests/{id}/preferences \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/connections/requests/{id}/preferences', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({}),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/connections/requests/{id}/preferences'\nresp = httpx.post(url, headers=headers, json={})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      },
      "get": {
        "operationId": "getSharingPreferences",
        "summary": "Get sharing preferences for a connection",
        "tags": [
          "Connections"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Preferences"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/connections/requests/{id}/preferences \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/connections/requests/{id}/preferences', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/connections/requests/{id}/preferences'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/connections/notifications": {
      "get": {
        "operationId": "getConnectionNotifications",
        "summary": "List connection-related notifications for the current user",
        "description": "Returns connection notifications — new requests, acceptances,\ninvitation outcomes, sharing-preference changes.\n",
        "tags": [
          "Connections"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "unread_only",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Notifications"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/connections/notifications \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/connections/notifications', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/connections/notifications'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/connections/notifications/{id}/read": {
      "post": {
        "operationId": "markNotificationRead",
        "summary": "Mark a notification as read",
        "tags": [
          "Connections"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Notification marked read"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/connections/notifications/{id}/read \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/connections/notifications/{id}/read', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/connections/notifications/{id}/read'\nresp = httpx.post(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/invitations": {
      "post": {
        "operationId": "createInvitation",
        "summary": "Create an invitation and send the magic-link email",
        "description": "Generates an opaque token, persists the invitation, and sends the\ninvitation email (best-effort — delivery status is tracked on the row,\na send failure does not fail the request). Returns the invitation and\nthe magic-link `invite_url` (`{app_url}/invite/{token}`).\n\n`target_kind` selects the action the invitee executes after onboarding:\n`none` (just join), `connection` (connect with the inviter — reuses the\nconnection machinery in-process), or `obligation` / `deal_flow` /\n`group_join` (the accept response returns a typed `next_action` the app\nexecutes via the GraphQL gateway). `target_payload` carries the\nkind-specific terms (e.g. obligation amount / denomination / expiry).\n",
        "tags": [
          "Invitations"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "invitee_email"
                ],
                "properties": {
                  "invitee_email": {
                    "type": "string",
                    "format": "email"
                  },
                  "invitee_name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "message": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Personal note shown on the landing page + email."
                  },
                  "inviter_display_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Denormalised so the unauthenticated landing page greets by name; falls back to the inviter's email local-part."
                  },
                  "target_kind": {
                    "type": "string",
                    "enum": [
                      "none",
                      "obligation",
                      "deal_flow",
                      "group_join",
                      "connection"
                    ],
                    "default": "none"
                  },
                  "target_payload": {
                    "type": "object",
                    "description": "Kind-specific terms (e.g. {amount, denomination, expiry, obligation_address} for obligation)."
                  },
                  "expires_in_hours": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "default": 168
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Invitation created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "invitation",
                    "invite_url"
                  ],
                  "properties": {
                    "invitation": {
                      "type": "object",
                      "description": "Full invitation row (inviter-facing).",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "token": {
                          "type": "string"
                        },
                        "inviter_entity_id": {
                          "type": "string"
                        },
                        "inviter_display_name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "invitee_email": {
                          "type": "string",
                          "format": "email"
                        },
                        "invitee_name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "message": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "target_kind": {
                          "type": "string",
                          "enum": [
                            "none",
                            "obligation",
                            "deal_flow",
                            "group_join",
                            "connection"
                          ]
                        },
                        "target_payload": {
                          "type": "object"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "pending",
                            "opened",
                            "accepted",
                            "revoked",
                            "expired"
                          ]
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "opened_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "accepted_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "revoked_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "accepted_by_entity_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "email_status": {
                          "type": "string",
                          "enum": [
                            "queued",
                            "sent",
                            "failed"
                          ]
                        },
                        "email_provider_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "email_error": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    },
                    "invite_url": {
                      "type": "string",
                      "description": "Magic link: {app_url}/invite/{token}"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid email or unknown target_kind (flat error body)"
          },
          "401": {
            "description": "Missing or invalid bearer token"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/invitations \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"invitee_email\": \"them@example.com\",\n  \"message\": \"Join me on YieldFabric\",\n  \"target_kind\": \"connection\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/invitations', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    invitee_email: 'them@example.com',\n    message: 'Join me on YieldFabric',\n    target_kind: 'connection',\n  }),\n});\nconst { invitation, invite_url } = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/auth/invitations'\nresp = httpx.post(url, headers=headers, json={'invitee_email': 'them@example.com', 'message': 'Join me on YieldFabric', 'target_kind': 'connection'})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      },
      "get": {
        "operationId": "listSentInvitations",
        "summary": "List invitations the current user has sent",
        "tags": [
          "Invitations"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Invitations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "invitations": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "description": "Full invitation row (inviter-facing).",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "token": {
                            "type": "string"
                          },
                          "inviter_entity_id": {
                            "type": "string"
                          },
                          "inviter_display_name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "invitee_email": {
                            "type": "string",
                            "format": "email"
                          },
                          "invitee_name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "message": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "target_kind": {
                            "type": "string",
                            "enum": [
                              "none",
                              "obligation",
                              "deal_flow",
                              "group_join",
                              "connection"
                            ]
                          },
                          "target_payload": {
                            "type": "object"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "pending",
                              "opened",
                              "accepted",
                              "revoked",
                              "expired"
                            ]
                          },
                          "expires_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "opened_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          },
                          "accepted_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          },
                          "revoked_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          },
                          "accepted_by_entity_id": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "email_status": {
                            "type": "string",
                            "enum": [
                              "queued",
                              "sent",
                              "failed"
                            ]
                          },
                          "email_provider_id": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "email_error": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updated_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/invitations \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/invitations', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst { invitations } = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nresp = httpx.get('https://auth.yieldfabric.com/auth/invitations', headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/invitations/received": {
      "get": {
        "operationId": "listReceivedInvitations",
        "summary": "List live invitations addressed to the current user's email",
        "description": "Returns pending/opened, non-expired invitations whose `invitee_email` matches the caller.",
        "tags": [
          "Invitations"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Received invitations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "invitations": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "description": "Full invitation row (inviter-facing).",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "token": {
                            "type": "string"
                          },
                          "inviter_entity_id": {
                            "type": "string"
                          },
                          "inviter_display_name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "invitee_email": {
                            "type": "string",
                            "format": "email"
                          },
                          "invitee_name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "message": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "target_kind": {
                            "type": "string",
                            "enum": [
                              "none",
                              "obligation",
                              "deal_flow",
                              "group_join",
                              "connection"
                            ]
                          },
                          "target_payload": {
                            "type": "object"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "pending",
                              "opened",
                              "accepted",
                              "revoked",
                              "expired"
                            ]
                          },
                          "expires_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "opened_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          },
                          "accepted_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          },
                          "revoked_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          },
                          "accepted_by_entity_id": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "email_status": {
                            "type": "string",
                            "enum": [
                              "queued",
                              "sent",
                              "failed"
                            ]
                          },
                          "email_provider_id": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "email_error": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updated_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/invitations/received \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/invitations/received', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst { invitations } = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nresp = httpx.get('https://auth.yieldfabric.com/auth/invitations/received', headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/invitations/{token}": {
      "get": {
        "operationId": "getInvitationPublic",
        "summary": "Look up an invitation by token (public, redacted)",
        "description": "**Unauthenticated** — the opaque token is the only credential. Returns a\nredacted view (inviter display name, message, target kind + a curated\n`target_preview`/`target_summary`, never the raw payload) and marks the\ninvitation `opened` on first view. Powers the `/invite/:token` landing\npage before the recipient has an account.\n",
        "tags": [
          "Invitations"
        ],
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Redacted invitation view",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "invitation": {
                      "type": "object",
                      "description": "Redacted, unauthenticated-safe projection (no raw target_payload).",
                      "properties": {
                        "token": {
                          "type": "string"
                        },
                        "inviter_display_name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "invitee_email": {
                          "type": "string",
                          "format": "email"
                        },
                        "invitee_name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "message": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "target_kind": {
                          "type": "string",
                          "enum": [
                            "none",
                            "obligation",
                            "deal_flow",
                            "group_join",
                            "connection"
                          ]
                        },
                        "target_summary": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "One-line",
                          "invitee-safe description.": null
                        },
                        "target_preview": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "description": "Curated presentational fields (e.g. obligation amount/denomination/expiry)."
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "pending",
                            "opened",
                            "accepted",
                            "revoked",
                            "expired"
                          ]
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No invitation for that token"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/auth/invitations/{token}"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch(`https://auth.yieldfabric.com/auth/invitations/${token}`);\nconst { invitation } = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx\nresp = httpx.get(f'https://auth.yieldfabric.com/auth/invitations/{token}')\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      },
      "delete": {
        "operationId": "revokeInvitation",
        "summary": "Soft-revoke an invitation by token (inviter only)",
        "description": "Marks the invitation `revoked` so the link stops working. Keyed by token (not id) so it shares this path with the public GET.",
        "tags": [
          "Invitations"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Invitation revoked"
          },
          "403": {
            "description": "Not the inviter"
          },
          "404": {
            "description": "Not found or no longer revocable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X DELETE https://auth.yieldfabric.com/auth/invitations/{token} \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "await fetch(`https://auth.yieldfabric.com/auth/invitations/${token}`, {\n  method: 'DELETE',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nhttpx.delete(f'https://auth.yieldfabric.com/auth/invitations/{token}', headers=headers).raise_for_status()"
          }
        ]
      }
    },
    "/auth/invitations/{token}/accept": {
      "post": {
        "operationId": "acceptInvitation",
        "summary": "Accept an invitation and resolve the next action",
        "description": "Authenticated. The caller's email MUST match the invitation's\n`invitee_email` (the magic link is bound to that inbox) — else `403`.\nMarks the invitation `accepted`, dispatches the target (for `connection`,\nforms + auto-accepts the connection in-process), and returns a typed\n`next_action` the app routes into (`obligation` → accept surface,\n`deal_flow` → deal, `group_join` → group, `none`/`connection` → home /\ncontacts). Idempotent: a second accept re-resolves the next action\nwithout re-running side effects.\n",
        "tags": [
          "Invitations"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Accepted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "invitation",
                    "next_action"
                  ],
                  "properties": {
                    "invitation": {
                      "type": "object",
                      "description": "Full invitation row (inviter-facing).",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "token": {
                          "type": "string"
                        },
                        "inviter_entity_id": {
                          "type": "string"
                        },
                        "inviter_display_name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "invitee_email": {
                          "type": "string",
                          "format": "email"
                        },
                        "invitee_name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "message": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "target_kind": {
                          "type": "string",
                          "enum": [
                            "none",
                            "obligation",
                            "deal_flow",
                            "group_join",
                            "connection"
                          ]
                        },
                        "target_payload": {
                          "type": "object"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "pending",
                            "opened",
                            "accepted",
                            "revoked",
                            "expired"
                          ]
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "opened_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "accepted_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "revoked_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "accepted_by_entity_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "email_status": {
                          "type": "string",
                          "enum": [
                            "queued",
                            "sent",
                            "failed"
                          ]
                        },
                        "email_provider_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "email_error": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    },
                    "next_action": {
                      "type": "object",
                      "properties": {
                        "kind": {
                          "type": "string",
                          "enum": [
                            "none",
                            "obligation",
                            "deal_flow",
                            "group_join",
                            "connection"
                          ]
                        },
                        "payload": {
                          "type": "object"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Authenticated email does not match the invited address"
          },
          "410": {
            "description": "Invitation expired or revoked"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/invitations/{token}/accept \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch(`https://auth.yieldfabric.com/auth/invitations/${token}/accept`, {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst { invitation, next_action } = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nresp = httpx.post(f'https://auth.yieldfabric.com/auth/invitations/{token}/accept', headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/invitations/{token}/resend": {
      "post": {
        "operationId": "resendInvitation",
        "summary": "Resend the invitation email (inviter only, rate-limited)",
        "tags": [
          "Invitations"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "invitation": {
                      "type": "object",
                      "description": "Full invitation row (inviter-facing).",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "token": {
                          "type": "string"
                        },
                        "inviter_entity_id": {
                          "type": "string"
                        },
                        "inviter_display_name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "invitee_email": {
                          "type": "string",
                          "format": "email"
                        },
                        "invitee_name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "message": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "target_kind": {
                          "type": "string",
                          "enum": [
                            "none",
                            "obligation",
                            "deal_flow",
                            "group_join",
                            "connection"
                          ]
                        },
                        "target_payload": {
                          "type": "object"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "pending",
                            "opened",
                            "accepted",
                            "revoked",
                            "expired"
                          ]
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "opened_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "accepted_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "revoked_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "accepted_by_entity_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "email_status": {
                          "type": "string",
                          "enum": [
                            "queued",
                            "sent",
                            "failed"
                          ]
                        },
                        "email_provider_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "email_error": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "updated_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    },
                    "invite_url": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Already accepted or revoked"
          },
          "410": {
            "description": "Expired"
          },
          "429": {
            "description": "Resend cooldown not elapsed"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/auth/invitations/{token}/resend \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch(`https://auth.yieldfabric.com/auth/invitations/${token}/resend`, {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nresp = httpx.post(f'https://auth.yieldfabric.com/auth/invitations/{token}/resend', headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/keys/": {
      "post": {
        "operationId": "createKeyPair",
        "summary": "Create a new server-custodied keypair for the current user",
        "description": "Generates a fresh secp256k1 keypair, encrypts the private key\nunder the master key, and stores it. Returns the public key +\nmetadata; the plaintext private key never leaves the server. When\n`register_with_wallet=true`, the target account is preflighted before\nkey generation or persistence, so a 409 is safe to activate and retry.\n",
        "tags": [
          "Keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "user_id",
                  "key_name",
                  "key_type"
                ],
                "properties": {
                  "user_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "key_name": {
                    "type": "string"
                  },
                  "key_type": {
                    "type": "string",
                    "description": "Stored-keypair purpose. `Signing` = secp256k1 used for EIP-191\nsignatures and account ownership; `Encryption` = secp256k1 used as\nan ECIES recipient endpoint; `Hybrid` = doubles as both (legacy\nemail-user shape); `External` = wallet-led signup, no private key\nheld server-side.\n",
                    "enum": [
                      "Signing",
                      "Encryption",
                      "Hybrid",
                      "External"
                    ]
                  },
                  "provider_type": {
                    "type": "string"
                  },
                  "expires_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "register_with_wallet": {
                    "type": "boolean",
                    "default": false
                  },
                  "chain_id": {
                    "type": "string",
                    "description": "Required and equal to the JWT chain when register_with_wallet=true."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Keypair created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Public keypair metadata. `account_signer_custody` is Auth's\nserver-authoritative classification for the account contract's\n`account_ecdsa65_v1` rail; clients must not infer custody from\n`provider_type`, `key_type`, or metadata. `custodial` selects Automatic\nexecution, `external` selects Manual wallet signing, and `null` means the\nkey is not eligible for that account-signature profile.\n",
                  "required": [
                    "id",
                    "entity_type",
                    "entity_id",
                    "key_name",
                    "public_key",
                    "provider_type",
                    "key_type",
                    "is_active",
                    "created_at",
                    "expires_at",
                    "metadata",
                    "account_signer_custody"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "entity_type": {
                      "type": "string",
                      "enum": [
                        "user",
                        "group"
                      ]
                    },
                    "entity_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "key_name": {
                      "type": "string"
                    },
                    "public_key": {
                      "type": "string"
                    },
                    "provider_type": {
                      "type": "string",
                      "enum": [
                        "OpenSSL",
                        "HSM",
                        "Hybrid",
                        "External"
                      ]
                    },
                    "key_type": {
                      "type": "string",
                      "description": "Stored-keypair purpose. `Signing` = secp256k1 used for EIP-191\nsignatures and account ownership; `Encryption` = secp256k1 used as\nan ECIES recipient endpoint; `Hybrid` = doubles as both (legacy\nemail-user shape); `External` = wallet-led signup, no private key\nheld server-side.\n",
                      "enum": [
                        "Signing",
                        "Encryption",
                        "Hybrid",
                        "External"
                      ]
                    },
                    "is_active": {
                      "type": "boolean"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "expires_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "metadata": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "additionalProperties": true
                    },
                    "account_signer_custody": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "custodial",
                        "external",
                        null
                      ]
                    },
                    "owner_registration_status": {
                      "type": "string",
                      "enum": [
                        "confirmed",
                        "pending_signature",
                        "retry_required",
                        "failed"
                      ]
                    },
                    "owner_registration_message_id": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The exact user/group has no smart account on the JWT-selected chain.\nThe handler checked this before durable mutation or MQ handoff. Activate\nthe returned entity/chain, refresh the same actor session, and retry once.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "code",
                    "entity_kind",
                    "entity_id",
                    "chain_id"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "const": "CHAIN_ACCOUNT_REQUIRED"
                    },
                    "entity_kind": {
                      "type": "string",
                      "enum": [
                        "user",
                        "group"
                      ]
                    },
                    "entity_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "chain_id": {
                      "type": "string"
                    },
                    "activation_url": {
                      "type": "string",
                      "description": "Exact recovery resource when the handler can name it."
                    },
                    "operation": {
                      "type": "string",
                      "description": "Stable operation label for diagnostics; clients route on `code` and the entity/chain fields."
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/keys/ \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"key_type\": \"<see KeyType>\",\n  \"name\": \"<string>\",\n  \"metadata\": {}\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/keys/', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"key_type\": \"<see KeyType>\",\n    \"name\": \"<string>\",\n    \"metadata\": {}\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/keys/'\nresp = httpx.post(url, headers=headers, json={\"key_type\": \"<see KeyType>\", \"name\": \"<string>\", \"metadata\": {}})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/keys/external": {
      "post": {
        "operationId": "registerExternalKey",
        "summary": "Register an external (wallet-held) key for the current user",
        "description": "For wallet-led signups (MetaMask, hardware wallets). The server\nstores only the public key with `key_type: External`; no private\nkey is held server-side. Wallet-owner registration preflights the\nselected-chain account before key or provider-link persistence.\n",
        "tags": [
          "Keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "user_id",
                  "key_name",
                  "public_key"
                ],
                "properties": {
                  "user_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "key_name": {
                    "type": "string"
                  },
                  "public_key": {
                    "type": "string"
                  },
                  "expires_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "register_with_wallet": {
                    "type": "boolean",
                    "default": false
                  },
                  "chain_id": {
                    "type": "string",
                    "description": "Required and equal to the JWT chain when register_with_wallet=true."
                  },
                  "passkey": {
                    "type": "object",
                    "description": "Optional P-256 passkey public coordinates and credential id."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "External key registered",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Public keypair metadata. `account_signer_custody` is Auth's\nserver-authoritative classification for the account contract's\n`account_ecdsa65_v1` rail; clients must not infer custody from\n`provider_type`, `key_type`, or metadata. `custodial` selects Automatic\nexecution, `external` selects Manual wallet signing, and `null` means the\nkey is not eligible for that account-signature profile.\n",
                  "required": [
                    "id",
                    "entity_type",
                    "entity_id",
                    "key_name",
                    "public_key",
                    "provider_type",
                    "key_type",
                    "is_active",
                    "created_at",
                    "expires_at",
                    "metadata",
                    "account_signer_custody"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "entity_type": {
                      "type": "string",
                      "enum": [
                        "user",
                        "group"
                      ]
                    },
                    "entity_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "key_name": {
                      "type": "string"
                    },
                    "public_key": {
                      "type": "string"
                    },
                    "provider_type": {
                      "type": "string",
                      "enum": [
                        "OpenSSL",
                        "HSM",
                        "Hybrid",
                        "External"
                      ]
                    },
                    "key_type": {
                      "type": "string",
                      "description": "Stored-keypair purpose. `Signing` = secp256k1 used for EIP-191\nsignatures and account ownership; `Encryption` = secp256k1 used as\nan ECIES recipient endpoint; `Hybrid` = doubles as both (legacy\nemail-user shape); `External` = wallet-led signup, no private key\nheld server-side.\n",
                      "enum": [
                        "Signing",
                        "Encryption",
                        "Hybrid",
                        "External"
                      ]
                    },
                    "is_active": {
                      "type": "boolean"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "expires_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "metadata": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "additionalProperties": true
                    },
                    "account_signer_custody": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "custodial",
                        "external",
                        null
                      ]
                    },
                    "owner_registration_status": {
                      "type": "string",
                      "enum": [
                        "confirmed",
                        "pending_signature",
                        "retry_required",
                        "failed"
                      ]
                    },
                    "owner_registration_message_id": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The exact user/group has no smart account on the JWT-selected chain.\nThe handler checked this before durable mutation or MQ handoff. Activate\nthe returned entity/chain, refresh the same actor session, and retry once.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "code",
                    "entity_kind",
                    "entity_id",
                    "chain_id"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "const": "CHAIN_ACCOUNT_REQUIRED"
                    },
                    "entity_kind": {
                      "type": "string",
                      "enum": [
                        "user",
                        "group"
                      ]
                    },
                    "entity_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "chain_id": {
                      "type": "string"
                    },
                    "activation_url": {
                      "type": "string",
                      "description": "Exact recovery resource when the handler can name it."
                    },
                    "operation": {
                      "type": "string",
                      "description": "Stable operation label for diagnostics; clients route on `code` and the entity/chain fields."
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/keys/external \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"public_key\": \"<string>\",\n  \"name\": \"<string>\",\n  \"metadata\": {}\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/keys/external', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"public_key\": \"<string>\",\n    \"name\": \"<string>\",\n    \"metadata\": {}\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/keys/external'\nresp = httpx.post(url, headers=headers, json={\"public_key\": \"<string>\", \"name\": \"<string>\", \"metadata\": {}})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/keys/external/verify-ownership": {
      "post": {
        "operationId": "verifyExternalKeyOwnership",
        "summary": "Prove ownership of an external key via signature",
        "description": "Pair with `/keys/external` for two-step registration: register\nthe key, then prove ownership by signing a server-issued nonce.\nMarks the key `is_verified = true` on success.\n",
        "tags": [
          "Keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "public_key",
                  "signature",
                  "nonce"
                ],
                "properties": {
                  "public_key": {
                    "type": "string"
                  },
                  "signature": {
                    "type": "string"
                  },
                  "nonce": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Ownership verified"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/keys/external/verify-ownership \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"public_key\": \"<string>\",\n  \"signature\": \"<string>\",\n  \"nonce\": \"<string>\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/keys/external/verify-ownership', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"public_key\": \"<string>\",\n    \"signature\": \"<string>\",\n    \"nonce\": \"<string>\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/keys/external/verify-ownership'\nresp = httpx.post(url, headers=headers, json={\"public_key\": \"<string>\", \"signature\": \"<string>\", \"nonce\": \"<string>\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/keys/{key_id}": {
      "get": {
        "operationId": "getKeyPair",
        "summary": "Get a specific keypair (public fields only)",
        "tags": [
          "Keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "key_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Key UUID (signature key or API key, depending on the route)."
          }
        ],
        "responses": {
          "200": {
            "description": "Keypair detail",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Public keypair metadata. `account_signer_custody` is Auth's\nserver-authoritative classification for the account contract's\n`account_ecdsa65_v1` rail; clients must not infer custody from\n`provider_type`, `key_type`, or metadata. `custodial` selects Automatic\nexecution, `external` selects Manual wallet signing, and `null` means the\nkey is not eligible for that account-signature profile.\n",
                  "required": [
                    "id",
                    "entity_type",
                    "entity_id",
                    "key_name",
                    "public_key",
                    "provider_type",
                    "key_type",
                    "is_active",
                    "created_at",
                    "expires_at",
                    "metadata",
                    "account_signer_custody"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "entity_type": {
                      "type": "string",
                      "enum": [
                        "user",
                        "group"
                      ]
                    },
                    "entity_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "key_name": {
                      "type": "string"
                    },
                    "public_key": {
                      "type": "string"
                    },
                    "provider_type": {
                      "type": "string",
                      "enum": [
                        "OpenSSL",
                        "HSM",
                        "Hybrid",
                        "External"
                      ]
                    },
                    "key_type": {
                      "type": "string",
                      "description": "Stored-keypair purpose. `Signing` = secp256k1 used for EIP-191\nsignatures and account ownership; `Encryption` = secp256k1 used as\nan ECIES recipient endpoint; `Hybrid` = doubles as both (legacy\nemail-user shape); `External` = wallet-led signup, no private key\nheld server-side.\n",
                      "enum": [
                        "Signing",
                        "Encryption",
                        "Hybrid",
                        "External"
                      ]
                    },
                    "is_active": {
                      "type": "boolean"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "expires_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "metadata": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "additionalProperties": true
                    },
                    "account_signer_custody": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "custodial",
                        "external",
                        null
                      ]
                    },
                    "owner_registration_status": {
                      "type": "string",
                      "enum": [
                        "confirmed",
                        "pending_signature",
                        "retry_required",
                        "failed"
                      ]
                    },
                    "owner_registration_message_id": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (user, group, key, delegation token, wallet, provider).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "keypair": {
                    "summary": "Keypair UUID not found",
                    "value": {
                      "error": "keypair not found"
                    }
                  },
                  "kyc-provider": {
                    "summary": "Requested KYC provider not enabled in yaml",
                    "value": {
                      "error": "kyc provider not enabled"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/keys/{key_id} \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/keys/{key_id}', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/keys/{key_id}'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      },
      "put": {
        "operationId": "updateKeyPair",
        "summary": "Update keypair metadata (name, metadata blob)",
        "tags": [
          "Keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "key_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Key UUID (signature key or API key, depending on the route)."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "metadata": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Updated"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X PUT https://auth.yieldfabric.com/keys/{key_id} \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"name\": \"<string>\",\n  \"metadata\": {}\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/keys/{key_id}', {\n  method: 'PUT',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"name\": \"<string>\",\n    \"metadata\": {}\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/keys/{key_id}'\nresp = httpx.put(url, headers=headers, json={\"name\": \"<string>\", \"metadata\": {}})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      },
      "delete": {
        "operationId": "deleteKeyPair",
        "summary": "Deactivate a keypair (soft-delete)",
        "description": "Soft-deleted; row retained for audit.",
        "tags": [
          "Keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "key_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Key UUID (signature key or API key, depending on the route)."
          }
        ],
        "responses": {
          "204": {
            "description": "Deactivated"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X DELETE https://auth.yieldfabric.com/keys/{key_id} \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/keys/{key_id}', {\n  method: 'DELETE',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/keys/{key_id}'\nresp = httpx.delete(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/keys/{key_id}/rotate": {
      "post": {
        "operationId": "rotateKeyPair",
        "summary": "Begin a keypair rotation (off-chain only)",
        "description": "Generates a successor keypair and links it to the predecessor.\nUse `/keys/{key_id}/rotate-on-chain` to propagate the rotation\nto the user's on-chain accounts, then\n`/keys/{key_id}/rotation/finalize` to swap the active flag.\n",
        "tags": [
          "Keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "key_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Key UUID (signature key or API key, depending on the route)."
          }
        ],
        "responses": {
          "200": {
            "description": "Rotation initiated; new key id returned",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "new_key_id"
                  ],
                  "properties": {
                    "new_key_id": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/keys/{key_id}/rotate \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/keys/{key_id}/rotate', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/keys/{key_id}/rotate'\nresp = httpx.post(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/keys/{key_id}/rotate-on-chain": {
      "post": {
        "operationId": "rotateKeyOnChain",
        "summary": "Propagate a key rotation to on-chain accounts",
        "description": "Submits `AddOwner` + `RemoveOwner` operations via payments to\nregister the new key + retire the old key on every account the\nold key owned. Returns the list of on-chain operation IDs.\n",
        "tags": [
          "Keys",
          "Accounts"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "key_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Key UUID (signature key or API key, depending on the route)."
          }
        ],
        "responses": {
          "200": {
            "description": "On-chain rotation submitted"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/keys/{key_id}/rotate-on-chain \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/keys/{key_id}/rotate-on-chain', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/keys/{key_id}/rotate-on-chain'\nresp = httpx.post(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/keys/{key_id}/rotation/finalize": {
      "post": {
        "operationId": "finalizeRotation",
        "summary": "Finalize a rotation (swap active flags)",
        "description": "After on-chain rotation completes, deactivates the old key and\nactivates the successor. Idempotent.\n",
        "tags": [
          "Keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "key_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Key UUID (signature key or API key, depending on the route)."
          }
        ],
        "responses": {
          "204": {
            "description": "Rotation finalized"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/keys/{key_id}/rotation/finalize \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/keys/{key_id}/rotation/finalize', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/keys/{key_id}/rotation/finalize'\nresp = httpx.post(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/keys/{key_id}/rotation/cancel": {
      "post": {
        "operationId": "cancelRotation",
        "summary": "Cancel an in-progress rotation",
        "description": "Discards the successor key. Refuses to cancel after on-chain rotation has been submitted.",
        "tags": [
          "Keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "key_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Key UUID (signature key or API key, depending on the route)."
          }
        ],
        "responses": {
          "204": {
            "description": "Rotation cancelled"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/keys/{key_id}/rotation/cancel \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/keys/{key_id}/rotation/cancel', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/keys/{key_id}/rotation/cancel'\nresp = httpx.post(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/keys/{key_id}/wallet-status": {
      "get": {
        "operationId": "checkKeyWalletRegistration",
        "summary": "Check whether the key is registered as an owner of the user's wallets on-chain",
        "tags": [
          "Keys",
          "Accounts"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "key_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Key UUID (signature key or API key, depending on the route)."
          },
          {
            "in": "query",
            "name": "chain_id",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Required canonical assertion; it must equal the authenticated JWT chain."
          },
          {
            "in": "query",
            "name": "wallet_address",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Smart-account address to inspect. Omission resolves the entity default on the authenticated chain."
          }
        ],
        "responses": {
          "200": {
            "description": "Per-wallet registration status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "key_id",
                    "chain_id",
                    "public_key",
                    "is_registered_with_wallet",
                    "key_address",
                    "status",
                    "operation_id",
                    "message_id",
                    "operation_kind",
                    "retry_required"
                  ],
                  "properties": {
                    "key_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "chain_id": {
                      "type": "string"
                    },
                    "public_key": {
                      "type": "string"
                    },
                    "key_address": {
                      "type": "string"
                    },
                    "wallet_address": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "registered_wallet_address": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "is_registered_with_wallet": {
                      "type": "boolean"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "confirmed",
                        "unregistered",
                        "pending",
                        "retry_required"
                      ]
                    },
                    "operation_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "message_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "operation_kind": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "add",
                        "remove",
                        null
                      ]
                    },
                    "retry_required": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET 'https://auth.yieldfabric.com/keys/{key_id}/wallet-status?chain_id=153' \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/keys/{key_id}/wallet-status?chain_id=153', {\n  method: 'GET',\n  headers: { 'Authorization': 'Bearer ' + process.env.TOKEN },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/keys/{key_id}/wallet-status'\nresp = httpx.get(url, headers=headers, params={'chain_id': '153'})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/keys/register-with-wallet": {
      "post": {
        "operationId": "registerKeyWithWallet",
        "summary": "Register the user's active key as an owner of their default wallet",
        "description": "Convenience wrapper — looks up the user's default wallet on the\nactive chain and submits an `AddOwner` operation if the key\nisn't already an owner. Idempotent.\n",
        "tags": [
          "Keys",
          "Accounts"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "key_id",
                  "wallet_address",
                  "chain_id"
                ],
                "properties": {
                  "key_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "wallet_address": {
                    "type": "string"
                  },
                  "chain_id": {
                    "type": "string",
                    "description": "Required canonical assertion matching the JWT chain."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Registration completed (or already registered)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "status",
                    "message_id",
                    "error",
                    "retry_required",
                    "key_id",
                    "chain_id",
                    "key_address",
                    "wallet_address"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "confirmed",
                        "pending_signature",
                        "retry_required",
                        "failed"
                      ]
                    },
                    "message_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "error": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "retry_required": {
                      "type": "boolean"
                    },
                    "key_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "chain_id": {
                      "type": "string"
                    },
                    "key_address": {
                      "type": "string"
                    },
                    "wallet_address": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The exact user/group has no smart account on the JWT-selected chain.\nThe handler checked this before durable mutation or MQ handoff. Activate\nthe returned entity/chain, refresh the same actor session, and retry once.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "code",
                    "entity_kind",
                    "entity_id",
                    "chain_id"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "const": "CHAIN_ACCOUNT_REQUIRED"
                    },
                    "entity_kind": {
                      "type": "string",
                      "enum": [
                        "user",
                        "group"
                      ]
                    },
                    "entity_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "chain_id": {
                      "type": "string"
                    },
                    "activation_url": {
                      "type": "string",
                      "description": "Exact recovery resource when the handler can name it."
                    },
                    "operation": {
                      "type": "string",
                      "description": "Stable operation label for diagnostics; clients route on `code` and the entity/chain fields."
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/keys/register-with-wallet \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"key_id\":\"<uuid>\",\"wallet_address\":\"0x<address>\",\"chain_id\":\"153\"}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/keys/register-with-wallet', {\n  method: 'POST',\n  headers: { 'Authorization': 'Bearer ' + process.env.TOKEN, 'Content-Type': 'application/json' },\n  body: JSON.stringify({ key_id: '<uuid>', wallet_address: '0x<address>', chain_id: '153' }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/keys/register-with-wallet'\nresp = httpx.post(url, headers=headers, json={'key_id': '<uuid>', 'wallet_address': '0x<address>', 'chain_id': '153'})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/keys/register-with-specific-wallet": {
      "post": {
        "operationId": "registerKeyWithSpecificWallet",
        "summary": "Register the user's active key as an owner of a specific wallet address",
        "tags": [
          "Keys",
          "Accounts"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "key_id",
                  "wallet_address",
                  "chain_id"
                ],
                "properties": {
                  "key_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "wallet_address": {
                    "type": "string"
                  },
                  "chain_id": {
                    "type": "string",
                    "description": "Required canonical assertion matching the JWT chain."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Registration completed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "status",
                    "message_id",
                    "error",
                    "retry_required",
                    "key_id",
                    "chain_id",
                    "key_address",
                    "wallet_address"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "confirmed",
                        "pending_signature",
                        "retry_required",
                        "failed"
                      ]
                    },
                    "message_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "error": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "retry_required": {
                      "type": "boolean"
                    },
                    "key_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "chain_id": {
                      "type": "string"
                    },
                    "key_address": {
                      "type": "string"
                    },
                    "wallet_address": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/keys/register-with-specific-wallet \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"key_id\":\"<uuid>\",\"wallet_address\":\"0x<address>\",\"chain_id\":\"153\"}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/keys/register-with-specific-wallet', {\n  method: 'POST',\n  headers: { 'Authorization': 'Bearer ' + process.env.TOKEN, 'Content-Type': 'application/json' },\n  body: JSON.stringify({ key_id: '<uuid>', wallet_address: '0x<address>', chain_id: '153' }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/keys/register-with-specific-wallet'\nresp = httpx.post(url, headers=headers, json={'key_id': '<uuid>', 'wallet_address': '0x<address>', 'chain_id': '153'})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/keys/unregister-from-specific-wallet": {
      "post": {
        "operationId": "unregisterKeyFromSpecificWallet",
        "summary": "Remove the user's active key as an owner of a specific wallet",
        "tags": [
          "Keys",
          "Accounts"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "key_id",
                  "wallet_address",
                  "chain_id"
                ],
                "properties": {
                  "key_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "wallet_address": {
                    "type": "string"
                  },
                  "chain_id": {
                    "type": "string",
                    "description": "Required canonical assertion matching the JWT chain."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Unregistration completed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "status",
                    "message_id",
                    "error",
                    "retry_required",
                    "key_id",
                    "chain_id",
                    "wallet_address",
                    "keypair_globally_deactivated"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "confirmed",
                        "pending_signature",
                        "retry_required",
                        "failed"
                      ]
                    },
                    "message_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "error": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "retry_required": {
                      "type": "boolean"
                    },
                    "key_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "chain_id": {
                      "type": "string"
                    },
                    "wallet_address": {
                      "type": "string"
                    },
                    "keypair_globally_deactivated": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/keys/unregister-from-specific-wallet \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"key_id\":\"<uuid>\",\"wallet_address\":\"0x<address>\",\"chain_id\":\"153\"}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/keys/unregister-from-specific-wallet', {\n  method: 'POST',\n  headers: { 'Authorization': 'Bearer ' + process.env.TOKEN, 'Content-Type': 'application/json' },\n  body: JSON.stringify({ key_id: '<uuid>', wallet_address: '0x<address>', chain_id: '153' }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/keys/unregister-from-specific-wallet'\nresp = httpx.post(url, headers=headers, json={'key_id': '<uuid>', 'wallet_address': '0x<address>', 'chain_id': '153'})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/keys/users/{user_id}/keys": {
      "get": {
        "operationId": "listUserKeyPairs",
        "summary": "List a user's keypairs (admin / self)",
        "tags": [
          "Keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "User UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "Keypairs",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "description": "Public keypair metadata. `account_signer_custody` is Auth's\nserver-authoritative classification for the account contract's\n`account_ecdsa65_v1` rail; clients must not infer custody from\n`provider_type`, `key_type`, or metadata. `custodial` selects Automatic\nexecution, `external` selects Manual wallet signing, and `null` means the\nkey is not eligible for that account-signature profile.\n",
                    "required": [
                      "id",
                      "entity_type",
                      "entity_id",
                      "key_name",
                      "public_key",
                      "provider_type",
                      "key_type",
                      "is_active",
                      "created_at",
                      "expires_at",
                      "metadata",
                      "account_signer_custody"
                    ],
                    "properties": {
                      "id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "entity_type": {
                        "type": "string",
                        "enum": [
                          "user",
                          "group"
                        ]
                      },
                      "entity_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "key_name": {
                        "type": "string"
                      },
                      "public_key": {
                        "type": "string"
                      },
                      "provider_type": {
                        "type": "string",
                        "enum": [
                          "OpenSSL",
                          "HSM",
                          "Hybrid",
                          "External"
                        ]
                      },
                      "key_type": {
                        "type": "string",
                        "description": "Stored-keypair purpose. `Signing` = secp256k1 used for EIP-191\nsignatures and account ownership; `Encryption` = secp256k1 used as\nan ECIES recipient endpoint; `Hybrid` = doubles as both (legacy\nemail-user shape); `External` = wallet-led signup, no private key\nheld server-side.\n",
                        "enum": [
                          "Signing",
                          "Encryption",
                          "Hybrid",
                          "External"
                        ]
                      },
                      "is_active": {
                        "type": "boolean"
                      },
                      "created_at": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "expires_at": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "format": "date-time"
                      },
                      "metadata": {
                        "type": [
                          "object",
                          "null"
                        ],
                        "additionalProperties": true
                      },
                      "account_signer_custody": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "enum": [
                          "custodial",
                          "external",
                          null
                        ]
                      },
                      "owner_registration_status": {
                        "type": "string",
                        "enum": [
                          "confirmed",
                          "pending_signature",
                          "retry_required",
                          "failed"
                        ]
                      },
                      "owner_registration_message_id": {
                        "type": "string",
                        "format": "uuid"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/keys/users/{user_id}/keys \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/keys/users/{user_id}/keys', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/keys/users/{user_id}/keys'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/keys/users/{user_id}/default-key": {
      "get": {
        "operationId": "getUserDefaultKeyPair",
        "summary": "Get a user's active default keypair",
        "description": "Returns the active keypair the server uses to sign / decrypt on\nthe user's behalf. Selection excludes `KeyType: External` in\nevery fallback — external keys (MetaMask, passkey, Dynamic)\ncannot be server-signed by design, and this endpoint is the\nMQ auto-execute flow's fallback when wallet preferences leave\n`authorized_keys` empty, so an External row here would silently\nbreak auto-sign for accounts that mix custodial + external\nkeys. See the\n[key-storage architecture note](../../architecture/key-storage.md#auto-sign-key-selection--external-is-excluded-everywhere)\nfor the cross-site rule.\n",
        "tags": [
          "Keys"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "User UUID."
          }
        ],
        "responses": {
          "200": {
            "description": "Default keypair (never `KeyType: External`)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Public keypair metadata. `account_signer_custody` is Auth's\nserver-authoritative classification for the account contract's\n`account_ecdsa65_v1` rail; clients must not infer custody from\n`provider_type`, `key_type`, or metadata. `custodial` selects Automatic\nexecution, `external` selects Manual wallet signing, and `null` means the\nkey is not eligible for that account-signature profile.\n",
                  "required": [
                    "id",
                    "entity_type",
                    "entity_id",
                    "key_name",
                    "public_key",
                    "provider_type",
                    "key_type",
                    "is_active",
                    "created_at",
                    "expires_at",
                    "metadata",
                    "account_signer_custody"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "entity_type": {
                      "type": "string",
                      "enum": [
                        "user",
                        "group"
                      ]
                    },
                    "entity_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "key_name": {
                      "type": "string"
                    },
                    "public_key": {
                      "type": "string"
                    },
                    "provider_type": {
                      "type": "string",
                      "enum": [
                        "OpenSSL",
                        "HSM",
                        "Hybrid",
                        "External"
                      ]
                    },
                    "key_type": {
                      "type": "string",
                      "description": "Stored-keypair purpose. `Signing` = secp256k1 used for EIP-191\nsignatures and account ownership; `Encryption` = secp256k1 used as\nan ECIES recipient endpoint; `Hybrid` = doubles as both (legacy\nemail-user shape); `External` = wallet-led signup, no private key\nheld server-side.\n",
                      "enum": [
                        "Signing",
                        "Encryption",
                        "Hybrid",
                        "External"
                      ]
                    },
                    "is_active": {
                      "type": "boolean"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "expires_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "metadata": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "additionalProperties": true
                    },
                    "account_signer_custody": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "custodial",
                        "external",
                        null
                      ]
                    },
                    "owner_registration_status": {
                      "type": "string",
                      "enum": [
                        "confirmed",
                        "pending_signature",
                        "retry_required",
                        "failed"
                      ]
                    },
                    "owner_registration_message_id": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource not found (user, group, key, delegation token, wallet, provider).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "keypair": {
                    "summary": "Keypair UUID not found",
                    "value": {
                      "error": "keypair not found"
                    }
                  },
                  "kyc-provider": {
                    "summary": "Requested KYC provider not enabled in yaml",
                    "value": {
                      "error": "kyc provider not enabled"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/keys/users/{user_id}/default-key \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/keys/users/{user_id}/default-key', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/keys/users/{user_id}/default-key'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/keys/logs": {
      "get": {
        "operationId": "getKeyOperationLogs",
        "summary": "Read the key-operation audit log",
        "description": "Returns key-operation audit entries for the current user, paginated.",
        "tags": [
          "Keys",
          "Admin"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 100
            },
            "description": "Max items to return."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Items to skip before returning."
          }
        ],
        "responses": {
          "200": {
            "description": "Audit log entries"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/keys/logs \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/keys/logs', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/keys/logs'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/keys/providers/health": {
      "get": {
        "operationId": "getProviderHealth",
        "summary": "Identity-provider health probe",
        "description": "Returns the latest health snapshot per configured identity provider.",
        "tags": [
          "Keys",
          "Identity providers"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Provider health",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "provider": {
                        "type": "string"
                      },
                      "status": {
                        "type": "string",
                        "enum": [
                          "healthy",
                          "degraded",
                          "down"
                        ]
                      },
                      "last_check": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "last_error": {
                        "type": [
                          "string",
                          "null"
                        ]
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/keys/providers/health \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/keys/providers/health', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/keys/providers/health'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/admin/users": {
      "get": {
        "operationId": "adminListUsers",
        "summary": "List every user (SuperAdmin)",
        "description": "Newest first, with verification tier, group/key counts, and\nbilling posture.\n\n`email_verified` / `kyc_verified` mirror the predicates the\ncapability gate uses (`auth::capability::verification_tier`) —\nthe same verified onboarding-OTP row, and the same\n(KYC-bearing provider link OR live `credential_verifications`\nrow) disjunction. They are a DISPLAY projection: the gate stays\nthe authority.\n",
        "tags": [
          "Admin"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Case-insensitive substring match on email."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 500
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 0,
              "minimum": 0
            }
          },
          {
            "name": "include_inactive",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": true
            },
            "description": "Defaults true — an operator console that hid deactivated rows would misreport the roster."
          }
        ],
        "responses": {
          "200": {
            "description": "One page of the user roster",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    },
                    "period_start": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "billing_enabled": {
                      "type": "boolean"
                    },
                    "users": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "email": {
                            "type": "string"
                          },
                          "role": {
                            "type": "string",
                            "example": "User"
                          },
                          "is_active": {
                            "type": "boolean"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true
                          },
                          "updated_at": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true
                          },
                          "prepaid_plan_required": {
                            "type": "boolean"
                          },
                          "email_verified": {
                            "type": "boolean"
                          },
                          "kyc_verified": {
                            "type": "boolean"
                          },
                          "verification_tier": {
                            "type": "string",
                            "enum": [
                              "unverified",
                              "email_verified",
                              "kyc_verified"
                            ]
                          },
                          "group_count": {
                            "type": "integer"
                          },
                          "key_count": {
                            "type": "integer"
                          },
                          "period_usd_micros": {
                            "type": "integer",
                            "format": "int64"
                          },
                          "billing": {
                            "nullable": true,
                            "allOf": [
                              {
                                "type": "object",
                                "properties": {
                                  "account_id": {
                                    "type": "string",
                                    "format": "uuid"
                                  },
                                  "status": {
                                    "type": "string",
                                    "enum": [
                                      "active",
                                      "past_due",
                                      "delinquent",
                                      "canceled",
                                      "reactivating"
                                    ]
                                  },
                                  "group_billing_policy": {
                                    "type": "string",
                                    "enum": [
                                      "admin_pays",
                                      "member_pays"
                                    ]
                                  },
                                  "has_payment_method": {
                                    "type": "boolean"
                                  },
                                  "stripe_customer_id": {
                                    "type": "string",
                                    "nullable": true
                                  },
                                  "has_subscription": {
                                    "type": "boolean"
                                  },
                                  "credit_plan_id": {
                                    "type": "string",
                                    "nullable": true
                                  },
                                  "credit_balance_micros": {
                                    "type": "integer",
                                    "format": "int64"
                                  },
                                  "credit_plan_renews_at": {
                                    "type": "string",
                                    "format": "date-time",
                                    "nullable": true
                                  },
                                  "sponsorship_mode": {
                                    "type": "string",
                                    "enum": [
                                      "off",
                                      "invite_only",
                                      "open"
                                    ]
                                  },
                                  "delinquent_since": {
                                    "type": "string",
                                    "format": "date-time",
                                    "nullable": true
                                  },
                                  "connect_payouts_enabled": {
                                    "type": "boolean"
                                  }
                                }
                              }
                            ]
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer"
          },
          "403": {
            "description": "Not a SuperAdmin, or not a direct personal session"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET 'https://auth.yieldfabric.com/auth/admin/users?search=example.com&limit=50' \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/admin/users?limit=50', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/admin/users'\nresp = httpx.get(url, headers=headers, params={'limit': 50})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/admin/groups": {
      "get": {
        "operationId": "adminListGroups",
        "summary": "List every group (SuperAdmin)",
        "description": "Newest first, with live membership count, KYB posture, resolved\nbilling admin, and billing account.\n\nOff-chain state only — it deliberately does NOT resolve each\ngroup's on-chain default wallet (that is one chain read per\ngroup, which is the wrong shape for a roster). Use\n`GET /auth/groups` or the per-group endpoints for chain detail.\n\n`entity_verification_status` mirrors\n`group_helpers::resolve_entity_verification`: a group with no\nbound `entity_verification_profile` reads `not_required` (it\nnever declared itself a legal entity), not `unverified`.\n",
        "tags": [
          "Admin"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Case-insensitive substring match on name, description, or group_type."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 500
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 0,
              "minimum": 0
            }
          },
          {
            "name": "include_inactive",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page of the group roster",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    },
                    "period_start": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "billing_enabled": {
                      "type": "boolean"
                    },
                    "groups": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "name": {
                            "type": "string"
                          },
                          "description": {
                            "type": "string",
                            "nullable": true
                          },
                          "group_type": {
                            "type": "string"
                          },
                          "is_active": {
                            "type": "boolean"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true
                          },
                          "updated_at": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true
                          },
                          "member_count": {
                            "type": "integer"
                          },
                          "member_verification_profile": {
                            "type": "string",
                            "nullable": true
                          },
                          "entity_verification_profile": {
                            "type": "string",
                            "nullable": true
                          },
                          "entity_verification_status": {
                            "type": "string",
                            "enum": [
                              "not_required",
                              "unverified",
                              "pending",
                              "verified",
                              "rejected",
                              "expired"
                            ]
                          },
                          "billing_admin_email": {
                            "type": "string",
                            "nullable": true
                          },
                          "period_usd_micros": {
                            "type": "integer",
                            "format": "int64"
                          },
                          "billing": {
                            "nullable": true,
                            "allOf": [
                              {
                                "type": "object",
                                "properties": {
                                  "account_id": {
                                    "type": "string",
                                    "format": "uuid"
                                  },
                                  "status": {
                                    "type": "string",
                                    "enum": [
                                      "active",
                                      "past_due",
                                      "delinquent",
                                      "canceled",
                                      "reactivating"
                                    ]
                                  },
                                  "group_billing_policy": {
                                    "type": "string",
                                    "enum": [
                                      "admin_pays",
                                      "member_pays"
                                    ]
                                  },
                                  "has_payment_method": {
                                    "type": "boolean"
                                  },
                                  "stripe_customer_id": {
                                    "type": "string",
                                    "nullable": true
                                  },
                                  "has_subscription": {
                                    "type": "boolean"
                                  },
                                  "credit_plan_id": {
                                    "type": "string",
                                    "nullable": true
                                  },
                                  "credit_balance_micros": {
                                    "type": "integer",
                                    "format": "int64"
                                  },
                                  "credit_plan_renews_at": {
                                    "type": "string",
                                    "format": "date-time",
                                    "nullable": true
                                  },
                                  "sponsorship_mode": {
                                    "type": "string",
                                    "enum": [
                                      "off",
                                      "invite_only",
                                      "open"
                                    ]
                                  },
                                  "delinquent_since": {
                                    "type": "string",
                                    "format": "date-time",
                                    "nullable": true
                                  },
                                  "connect_payouts_enabled": {
                                    "type": "boolean"
                                  }
                                }
                              }
                            ]
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer"
          },
          "403": {
            "description": "Not a SuperAdmin, or not a direct personal session"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET 'https://auth.yieldfabric.com/auth/admin/groups?limit=50' \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/admin/groups?limit=50', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/admin/groups'\nresp = httpx.get(url, headers=headers, params={'limit': 50})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/auth/admin/billing": {
      "get": {
        "operationId": "adminListBilling",
        "summary": "List every billing account (SuperAdmin)",
        "description": "Every `billing_accounts` row with its owner resolved to a human\nlabel (user email / group name) and its current-period spend,\nordered by spend descending. Plus a deployment-wide `summary`\nand per-meter period totals.\n\nComplements `GET /auth/billing/ops`, which answers \"is the\ningest→price→push pipeline healthy\". This one answers \"who owes\nwhat\". An operator console should show both.\n\n**Never 404s on a billing-less deployment.** The tables exist\nregardless of `auth.billing`, so a deployment with billing off\ngets an honest roster plus `billing_enabled: false` rather than\na broken surface.\n",
        "tags": [
          "Admin"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Case-insensitive substring match on the resolved owner label."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 500
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 0,
              "minimum": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page of billing accounts plus deployment roll-ups",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    },
                    "period_start": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "billing_enabled": {
                      "type": "boolean"
                    },
                    "price_version": {
                      "type": "string",
                      "nullable": true
                    },
                    "summary": {
                      "type": "object",
                      "description": "Deployment-wide, unfiltered by `search`.",
                      "properties": {
                        "accounts": {
                          "type": "integer"
                        },
                        "delinquent": {
                          "type": "integer"
                        },
                        "past_due": {
                          "type": "integer"
                        },
                        "with_card": {
                          "type": "integer"
                        },
                        "on_credit_plan": {
                          "type": "integer"
                        },
                        "credit_balance_micros": {
                          "type": "integer",
                          "format": "int64"
                        },
                        "period_usd_micros": {
                          "type": "integer",
                          "format": "int64"
                        },
                        "line_count": {
                          "type": "integer"
                        },
                        "platform_usd_micros": {
                          "type": "integer",
                          "format": "int64",
                          "description": "Unattributed usage the platform absorbed — recorded",
                          "never pushed to Stripe.": null
                        },
                        "platform_lines": {
                          "type": "integer"
                        }
                      }
                    },
                    "meters": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "meter": {
                            "type": "string",
                            "example": "llm_usage_usd"
                          },
                          "amount_usd_micros": {
                            "type": "integer",
                            "format": "int64"
                          },
                          "line_count": {
                            "type": "integer"
                          }
                        }
                      }
                    },
                    "accounts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "account_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "owner_type": {
                            "type": "string",
                            "enum": [
                              "user",
                              "group"
                            ]
                          },
                          "owner_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "owner_label": {
                            "type": "string",
                            "nullable": true,
                            "description": "User email or group name; falls back to the owner uuid."
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "active",
                              "past_due",
                              "delinquent",
                              "canceled",
                              "reactivating"
                            ]
                          },
                          "group_billing_policy": {
                            "type": "string",
                            "enum": [
                              "admin_pays",
                              "member_pays"
                            ]
                          },
                          "has_payment_method": {
                            "type": "boolean"
                          },
                          "stripe_customer_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "has_subscription": {
                            "type": "boolean"
                          },
                          "credit_plan_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "credit_balance_micros": {
                            "type": "integer",
                            "format": "int64"
                          },
                          "credit_authorization_balance_micros": {
                            "type": "integer",
                            "format": "int64"
                          },
                          "credit_plan_renews_at": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true
                          },
                          "sponsorship_mode": {
                            "type": "string",
                            "enum": [
                              "off",
                              "invite_only",
                              "open"
                            ]
                          },
                          "delinquent_since": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true
                          },
                          "failed_stripe_invoice_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "connect_payouts_enabled": {
                            "type": "boolean"
                          },
                          "billing_admin_email": {
                            "type": "string",
                            "nullable": true
                          },
                          "period_usd_micros": {
                            "type": "integer",
                            "format": "int64"
                          },
                          "line_count": {
                            "type": "integer"
                          },
                          "unpushed": {
                            "type": "integer",
                            "description": "Line items never pushed to Stripe (all time",
                            "not period-windowed).": null
                          },
                          "last_usage_at": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer"
          },
          "403": {
            "description": "Not a SuperAdmin, or not a direct personal session"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET 'https://auth.yieldfabric.com/auth/admin/billing?limit=50' \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/auth/admin/billing?limit=50', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/auth/admin/billing'\nresp = httpx.get(url, headers=headers, params={'limit': 50})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/health": {
      "get": {
        "operationId": "healthCheck",
        "summary": "Service health check",
        "tags": [
          "Admin"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Service is up",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "ok"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/health"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/health', {\n  method: 'GET',\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nurl = 'https://auth.yieldfabric.com/health'\nresp = httpx.get(url)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/health/db": {
      "get": {
        "operationId": "databaseHealthCheck",
        "summary": "Database connectivity check",
        "description": "Verifies the auth database is reachable. Returns 503 on failure.",
        "tags": [
          "Admin"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Database reachable"
          },
          "503": {
            "description": "Database unreachable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/health/db"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/health/db', {\n  method: 'GET',\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nurl = 'https://auth.yieldfabric.com/health/db'\nresp = httpx.get(url)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/health/cleanup": {
      "post": {
        "operationId": "cleanupRefreshTokens",
        "summary": "Trigger refresh-token cleanup",
        "description": "Manually triggers the background cleanup of expired refresh\ntokens. Normally this runs on a timer; the endpoint exists for\nops scripts.\n",
        "tags": [
          "Admin"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Cleanup completed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "removed": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/health/cleanup \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/health/cleanup', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/health/cleanup'\nresp = httpx.post(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/version": {
      "get": {
        "operationId": "versionInfo",
        "summary": "Build/version identity",
        "description": "Returns the running build's identity (`service`, `api_version`,\n`git_sha`, `built_at`). `api_version` is baked from this spec's\n`info.version` at compile time, so a deployment cannot report a\nversion it doesn't implement — the way to confirm which API\ncontract a server serves without probing routes. No auth required.\n",
        "tags": [
          "Admin"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Build identity of the running service.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "service",
                    "api_version",
                    "git_sha",
                    "built_at"
                  ],
                  "properties": {
                    "service": {
                      "type": "string",
                      "example": "yieldfabric-auth"
                    },
                    "api_version": {
                      "type": "string",
                      "example": "0.3.0"
                    },
                    "git_sha": {
                      "type": "string",
                      "example": "e45bea7b3a30"
                    },
                    "built_at": {
                      "type": "string",
                      "example": "2026-06-14T10:42:33.000Z"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/version"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/version');\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx\nurl = 'https://auth.yieldfabric.com/version'\nresp = httpx.get(url)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/protected/jwt": {
      "get": {
        "operationId": "protectedJwt",
        "summary": "Validate a JWT and return its auth context projection",
        "description": "The endpoint sister services hit when they receive a bearer\ntoken and need auth to validate it. The response is a\n`ProtectedResponse` projection, not the bearer JWT's raw claims:\nit uses `user_id` rather than `sub` and has no `kind` field.\nFor a nested child it echoes the complete sealed authority tuple:\n`delegation_path`, `authority_source`, `membership_edge`,\n`signer_key_id`, `signer_address`, `signer_scheme`, and\n`signer_custody`. Partial or malformed provenance is rejected.\nPayments and agents normally validate service tokens locally,\nalthough this handler also returns a minimal projection when one\nis presented.\n\n`session_kind` reports the kind auth computed for the bearer from\nits own credential predicates — `personal` (login, with-services,\nprovider exchange, personal API key), `connector` (OAuth grant or\nconnector API-key session, incl. on-behalf-of tokens), `delegated`\n(acting as a group) or `service` — and `allowed_chains` the chains\na connector session may run on (absent = unrestricted / not a\nconnector). Consumers read `AuthClaims::is_direct_personal()` /\n`is_connector()` off this projection; they never re-derive the kind.\n",
        "tags": [
          "Admin"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Validated auth context",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "user_id",
                    "role"
                  ],
                  "description": "Validated context projection returned by `/protected/jwt`; this is not\nthe raw JWT claims object. Service-token responses use\n`user_id: service:<name>` and leave user/group wallet fields null.\nOwnership-delegation provenance is copied from the signed bearer.\n",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "user_id": {
                      "type": "string",
                      "description": "User UUID for user tokens; `service:<name>` for service tokens."
                    },
                    "role": {
                      "type": "string",
                      "description": "Built-in role assigned to every user. Role + Permission grants make\nup the RBAC matrix; see `auth.roles` in `config/system.yaml` for\nrole overrides. SuperAdmin always has every permission.\n",
                      "enum": [
                        "SuperAdmin",
                        "Admin",
                        "Manager",
                        "Operator",
                        "User",
                        "Viewer",
                        "ApiClient"
                      ]
                    },
                    "account_address": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "User's default account on `default_chain_id`; null until that chain is explicitly activated."
                    },
                    "group_account_address": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Group's deployed account address (delegation tokens only)."
                    },
                    "acting_as": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Group ID when acting on behalf of a group."
                    },
                    "delegation_scope": {
                      "type": [
                        "array",
                        "null"
                      ],
                      "items": {
                        "type": "string",
                        "description": "Capability that may be scoped to one `acting_as` group. Delegation\nendpoints reject the entire request with `400` when a value is\nunknown, personal-user authority, or platform-global authority;\nthey do not silently drop unsupported values. `PolicyExecution` is\nvalid for direct policy-member delegation, but the A→B owned-group\nexchange rejects it. The ownership exchange further attenuates the\nremaining valid request to `OwnedGroupOperationalPermission`.\n",
                        "enum": [
                          "CreateEntity",
                          "ReadEntity",
                          "UpdateEntity",
                          "DeleteEntity",
                          "ApiRead",
                          "ApiWrite",
                          "BulkOperations",
                          "ReadGroup",
                          "UpdateGroup",
                          "DeleteGroup",
                          "ManageGroupMembers",
                          "ManageGroupPermissions",
                          "ManageGroupOwners",
                          "ManageGroupEntityScope",
                          "CreateDelegationToken",
                          "ViewDelegationTokens",
                          "RevokeDelegationToken",
                          "CryptoOperations",
                          "PolicyExecution",
                          "LlmInference"
                        ]
                      }
                    },
                    "delegation_token_id": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "delegation_path": {
                      "type": [
                        "array",
                        "null"
                      ],
                      "items": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "description": "Ordered `[source_group_id, target_group_id]` path for a one-hop ownership delegation."
                    },
                    "authority_source": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Signed authority reason. Auth recognizes only `nft_membership` with complete edge and human-signer provenance; unknown, partial, or malformed provenance fails closed."
                    },
                    "membership_edge": {
                      "oneOf": [
                        {
                          "type": "object",
                          "additionalProperties": false,
                          "required": [
                            "parent_group_id",
                            "child_group_id",
                            "chain_id",
                            "credential_contract",
                            "token_id"
                          ],
                          "properties": {
                            "parent_group_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "child_group_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "chain_id": {
                              "type": "string"
                            },
                            "credential_contract": {
                              "type": "string",
                              "pattern": "^0x[0-9a-f]{40}$"
                            },
                            "token_id": {
                              "type": "string",
                              "pattern": "^[1-9][0-9]*$",
                              "description": "Canonical non-zero decimal uint256."
                            }
                          }
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "signer_key_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid",
                      "description": "Exact account-compatible human user key selected for nested execution."
                    },
                    "signer_address": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "pattern": "^0x[0-9a-f]{40}$"
                    },
                    "signer_scheme": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "account_ecdsa65_v1",
                        null
                      ]
                    },
                    "signer_custody": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "custodial",
                        "external",
                        null
                      ],
                      "description": "Custodial keys use Automatic execution; external keys use Manual execution."
                    },
                    "default_wallet_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Active entity wallet on `default_chain_id`; null for a walletless personal session."
                    },
                    "default_chain_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Required, enabled chain for user tokens; null only for modeless service tokens."
                    },
                    "permissions": {
                      "type": [
                        "array",
                        "null"
                      ],
                      "items": {
                        "type": "string"
                      },
                      "description": "Effective post-gate permissions; delegation validators project delegation_scope here."
                    },
                    "billing_gate": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Denial reason annotation when the billing gate stripped capabilities."
                    },
                    "billing_gate_payer": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "self",
                        "group",
                        null
                      ],
                      "description": "Billing context evaluated by the mint-time gate."
                    },
                    "billing_gate_payer_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid",
                      "description": "Exact paying group, present for healthy and gated group-paid sessions; null for personal/self pay."
                    },
                    "verification_profiles": {
                      "type": [
                        "array",
                        "null"
                      ],
                      "items": {
                        "type": "string"
                      },
                      "description": "Verification-profile names satisfied by the user at mint time."
                    },
                    "billing_sponsor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid",
                      "description": "Validated sponsoring platform group for this session; attribution only."
                    },
                    "onboarding_gate": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Platform-onboarding denial annotation (`kyc_required` or `email_required`)."
                    },
                    "session_kind": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "personal",
                        "connector",
                        "mcp_signer",
                        "delegated",
                        "service",
                        null
                      ],
                      "description": "The session kind auth computed for the validated bearer. `personal` is the only kind that may execute or confirm;\n`connector` (OAuth grant / connector API key / on-behalf-of token)\nmay read and propose and NEVER carries `CryptoOperations` /\n`PolicyExecution` (every connector mint path strips them);\n`mcp_signer` is the signer-bearing MCP credential from\n`/auth/mcp/login` / `/auth/mcp/generate-token` — pinned to one\ncustodial signing key, keeps `CryptoOperations`, never a direct\nsession, never admitted at `/mcp`, never exchangeable;\n`delegated` and `service` as today. Absent only from responses of\nolder auth deployments — consumers fail closed on absence.\n"
                    },
                    "allowed_chains": {
                      "type": [
                        "array",
                        "null"
                      ],
                      "items": {
                        "type": "string"
                      },
                      "description": "Chains a connector session may run on (copied from its credential at mint, never widened on refresh). Absent = unrestricted, not a connector, or an older deployment."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, expired, or wrong-audience JWT (user or service).\nIncludes invalid API keys on the exchange endpoints, failed wallet\nsignatures on the signin endpoints, and failed Sumsub HMAC on the\nwebhook.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "no-token": {
                    "summary": "Missing Authorization header",
                    "value": {
                      "error": "missing bearer token"
                    }
                  },
                  "invalid-token": {
                    "summary": "JWT signature / structure invalid",
                    "value": {
                      "error": "invalid token"
                    }
                  },
                  "invalid-subject": {
                    "summary": "JWT sub field unparseable",
                    "value": {
                      "error": "invalid token subject"
                    }
                  },
                  "webhook-signature": {
                    "summary": "Sumsub HMAC verification failed",
                    "value": {
                      "error": "invalid webhook signature or payload"
                    }
                  },
                  "averer-reverify": {
                    "summary": "Averer flagged a re-verification requirement",
                    "value": {
                      "error": "averer_reverify_required"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/protected/jwt \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/protected/jwt', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/protected/jwt'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/protected/signature": {
      "get": {
        "operationId": "protectedSignature",
        "summary": "Signature-authenticated probe",
        "description": "Returns 200 if the wallet-signature headers verify against a registered key. Used for sanity checks.",
        "tags": [
          "Admin"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Signature verified"
          },
          "401": {
            "description": "Missing, malformed, expired, or wrong-audience JWT (user or service).\nIncludes invalid API keys on the exchange endpoints, failed wallet\nsignatures on the signin endpoints, and failed Sumsub HMAC on the\nwebhook.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "no-token": {
                    "summary": "Missing Authorization header",
                    "value": {
                      "error": "missing bearer token"
                    }
                  },
                  "invalid-token": {
                    "summary": "JWT signature / structure invalid",
                    "value": {
                      "error": "invalid token"
                    }
                  },
                  "invalid-subject": {
                    "summary": "JWT sub field unparseable",
                    "value": {
                      "error": "invalid token subject"
                    }
                  },
                  "webhook-signature": {
                    "summary": "Sumsub HMAC verification failed",
                    "value": {
                      "error": "invalid webhook signature or payload"
                    }
                  },
                  "averer-reverify": {
                    "summary": "Averer flagged a re-verification requirement",
                    "value": {
                      "error": "averer_reverify_required"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/protected/signature"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/protected/signature', {\n  method: 'GET',\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nurl = 'https://auth.yieldfabric.com/protected/signature'\nresp = httpx.get(url)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/protected/api-key": {
      "get": {
        "operationId": "protectedApiKey",
        "summary": "API-key-authenticated probe",
        "tags": [
          "Admin"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "API key verified"
          },
          "401": {
            "description": "Missing, malformed, expired, or wrong-audience JWT (user or service).\nIncludes invalid API keys on the exchange endpoints, failed wallet\nsignatures on the signin endpoints, and failed Sumsub HMAC on the\nwebhook.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "no-token": {
                    "summary": "Missing Authorization header",
                    "value": {
                      "error": "missing bearer token"
                    }
                  },
                  "invalid-token": {
                    "summary": "JWT signature / structure invalid",
                    "value": {
                      "error": "invalid token"
                    }
                  },
                  "invalid-subject": {
                    "summary": "JWT sub field unparseable",
                    "value": {
                      "error": "invalid token subject"
                    }
                  },
                  "webhook-signature": {
                    "summary": "Sumsub HMAC verification failed",
                    "value": {
                      "error": "invalid webhook signature or payload"
                    }
                  },
                  "averer-reverify": {
                    "summary": "Averer flagged a re-verification requirement",
                    "value": {
                      "error": "averer_reverify_required"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/protected/api-key"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/protected/api-key', {\n  method: 'GET',\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nurl = 'https://auth.yieldfabric.com/protected/api-key'\nresp = httpx.get(url)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/protected/unified": {
      "get": {
        "operationId": "protectedUnified",
        "summary": "Unified authentication probe",
        "description": "Accepts JWT, API key, or wallet signature; returns 200 on any valid credential.",
        "tags": [
          "Admin"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Authenticated"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/protected/unified"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/protected/unified', {\n  method: 'GET',\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nurl = 'https://auth.yieldfabric.com/protected/unified'\nresp = httpx.get(url)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/protected/hello": {
      "get": {
        "operationId": "protectedHello",
        "summary": "Hello-world authenticated endpoint",
        "description": "Smoke test used by automated tooling.",
        "tags": [
          "Admin"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Greeting",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/protected/hello \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/protected/hello', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/protected/hello'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/admin/master-key/rotate": {
      "post": {
        "operationId": "rotateMasterKey",
        "summary": "Rotate the master encryption key",
        "description": "Two-step. The first call generates a new master key, re-encrypts\nevery keypair under it (background job), and returns a job id.\nProgress is observable via the\n`/admin/emergency/reencryption/progress` endpoint. Until\nre-encryption completes, both the old and new master keys are\naccepted for decrypt.\n",
        "tags": [
          "Admin"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "202": {
            "description": "Rotation initiated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "job_id"
                  ],
                  "properties": {
                    "job_id": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/admin/master-key/rotate \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/admin/master-key/rotate', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/admin/master-key/rotate'\nresp = httpx.post(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/admin/master-key/backup": {
      "post": {
        "operationId": "backupMasterKey",
        "summary": "Create an encrypted backup of the master key",
        "description": "Returns the master key wrapped under a backup KEK supplied in the\nrequest. Used for cold-storage backups; the resulting blob is\nwhat `POST /admin/emergency/recovery` consumes.\n",
        "tags": [
          "Admin"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "backup_kek"
                ],
                "properties": {
                  "backup_kek": {
                    "type": "string",
                    "description": "Hex-encoded KEK."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Backup blob",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "backup_blob"
                  ],
                  "properties": {
                    "backup_blob": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/admin/master-key/backup \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"backup_kek\": \"<string>\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/admin/master-key/backup', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"backup_kek\": \"<string>\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/admin/master-key/backup'\nresp = httpx.post(url, headers=headers, json={\"backup_kek\": \"<string>\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/admin/master-key/status": {
      "get": {
        "operationId": "getMasterKeyStatus",
        "summary": "Get master-key health and rotation status",
        "tags": [
          "Admin"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "rotation_in_progress": {
                      "type": "boolean"
                    },
                    "last_rotated_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "key_version": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/admin/master-key/status \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/admin/master-key/status', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/admin/master-key/status'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/admin/master-key/emergency-recovery": {
      "post": {
        "operationId": "masterKeyEmergencyRecovery",
        "summary": "One-call emergency recovery (sets a new master key from a backup KEK)",
        "description": "Single-step convenience wrapper for incidents where the regular\nrotation flow can't be used (e.g. the in-memory master key was\nlost). Use `/admin/emergency/recovery` for the multi-step variant\nthat yields a progress-trackable re-encryption job.\n",
        "tags": [
          "Admin"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "backup_blob",
                  "backup_kek"
                ],
                "properties": {
                  "backup_blob": {
                    "type": "string"
                  },
                  "backup_kek": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recovery completed"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/admin/master-key/emergency-recovery \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"backup_blob\": \"<string>\",\n  \"backup_kek\": \"<string>\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/admin/master-key/emergency-recovery', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"backup_blob\": \"<string>\",\n    \"backup_kek\": \"<string>\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/admin/master-key/emergency-recovery'\nresp = httpx.post(url, headers=headers, json={\"backup_blob\": \"<string>\", \"backup_kek\": \"<string>\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/admin/emergency/recovery": {
      "post": {
        "operationId": "emergencyRecoveryFromBackup",
        "summary": "Multi-step emergency recovery from a backup blob",
        "description": "Begins recovery using a backup blob produced by\n`POST /admin/master-key/backup`. Returns a job id; the\nre-encryption sweep runs in the background and is observable via\n`/admin/emergency/reencryption/progress`.\n",
        "tags": [
          "Admin"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "backup_blob",
                  "backup_kek"
                ],
                "properties": {
                  "backup_blob": {
                    "type": "string"
                  },
                  "backup_kek": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Recovery job created"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/admin/emergency/recovery \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n  \"backup_blob\": \"<string>\",\n  \"backup_kek\": \"<string>\"\n}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/admin/emergency/recovery', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    \"backup_blob\": \"<string>\",\n    \"backup_kek\": \"<string>\"\n  }),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/admin/emergency/recovery'\nresp = httpx.post(url, headers=headers, json={\"backup_blob\": \"<string>\", \"backup_kek\": \"<string>\"})\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/admin/emergency/reencryption/initiate": {
      "post": {
        "operationId": "initiateSystemReencryption",
        "summary": "Initiate a system-wide re-encryption sweep",
        "description": "Used when the master key has rotated (via `POST /admin/master-key/rotate`)\nbut the background re-encryption job didn't start, or to resume after\na cancelled job. Picks up where the previous sweep left off.\n",
        "tags": [
          "Admin"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "202": {
            "description": "Sweep initiated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "job_id"
                  ],
                  "properties": {
                    "job_id": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/admin/emergency/reencryption/initiate \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/admin/emergency/reencryption/initiate', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/admin/emergency/reencryption/initiate'\nresp = httpx.post(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/admin/emergency/reencryption/progress": {
      "get": {
        "operationId": "getReencryptionProgress",
        "summary": "Get progress of an in-flight re-encryption sweep",
        "tags": [
          "Admin"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Progress",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "job_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "idle",
                        "in_progress",
                        "cancelled",
                        "completed"
                      ]
                    },
                    "total_keys": {
                      "type": "integer"
                    },
                    "processed_keys:{ type": "integer }",
                    "started_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "completed_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/admin/emergency/reencryption/progress \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/admin/emergency/reencryption/progress', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/admin/emergency/reencryption/progress'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/admin/emergency/reencryption/cancel": {
      "post": {
        "operationId": "cancelReencryption",
        "summary": "Cancel an in-flight re-encryption sweep",
        "description": "Stops the background job. Keys that were already re-encrypted\nstay re-encrypted; the remaining keys can be picked up with a\nfresh `/admin/emergency/reencryption/initiate` call.\n",
        "tags": [
          "Admin"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "204": {
            "description": "Cancellation accepted"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/admin/emergency/reencryption/cancel \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/admin/emergency/reencryption/cancel', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/admin/emergency/reencryption/cancel'\nresp = httpx.post(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/graphql/identity": {
      "post": {
        "operationId": "identityGraphQL",
        "summary": "Federated identity subgraph",
        "description": "GraphQL endpoint behind the JWT middleware. Apollo Router's\ncompose step fetches the SDL from `/graphql/identity/sdl`\n(unauthenticated) and the data endpoint at this path\n(JWT-gated). Service tokens with audience `payments-service` and\nstrict direct user sessions are accepted. Delegated/provenance,\nMCP/impersonation, and password-change-only user credentials are\nrejected because this targetless identity surface cannot safely\nconsume group-scoped authority.\n\nThe federation key types this subgraph exposes are intentionally\nnarrow — only the identity fields downstream services need to\nstitch in (e.g. entity wallets for `Account`, `Entity`, etc.).\nThe full schema is in `schema.graphql` (federated, regenerated\nvia `tools/introspect.sh`).\n",
        "tags": [
          "GraphQL"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Common shape for mutation responses.",
                "required": [
                  "success"
                ],
                "properties": {
                  "success": {
                    "type": "boolean"
                  },
                  "message": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL response (success or `errors[]`)"
          },
          "401": {
            "description": "Missing, malformed, expired, or wrong-audience JWT (user or service).\nIncludes invalid API keys on the exchange endpoints, failed wallet\nsignatures on the signin endpoints, and failed Sumsub HMAC on the\nwebhook.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
                  "properties": {
                    "error": {
                      "type": "string",
                      "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                    }
                  }
                },
                "examples": {
                  "no-token": {
                    "summary": "Missing Authorization header",
                    "value": {
                      "error": "missing bearer token"
                    }
                  },
                  "invalid-token": {
                    "summary": "JWT signature / structure invalid",
                    "value": {
                      "error": "invalid token"
                    }
                  },
                  "invalid-subject": {
                    "summary": "JWT sub field unparseable",
                    "value": {
                      "error": "invalid token subject"
                    }
                  },
                  "webhook-signature": {
                    "summary": "Sumsub HMAC verification failed",
                    "value": {
                      "error": "invalid webhook signature or payload"
                    }
                  },
                  "averer-reverify": {
                    "summary": "Averer flagged a re-verification requirement",
                    "value": {
                      "error": "averer_reverify_required"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Delegated/provenance, MCP/impersonation, or password-change-only user credential."
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://auth.yieldfabric.com/graphql/identity \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '\"<see BaseResponse>\"'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/graphql/identity', {\n  method: 'POST',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'Content-Type': 'application/json' },\n  body: JSON.stringify(\"<see BaseResponse>\"),\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}', 'Content-Type': 'application/json'}\nurl = 'https://auth.yieldfabric.com/graphql/identity'\nresp = httpx.post(url, headers=headers, json=\"<see BaseResponse>\")\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      },
      "get": {
        "operationId": "identityGraphiQL",
        "summary": "GraphiQL playground (dev only)",
        "description": "Serves the GraphiQL HTML UI for interactive querying. Same JWT\nmiddleware as the POST handler — the browser must present a\nstrict direct-user bearer (typically via a manual cookie or a\none-shot dev token). Delegated and MCP/impersonation user tokens\nare rejected.\n",
        "tags": [
          "GraphQL"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "GraphiQL HTML",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "description": "Delegated/provenance, MCP/impersonation, or password-change-only user credential."
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/graphql/identity \\\n  -H \"Authorization: Bearer $TOKEN\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/graphql/identity', {\n  method: 'GET',\n  headers: { 'Authorization': `Bearer ${process.env.TOKEN}` },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nheaders = {'Authorization': f'Bearer {os.environ[\"TOKEN\"]}'}\nurl = 'https://auth.yieldfabric.com/graphql/identity'\nresp = httpx.get(url, headers=headers)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    },
    "/graphql/identity/sdl": {
      "get": {
        "operationId": "identitySDL",
        "summary": "Federated subgraph SDL (unauthenticated)",
        "description": "Returns the GraphQL SDL as `text/plain`. Unauthenticated so\nApollo Router's compose step can fetch it without a service\ntoken. The endpoint exposes only schema metadata — no data.\n",
        "tags": [
          "GraphQL"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "SDL",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X GET https://auth.yieldfabric.com/graphql/identity/sdl"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "const resp = await fetch('https://auth.yieldfabric.com/graphql/identity/sdl', {\n  method: 'GET',\n});\nconst data = await resp.json();"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import httpx, os\nurl = 'https://auth.yieldfabric.com/graphql/identity/sdl'\nresp = httpx.get(url)\nresp.raise_for_status()\ndata = resp.json()"
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "HS256-signed JWT in the `Authorization: Bearer <jwt>` header.\nHeader carries `kid: \"v1\"` (`JWT_KID_CURRENT`) — one signing secret\ntoday; the kid stamp is in place so a future multi-key validator\ncan route by it without re-minting existing tokens.\n\nTwo flavors:\n\n- **User tokens** — minted by login, refresh, signature, or\n  provider-exchange flows. Validated server-side by checking the\n  signature, expiry, and audience.\n- **Service tokens** — minted in-process by\n  `JwtService::mint_service_token` for inter-service REST calls\n  (auth → payments and similar). `sub = \"service:<name>\"`,\n  audience `[\"payments-service\"]`. The downstream service validates\n  **locally** against the shared `JWT_SECRET` — no auth round-trip.\n\n## Required claims (user tokens)\n\n| Claim | Type | Notes |\n|-------|------|-------|\n| `sub` | string | Entity ID (user_id or group_id) for user tokens; `\"service:<name>\"` for service tokens. |\n| `aud` | string[] | **User tokens**: `[\"default\", \"payments\"]` typical; extended audiences accepted by adjacent services include `\"yieldfabric\"`, `\"vault\"`. **Service tokens**: `[\"payments-service\"]` only (`SERVICE_AUDIENCE`). |\n| `exp` | int (unix) | Expiry. Access tokens default to **15 minutes**, refresh tokens to **30 days**. These are server defaults; clients shouldn't rely on exact values — always check `exp` on the token you receive. |\n| `iat` | int (unix) | Issued-at. |\n| `role` | string | UserRole — `SuperAdmin` / `Admin` / `Manager` / `Operator` / `User` / `Viewer` / `ApiClient`. |\n| `kind` | object | `{ \"kind\": \"user\" }` for user tokens; `{ \"kind\": \"service\", \"name\": \"<service>\" }` for service tokens. Derived from `sub` at validation time. |\n\n## Optional claims\n\n| Claim | Type | Notes |\n|-------|------|-------|\n| `account_address` | string | Default wallet address on the active chain. |\n| `default_chain_id` | string | Snapshot of the active chain at mint time — sealed for the JWT lifetime. |\n| `default_wallet_id` | string | Default wallet ID on that chain. |\n| `group_account_address` | string | Group's deployed account address; populated on delegation tokens. |\n| `acting_as` | string | **Delegation** — group_id the JWT is acting on behalf of. Resolved server-side via `AuthClaims::context_entity_id() = acting_as.unwrap_or(sub)`. This is a JWT claim, **not** an HTTP header. |\n| `delegation_scope` | string[] | Operations allowed under delegation. |\n| `delegation_token_id` | string | Audit trail for delegation. |\n| `delegation_path` | string[] | Ownership-derived delegation provenance; v1 is exactly `[source_group_id, target_group_id]`. |\n| `authority_source` | string | Nested authority reason. The only accepted value is `nft_membership`. |\n| `membership_edge` | object | Exact `{parent_group_id, child_group_id, chain_id, credential_contract, token_id}` NFT edge. |\n| `signer_key_id` | string | UUID of the exact account-compatible **human user** key selected for the child. |\n| `signer_address` | string | Canonical EVM address derived from the selected human key. |\n| `signer_scheme` | string | Exact contract signature profile; currently `account_ecdsa65_v1`. |\n| `signer_custody` | string | `custodial` uses Automatic; `external` uses Manual. |\n\nDelegation claims are consumed only by explicitly target-bound group\noperations. Personal-user, credential-management, Identity GraphQL,\nand platform-global routes require a direct user (or their documented\nservice boundary) and reject delegated/MCP credentials; the embedded\nhuman role never widens group scope.\n\nSee [guides/authentication.md](../guides/authentication.md) for the\nfull lifecycle, including service tokens, refresh-token rotation,\nand the `AuthClaims::for_user_entity` synthesis helper.\n"
      },
      "apiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "Authorization",
        "description": "API-key authentication — an alternative to JWT for non-interactive\ncallers (cron jobs, server-side integrations). Header shape is\n`Authorization: Bearer <api_key>` — the key itself is presented in\nthe bearer slot, and auth differentiates user-JWT vs API-key by\ndecode-then-fallback at the handler boundary.\n\nKeys are minted by `POST /auth/api-key/generate` (returns the\nplaintext exactly once — store it securely). The matching record\nin the `api_keys` table stores only the hash. See\n[guides/api-keys.md](../guides/api-keys.md) for rotation,\nrevocation, and scope.\n"
      },
      "signatureAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Signature",
        "description": "**Pseudo-scheme** — wallet-signature auth is not an HTTP-level\nbearer scheme. Sign-in endpoints (`POST /auth/signature`,\n`POST /auth/signature/signin`, `POST /auth/{provider}/exchange`,\n`POST /auth/webauthn/exchange`) carry the signature inside the\nJSON request body (`public_key` + `signature` + `message` +\n`timestamp` or provider-equivalent shape).\n\nDocumented here for spec completeness only; tooling that consumes\nthis scheme should treat it as a marker that the operation has its\nown inner-payload signature verification, not an actual HTTP\ncredential.\n\nSee [guides/signatures.md](../guides/signatures.md) and\n[guides/identity-providers.md](../guides/identity-providers.md).\n"
      }
    },
    "parameters": {
      "UserId": {
        "name": "user_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "description": "User UUID."
      },
      "GroupId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "description": "Group UUID. (Auth uses `{id}` for group paths and `{user_id}` for user paths — kept distinct so the URL itself reads as the entity kind.)"
      },
      "KeyId": {
        "name": "key_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "description": "Key UUID (signature key or API key, depending on the route)."
      },
      "PermissionName": {
        "name": "permission",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "description": "Granular permission grant. Roles ship with a default permission set\n(see `permissions_management.rs::default_permissions_for_role`);\n`POST /auth/users/{user_id}/permissions` overrides per-user.\n",
          "enum": [
            "CreateUser",
            "ReadUser",
            "UpdateUser",
            "DeleteUser",
            "UsersManage",
            "CreateEntity",
            "ReadEntity",
            "UpdateEntity",
            "DeleteEntity",
            "SystemConfig",
            "ViewLogs",
            "ManageRoles",
            "ApiRead",
            "ApiWrite",
            "ApiAdmin",
            "BulkOperations",
            "CreateGroup",
            "ReadGroup",
            "UpdateGroup",
            "DeleteGroup",
            "ManageGroupMembers",
            "ManageGroupPermissions",
            "ManageGroupOwners",
            "ManageGroupEntityScope",
            "CreateDelegationToken",
            "ViewDelegationTokens",
            "RevokeDelegationToken",
            "CryptoOperations",
            "PolicyExecution",
            "ManageCompliance",
            "LlmInference"
          ]
        },
        "description": "Permission identifier (e.g. `CreateUser`, `ApiRead`, `CryptoOperations`)."
      },
      "ContactId": {
        "name": "contact_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "Hashed contact identifier (typically `keccak256(email_or_group_name)`); used by `/api/v1/public-key/{contact_id}` for ECIES recipient lookup."
      },
      "EntityType": {
        "name": "entity_type",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "enum": [
            "user",
            "group"
          ]
        },
        "description": "Activatable identity kind for the `/entities/**` routes."
      },
      "EntityId": {
        "name": "entity_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "description": "Entity UUID for the `/entities/**` routes."
      },
      "ProviderName": {
        "name": "provider",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "enum": [
            "averer",
            "metamask",
            "dynamic",
            "email",
            "webauthn"
          ]
        },
        "description": "Identity-provider id. The full enum is shown above; the set of\nproviders actually mounted depends on this deployment's operator\nconfig. Clients should call `GET /auth/providers/config` at boot\nto discover which providers are available.\n"
      },
      "DelegationTokenId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "description": "Delegation token UUID."
      },
      "PaginationLimit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 1000,
          "default": 100
        },
        "description": "Max items to return."
      },
      "PaginationOffset": {
        "name": "offset",
        "in": "query",
        "required": false,
        "schema": {
          "type": "integer",
          "minimum": 0,
          "default": 0
        },
        "description": "Items to skip before returning."
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Malformed request, invalid field, or business-rule violation.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "error"
              ],
              "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
              "properties": {
                "error": {
                  "type": "string",
                  "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                }
              }
            },
            "examples": {
              "invalid-email": {
                "summary": "Email format check failed",
                "value": {
                  "error": "invalid email format"
                }
              },
              "invalid-uuid": {
                "summary": "Path parameter not a UUID",
                "value": {
                  "error": "user_id must be a UUID"
                }
              },
              "lockdown-state": {
                "summary": "Lockdown flow state mismatch",
                "value": {
                  "error": "no_active_lockdown"
                }
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing, malformed, expired, or wrong-audience JWT (user or service).\nIncludes invalid API keys on the exchange endpoints, failed wallet\nsignatures on the signin endpoints, and failed Sumsub HMAC on the\nwebhook.\n",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "error"
              ],
              "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
              "properties": {
                "error": {
                  "type": "string",
                  "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                }
              }
            },
            "examples": {
              "no-token": {
                "summary": "Missing Authorization header",
                "value": {
                  "error": "missing bearer token"
                }
              },
              "invalid-token": {
                "summary": "JWT signature / structure invalid",
                "value": {
                  "error": "invalid token"
                }
              },
              "invalid-subject": {
                "summary": "JWT sub field unparseable",
                "value": {
                  "error": "invalid token subject"
                }
              },
              "webhook-signature": {
                "summary": "Sumsub HMAC verification failed",
                "value": {
                  "error": "invalid webhook signature or payload"
                }
              },
              "averer-reverify": {
                "summary": "Averer flagged a re-verification requirement",
                "value": {
                  "error": "averer_reverify_required"
                }
              }
            }
          }
        }
      },
      "Forbidden": {
        "description": "JWT valid (signature + audience accepted) but the caller is not\nauthorised for this operation. Examples: `role` lacks the required\npermission; trying to act on another user's resource without\nSuperAdmin; trying to use a delegation token outside its\n`delegation_scope`.\n",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "error"
              ],
              "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
              "properties": {
                "error": {
                  "type": "string",
                  "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                }
              }
            },
            "examples": {
              "permission-required": {
                "summary": "Caller lacks the permission the handler requires",
                "value": {
                  "error": "permission 'ManageGroupMembers' required"
                }
              },
              "not-account-member": {
                "summary": "Caller is not a member or NFT-owner of the group",
                "value": {
                  "error": "not a member of group"
                }
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found (user, group, key, delegation token, wallet, provider).",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "error"
              ],
              "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
              "properties": {
                "error": {
                  "type": "string",
                  "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                }
              }
            },
            "examples": {
              "keypair": {
                "summary": "Keypair UUID not found",
                "value": {
                  "error": "keypair not found"
                }
              },
              "kyc-provider": {
                "summary": "Requested KYC provider not enabled in yaml",
                "value": {
                  "error": "kyc provider not enabled"
                }
              }
            }
          }
        }
      },
      "Conflict": {
        "description": "Request would violate a uniqueness or state invariant. Examples:\ncreating a user with an email that already exists; submitting a\nlockdown request while one is already pending; cancelling a\nlockdown that has already been cancelled.\n",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "error"
              ],
              "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
              "properties": {
                "error": {
                  "type": "string",
                  "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                }
              }
            },
            "examples": {
              "lockdown-already-cancelled": {
                "value": {
                  "error": "lockdown_already_cancelled"
                }
              },
              "cooldown-not-elapsed": {
                "value": {
                  "error": "cooldown_not_elapsed"
                }
              }
            }
          }
        }
      },
      "ChainAccountRequired": {
        "description": "The exact user/group has no smart account on the JWT-selected chain.\nThe handler checked this before durable mutation or MQ handoff. Activate\nthe returned entity/chain, refresh the same actor session, and retry once.\n",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "error",
                "code",
                "entity_kind",
                "entity_id",
                "chain_id"
              ],
              "properties": {
                "error": {
                  "type": "string"
                },
                "code": {
                  "type": "string",
                  "const": "CHAIN_ACCOUNT_REQUIRED"
                },
                "entity_kind": {
                  "type": "string",
                  "enum": [
                    "user",
                    "group"
                  ]
                },
                "entity_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "chain_id": {
                  "type": "string"
                },
                "activation_url": {
                  "type": "string",
                  "description": "Exact recovery resource when the handler can name it."
                },
                "operation": {
                  "type": "string",
                  "description": "Stable operation label for diagnostics; clients route on `code` and the entity/chain fields."
                }
              }
            }
          }
        }
      },
      "RateLimited": {
        "description": "An endpoint-specific hard request/attempt budget was exhausted.\nShapes vary: some handlers return only the flat error body, while\nlifecycle endpoints may add a stable `code` and\n`retry_after_seconds`. `/auth/login` is different: consecutive\nfailures impose a progressive delay but retain the normal `401`\nresponse rather than returning 429.\n",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "error"
              ],
              "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
              "properties": {
                "error": {
                  "type": "string",
                  "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                }
              }
            }
          }
        }
      },
      "InternalError": {
        "description": "Unexpected server error. Treat as transient; safe to retry.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "error"
              ],
              "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
              "properties": {
                "error": {
                  "type": "string",
                  "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
                }
              }
            },
            "examples": {
              "internal": {
                "value": {
                  "error": "internal error"
                }
              },
              "load-failed": {
                "summary": "DB load error",
                "value": {
                  "error": "load failed"
                }
              },
              "persist-failed": {
                "summary": "DB persist error",
                "value": {
                  "error": "persist failed"
                }
              }
            }
          }
        }
      }
    },
    "schemas": {
      "ErrorBody": {
        "type": "object",
        "required": [
          "error"
        ],
        "description": "Auth's ordinary flat error shape: one human-readable `error` field.\nThere is no nested `code`/`message`/`details` envelope.\nResource-lifecycle gates may instead reference a dedicated flat schema\nwith additional top-level recovery fields, such as\n`auth.yaml#/ChainAccountRequiredError`.\n",
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable error message. Examples from real handler code:\n`\"missing bearer token\"`, `\"invalid token\"`, `\"kyc provider not\nenabled\"`, `\"invalid email format\"`, `\"keypair not found\"`,\n`\"invalid webhook signature or payload\"`,\n`\"cooldown_not_elapsed\"`, `\"no_active_lockdown\"`,\n`\"averer_reverify_required\"`.\n\nSome endpoints surface a snake_case enum-like string (the\nlockdown / Averer / KYC paths); others use sentence-style\nprose. The string is **not** a stable machine-readable\nidentifier — match on **HTTP status code** for ordinary errors.\nOnly documented lifecycle schemas define stable body codes. See\n[guides/errors.md](../../guides/errors.md).\n"
          }
        }
      },
      "BaseResponse": {
        "type": "object",
        "description": "Common shape for mutation responses.",
        "required": [
          "success"
        ],
        "properties": {
          "success": {
            "type": "boolean"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "PaginationInput": {
        "type": "object",
        "properties": {
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1000,
            "default": 100
          },
          "offset": {
            "type": "integer",
            "minimum": 0,
            "default": 0
          }
        }
      },
      "UserRole": {
        "type": "string",
        "description": "Built-in role assigned to every user. Role + Permission grants make\nup the RBAC matrix; see `auth.roles` in `config/system.yaml` for\nrole overrides. SuperAdmin always has every permission.\n",
        "enum": [
          "SuperAdmin",
          "Admin",
          "Manager",
          "Operator",
          "User",
          "Viewer",
          "ApiClient"
        ]
      },
      "Permission": {
        "type": "string",
        "description": "Granular permission grant. Roles ship with a default permission set\n(see `permissions_management.rs::default_permissions_for_role`);\n`POST /auth/users/{user_id}/permissions` overrides per-user.\n",
        "enum": [
          "CreateUser",
          "ReadUser",
          "UpdateUser",
          "DeleteUser",
          "UsersManage",
          "CreateEntity",
          "ReadEntity",
          "UpdateEntity",
          "DeleteEntity",
          "SystemConfig",
          "ViewLogs",
          "ManageRoles",
          "ApiRead",
          "ApiWrite",
          "ApiAdmin",
          "BulkOperations",
          "CreateGroup",
          "ReadGroup",
          "UpdateGroup",
          "DeleteGroup",
          "ManageGroupMembers",
          "ManageGroupPermissions",
          "ManageGroupOwners",
          "ManageGroupEntityScope",
          "CreateDelegationToken",
          "ViewDelegationTokens",
          "RevokeDelegationToken",
          "CryptoOperations",
          "PolicyExecution",
          "ManageCompliance",
          "LlmInference"
        ]
      },
      "OwnedGroupOperationalPermission": {
        "type": "string",
        "description": "Capability that may survive into an A→B ownership-derived child after\nintersection with the requested and parent scopes. Administrative group,\nmembership, owner, account-topology, credential, entity-scope, and\ndelegation-management capabilities are deliberately excluded.\n",
        "enum": [
          "CreateEntity",
          "ReadEntity",
          "UpdateEntity",
          "DeleteEntity",
          "ApiRead",
          "ApiWrite",
          "BulkOperations",
          "ReadGroup",
          "CryptoOperations",
          "LlmInference"
        ]
      },
      "ProtectedResponse": {
        "type": "object",
        "required": [
          "message",
          "user_id",
          "role"
        ],
        "description": "Validated context projection returned by `/protected/jwt`; this is not\nthe raw JWT claims object. Service-token responses use\n`user_id: service:<name>` and leave user/group wallet fields null.\nOwnership-delegation provenance is copied from the signed bearer.\n",
        "properties": {
          "message": {
            "type": "string"
          },
          "user_id": {
            "type": "string",
            "description": "User UUID for user tokens; `service:<name>` for service tokens."
          },
          "role": {
            "type": "string",
            "description": "Built-in role assigned to every user. Role + Permission grants make\nup the RBAC matrix; see `auth.roles` in `config/system.yaml` for\nrole overrides. SuperAdmin always has every permission.\n",
            "enum": [
              "SuperAdmin",
              "Admin",
              "Manager",
              "Operator",
              "User",
              "Viewer",
              "ApiClient"
            ]
          },
          "account_address": {
            "type": [
              "string",
              "null"
            ],
            "description": "User's default account on `default_chain_id`; null until that chain is explicitly activated."
          },
          "group_account_address": {
            "type": [
              "string",
              "null"
            ],
            "description": "Group's deployed account address (delegation tokens only)."
          },
          "acting_as": {
            "type": [
              "string",
              "null"
            ],
            "description": "Group ID when acting on behalf of a group."
          },
          "delegation_scope": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string",
              "description": "Capability that may be scoped to one `acting_as` group. Delegation\nendpoints reject the entire request with `400` when a value is\nunknown, personal-user authority, or platform-global authority;\nthey do not silently drop unsupported values. `PolicyExecution` is\nvalid for direct policy-member delegation, but the A→B owned-group\nexchange rejects it. The ownership exchange further attenuates the\nremaining valid request to `OwnedGroupOperationalPermission`.\n",
              "enum": [
                "CreateEntity",
                "ReadEntity",
                "UpdateEntity",
                "DeleteEntity",
                "ApiRead",
                "ApiWrite",
                "BulkOperations",
                "ReadGroup",
                "UpdateGroup",
                "DeleteGroup",
                "ManageGroupMembers",
                "ManageGroupPermissions",
                "ManageGroupOwners",
                "ManageGroupEntityScope",
                "CreateDelegationToken",
                "ViewDelegationTokens",
                "RevokeDelegationToken",
                "CryptoOperations",
                "PolicyExecution",
                "LlmInference"
              ]
            }
          },
          "delegation_token_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "delegation_path": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Ordered `[source_group_id, target_group_id]` path for a one-hop ownership delegation."
          },
          "authority_source": {
            "type": [
              "string",
              "null"
            ],
            "description": "Signed authority reason. Auth recognizes only `nft_membership` with complete edge and human-signer provenance; unknown, partial, or malformed provenance fails closed."
          },
          "membership_edge": {
            "oneOf": [
              {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "parent_group_id",
                  "child_group_id",
                  "chain_id",
                  "credential_contract",
                  "token_id"
                ],
                "properties": {
                  "parent_group_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "child_group_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "chain_id": {
                    "type": "string"
                  },
                  "credential_contract": {
                    "type": "string",
                    "pattern": "^0x[0-9a-f]{40}$"
                  },
                  "token_id": {
                    "type": "string",
                    "pattern": "^[1-9][0-9]*$",
                    "description": "Canonical non-zero decimal uint256."
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "signer_key_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Exact account-compatible human user key selected for nested execution."
          },
          "signer_address": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^0x[0-9a-f]{40}$"
          },
          "signer_scheme": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "account_ecdsa65_v1",
              null
            ]
          },
          "signer_custody": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "custodial",
              "external",
              null
            ],
            "description": "Custodial keys use Automatic execution; external keys use Manual execution."
          },
          "default_wallet_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Active entity wallet on `default_chain_id`; null for a walletless personal session."
          },
          "default_chain_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Required, enabled chain for user tokens; null only for modeless service tokens."
          },
          "permissions": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            },
            "description": "Effective post-gate permissions; delegation validators project delegation_scope here."
          },
          "billing_gate": {
            "type": [
              "string",
              "null"
            ],
            "description": "Denial reason annotation when the billing gate stripped capabilities."
          },
          "billing_gate_payer": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "self",
              "group",
              null
            ],
            "description": "Billing context evaluated by the mint-time gate."
          },
          "billing_gate_payer_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Exact paying group, present for healthy and gated group-paid sessions; null for personal/self pay."
          },
          "verification_profiles": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            },
            "description": "Verification-profile names satisfied by the user at mint time."
          },
          "billing_sponsor": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Validated sponsoring platform group for this session; attribution only."
          },
          "onboarding_gate": {
            "type": [
              "string",
              "null"
            ],
            "description": "Platform-onboarding denial annotation (`kyc_required` or `email_required`)."
          },
          "session_kind": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "personal",
              "connector",
              "mcp_signer",
              "delegated",
              "service",
              null
            ],
            "description": "The session kind auth computed for the validated bearer. `personal` is the only kind that may execute or confirm;\n`connector` (OAuth grant / connector API key / on-behalf-of token)\nmay read and propose and NEVER carries `CryptoOperations` /\n`PolicyExecution` (every connector mint path strips them);\n`mcp_signer` is the signer-bearing MCP credential from\n`/auth/mcp/login` / `/auth/mcp/generate-token` — pinned to one\ncustodial signing key, keeps `CryptoOperations`, never a direct\nsession, never admitted at `/mcp`, never exchangeable;\n`delegated` and `service` as today. Absent only from responses of\nolder auth deployments — consumers fail closed on absence.\n"
          },
          "allowed_chains": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            },
            "description": "Chains a connector session may run on (copied from its credential at mint, never widened on refresh). Absent = unrestricted, not a connector, or an older deployment."
          }
        }
      },
      "ClaimsKind": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "kind"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "const": "user"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "kind",
              "name"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "const": "service"
              },
              "name": {
                "type": "string",
                "description": "Service name minted into the token (e.g. `auth-internal`)."
              }
            }
          }
        ],
        "description": "Tagged union for user vs service tokens. Wire shape uses the serde\n`#[serde(tag = \"kind\", rename_all = \"lowercase\")]` representation\ndeclared in `yieldfabric-auth-client/src/claims.rs`. Derived from\n`sub` at validation time (`sub` starting with `service:` → service).\n"
      },
      "GroupType": {
        "type": "string",
        "description": "Group flavor. Drives default permissions, entity-scope hints, and\non-chain account naming.\n",
        "enum": [
          "Organization",
          "Team",
          "Project",
          "Custom"
        ]
      },
      "KeyType": {
        "type": "string",
        "description": "Stored-keypair purpose. `Signing` = secp256k1 used for EIP-191\nsignatures and account ownership; `Encryption` = secp256k1 used as\nan ECIES recipient endpoint; `Hybrid` = doubles as both (legacy\nemail-user shape); `External` = wallet-led signup, no private key\nheld server-side.\n",
        "enum": [
          "Signing",
          "Encryption",
          "Hybrid",
          "External"
        ]
      },
      "LoginResponse": {
        "type": "object",
        "required": [
          "token",
          "refresh_token",
          "token_type",
          "expires_in",
          "user"
        ],
        "description": "Returned by `POST /auth/login` and `POST /auth/login/with-services`.",
        "properties": {
          "token": {
            "type": "string",
            "description": "Short-lived access token (default 15 min)."
          },
          "refresh_token": {
            "type": "string",
            "description": "Long-lived refresh token (30 days, single-use, rotated on each refresh)."
          },
          "token_type": {
            "type": "string",
            "const": "Bearer"
          },
          "expires_in": {
            "type": "integer",
            "description": "Access-token lifetime in seconds."
          },
          "user": {
            "type": "object",
            "required": [
              "id",
              "email",
              "role",
              "is_active"
            ],
            "description": "User record shape returned by `/auth/users/me` and friends.\n`password_hash` is always returned as an empty string (never\nexposed). See `yieldfabric-auth/src/handlers/types.rs::UserInfo`.\n",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "email": {
                "type": "string",
                "format": "email"
              },
              "password_hash": {
                "type": "string",
                "description": "Always empty in responses."
              },
              "role": {
                "type": "string",
                "description": "Built-in role assigned to every user. Role + Permission grants make\nup the RBAC matrix; see `auth.roles` in `config/system.yaml` for\nrole overrides. SuperAdmin always has every permission.\n",
                "enum": [
                  "SuperAdmin",
                  "Admin",
                  "Manager",
                  "Operator",
                  "User",
                  "Viewer",
                  "ApiClient"
                ]
              },
              "is_active": {
                "type": "boolean"
              },
              "must_change_password": {
                "type": "boolean"
              },
              "password_changed_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "failed_login_count": {
                "type": "integer"
              },
              "last_failed_login": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "created_at": {
                "type": "string",
                "format": "date-time"
              },
              "updated_at": {
                "type": "string",
                "format": "date-time"
              },
              "is_external_signer": {
                "type": "boolean",
                "description": "Derived. **True** when the user has no active `KeyType: Signing`\nkeypair with `encrypted_private_key` — i.e. they sign exclusively\nvia an external wallet (Averer / Dynamic / MetaMask /\nWebAuthn passkey). The frontend trusts this flag verbatim and\nsets `requireManualSignature: true` on every payment mutation\nwhen it's true, so the backend routes the message into the\nmanual-sign drawer instead of attempting server-side signing.\nRecomputed on every call from a case-insensitive\n`LOWER(key_type) = 'signing'` SQL filter. See the architecture\ndoc for the full chain and the cache-invalidation gotcha.\n"
              },
              "live_connectors_enabled": {
                "type": "boolean",
                "description": "The person's LIVE-connector opt-in (`users.live_connectors_enabled`,\nagent surface spec §5.4). While false, none of their connector\ncredentials can be generated for, minted on, or refreshed onto a\nlive chain. Set via `PUT /auth/users/me/live-connectors`\n(direct session + live verification floor). Returned by\n`GET /auth/users/me` only; best-effort read.\n"
              }
            }
          }
        }
      },
      "UserProfile": {
        "type": "object",
        "required": [
          "id",
          "email",
          "role",
          "is_active"
        ],
        "description": "User record shape returned by `/auth/users/me` and friends.\n`password_hash` is always returned as an empty string (never\nexposed). See `yieldfabric-auth/src/handlers/types.rs::UserInfo`.\n",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "password_hash": {
            "type": "string",
            "description": "Always empty in responses."
          },
          "role": {
            "type": "string",
            "description": "Built-in role assigned to every user. Role + Permission grants make\nup the RBAC matrix; see `auth.roles` in `config/system.yaml` for\nrole overrides. SuperAdmin always has every permission.\n",
            "enum": [
              "SuperAdmin",
              "Admin",
              "Manager",
              "Operator",
              "User",
              "Viewer",
              "ApiClient"
            ]
          },
          "is_active": {
            "type": "boolean"
          },
          "must_change_password": {
            "type": "boolean"
          },
          "password_changed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "failed_login_count": {
            "type": "integer"
          },
          "last_failed_login": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "is_external_signer": {
            "type": "boolean",
            "description": "Derived. **True** when the user has no active `KeyType: Signing`\nkeypair with `encrypted_private_key` — i.e. they sign exclusively\nvia an external wallet (Averer / Dynamic / MetaMask /\nWebAuthn passkey). The frontend trusts this flag verbatim and\nsets `requireManualSignature: true` on every payment mutation\nwhen it's true, so the backend routes the message into the\nmanual-sign drawer instead of attempting server-side signing.\nRecomputed on every call from a case-insensitive\n`LOWER(key_type) = 'signing'` SQL filter. See the architecture\ndoc for the full chain and the cache-invalidation gotcha.\n"
          },
          "live_connectors_enabled": {
            "type": "boolean",
            "description": "The person's LIVE-connector opt-in (`users.live_connectors_enabled`,\nagent surface spec §5.4). While false, none of their connector\ncredentials can be generated for, minted on, or refreshed onto a\nlive chain. Set via `PUT /auth/users/me/live-connectors`\n(direct session + live verification floor). Returned by\n`GET /auth/users/me` only; best-effort read.\n"
          }
        }
      },
      "GroupProfile": {
        "type": "object",
        "required": [
          "id",
          "name",
          "description",
          "group_type",
          "is_active",
          "created_at",
          "updated_at",
          "role",
          "account_address",
          "account_chain_id",
          "entity_verification_profile",
          "entity_verification_status"
        ],
        "description": "Group record shape returned by `/auth/groups` and friends. Account fields\nare explicitly chain-qualified: a null `account_address` means the group\nhas no default wallet on `account_chain_id`.\n",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "group_type": {
            "type": "string",
            "enum": [
              "organization",
              "team",
              "project",
              "custom"
            ]
          },
          "role": {
            "type": [
              "string",
              "null"
            ],
            "description": "Current caller's off-chain membership role when the list endpoint supplies it."
          },
          "account_address": {
            "type": [
              "string",
              "null"
            ],
            "description": "Default account address on `account_chain_id`, or null when not activated there."
          },
          "account_chain_id": {
            "type": "string",
            "description": "Chain against which `account_address` was resolved."
          },
          "account_activation": {
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "status",
                  "chain_id",
                  "wallet_id",
                  "account_address",
                  "deployed"
                ],
                "properties": {
                  "status": {
                    "type": "string",
                    "const": "ready"
                  },
                  "chain_id": {
                    "type": "string"
                  },
                  "wallet_id": {
                    "type": "string"
                  },
                  "account_address": {
                    "type": "string",
                    "pattern": "^0x[0-9a-fA-F]{40}$"
                  },
                  "deployed": {
                    "type": "boolean",
                    "description": "True only when this request completed a new deployment."
                  }
                }
              },
              {
                "type": "object",
                "required": [
                  "status",
                  "chain_id",
                  "message_id"
                ],
                "properties": {
                  "status": {
                    "type": "string",
                    "const": "provisioning"
                  },
                  "chain_id": {
                    "type": "string"
                  },
                  "message_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                }
              },
              {
                "type": "object",
                "required": [
                  "status",
                  "chain_id",
                  "message_id"
                ],
                "properties": {
                  "status": {
                    "type": "string",
                    "const": "pending_signature"
                  },
                  "chain_id": {
                    "type": "string"
                  },
                  "message_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                }
              },
              {
                "type": "object",
                "required": [
                  "status",
                  "chain_id",
                  "error"
                ],
                "properties": {
                  "status": {
                    "type": "string",
                    "const": "failed_retryable"
                  },
                  "chain_id": {
                    "type": "string"
                  },
                  "error": {
                    "type": "string"
                  }
                }
              }
            ],
            "discriminator": {
              "propertyName": "status"
            }
          },
          "billing_policy": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "admin_pays",
              "member_pays",
              null
            ],
            "description": "Present on create when a declared policy committed atomically with the\ngroup. Omitted when no policy was declared or billing is disabled.\n"
          },
          "entity_verification_profile": {
            "type": [
              "string",
              "null"
            ],
            "description": "Bound entity/KYB verification profile, or null when the group is not declared as a business."
          },
          "entity_verification_status": {
            "type": "string",
            "enum": [
              "not_required",
              "unverified",
              "pending",
              "verified",
              "rejected",
              "expired"
            ],
            "description": "Derived current group KYB posture; always serialized."
          },
          "is_active": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SignatureKey": {
        "type": "object",
        "required": [
          "id",
          "user_id",
          "public_key",
          "key_type",
          "is_active",
          "created_at"
        ],
        "description": "Registered public key in `signature_keys`. Used for signature-based auth and account ownership.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "user_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "public_key": {
            "type": "string",
            "description": "Uncompressed or compressed hex, with or without 0x prefix."
          },
          "key_type": {
            "type": "string",
            "description": "Stored-keypair purpose. `Signing` = secp256k1 used for EIP-191\nsignatures and account ownership; `Encryption` = secp256k1 used as\nan ECIES recipient endpoint; `Hybrid` = doubles as both (legacy\nemail-user shape); `External` = wallet-led signup, no private key\nheld server-side.\n",
            "enum": [
              "Signing",
              "Encryption",
              "Hybrid",
              "External"
            ]
          },
          "address": {
            "type": [
              "string",
              "null"
            ],
            "description": "Derived EVM address (lowercased)."
          },
          "is_active": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "metadata": {
            "type": "object"
          }
        }
      },
      "ApiKeyInfo": {
        "type": "object",
        "required": [
          "id",
          "user_id",
          "name",
          "is_active",
          "created_at"
        ],
        "description": "API-key metadata as returned by `GET /auth/api-keys`. The plaintext key is returned **only** on creation and stored hashed.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "user_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "is_active": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_used_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "revoked_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "kind": {
            "type": "string",
            "enum": [
              "personal",
              "connector"
            ],
            "description": "Credential kind decided at mint (`POST /auth/api-key/generate {kind}`)."
          },
          "allowed_chains": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            },
            "description": "Connector keys only — the chains their sessions may run on; absent = unrestricted or a personal key."
          }
        }
      }
    }
  },
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "x-yf-service": "auth"
}