{
  "openapi": "3.1.0",
  "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
  "info": {
    "title": "Speedle Cloud API",
    "version": "0.4.0",
    "description": "Production HTTP contract for cloud.speedle.io. Session-protected console routes use exact Origin and X-SC-CSRF checks; ADS and policy-management routes use scoped Bearer API tokens. Test-provider routes are intentionally excluded because production never mounts them. Authentication responses, credentials and provider tokens are never logged or cached."
  },
  "servers": [
    {
      "url": "https://cloud.speedle.io",
      "description": "production"
    }
  ],
  "paths": {
    "/api/config": {
      "get": {
        "operationId": "getPublicConfig",
        "summary": "Read public runtime sign-in availability",
        "description": "Public no-store response containing only signInEnabled. SIGN_IN_ENABLED is a per-stage TOML boolean, default false when absent; invalid values fail closed. Issue #142 enables beta and gamma; prod stays disabled pending #62. This exact sign-in flag difference is allow-listed in gamma/prod parity; issue #228 separately limits test providers to beta/gamma and never prod. Disabled /api/auth/{provider}/* (including enabled beta/gamma test-provider routes) return 503 sign_in_disabled before provider calls or mutations; session reads, logout and account deletion remain available. Disabled test routes remain absent (404).",
        "security": [],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicConfigResponse"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/Response1"
          }
        }
      }
    },
    "/api/auth/{provider}/start": {
      "get": {
        "operationId": "startProviderSignIn",
        "summary": "Start GitHub OAuth or Google OIDC",
        "description": "Reject unknown providers/parameters before external requests. Fixed authorization and token endpoints, callback URI and scopes. Use server-selected intent signin. Generate independent 32-byte state, PKCE verifier and nonce; sign the provider-bound OAuth cookie for 10 minutes. Clear this browser's prior completion cookie for the provider/context and invalidate its authenticated one-time record; never let a previous link/reauth completion survive a new flow. New sign-in sessions record auth_time at server callback time; creating a new account records it at successful confirmation. PKCE S256 for both; Google additionally sends nonce. No arbitrary returnTo/redirect parameters. Each provider start is limited to 10/IP/minute, within the general 20 auth requests/IP/minute. Disabled runtime switch returns 503 sign_in_disabled before provider calls or mutations.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/provider"
          }
        ],
        "x-query-schema": {
          "$ref": "#/components/schemas/OAuthStartQuery"
        },
        "responses": {
          "302": {
            "description": "Top-level navigation to a fixed destination; no response body.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              },
              "Location": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthStartLocation"
                }
              },
              "Set-Cookie": {
                "description": "Set __Host-sc_{provider}_oauth=<signed provider/intent/sessionBinding/state/PKCE/nonce/issuedAt/expiresAt>; Max-Age=600; Secure; HttpOnly; SameSite=Lax; Path=/; choose the cookie name for the path provider. No Domain attribute. Bind provider, intent and (for link/reauth) initiating session/user under a purpose-specific signature; test flows use isolated cookie names and signing contexts; server checks expiry, never relying on browser expiry.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Response2"
          },
          "429": {
            "$ref": "#/components/responses/Response3"
          },
          "500": {
            "$ref": "#/components/responses/Response1"
          },
          "503": {
            "$ref": "#/components/responses/Response4"
          }
        }
      }
    },
    "/api/auth/{provider}/callback": {
      "get": {
        "operationId": "finishProviderSignIn",
        "summary": "Validate the callback and resolve the account",
        "description": "For real success and denial, select the strict transport schema by path provider and require decoded iss exactly https://github.com/login/oauth for GitHub or https://accounts.google.com for Google. Reject missing, duplicate or mismatching issuer without URL normalization; the legacy Google ID-token issuer is not a callback issuer. Google additionally validates optional hd as a bounded ASCII DNS hostname; hd has no authority. Remove only the validated issuer and Google scope/authuser/prompt/hd before strict canonical validation. Audit of GitHub's documented authorization-code success/error responses requires no additional auxiliary fields. Reject all other unknown fields and every duplicate key before object construction. Beta/gamma fake callbacks retain the issuer-free canonical protocol, without real-provider metadata. Validate cookie signature/provider/server-side expiry and constant-time state, then exchange the single-use code with PKCE. Google validates iss/aud/azp/exp/iat/nonce and email_verified; GitHub requires verified primary email. Provider tokens stay in callback memory only. Clear OAuth cookie on success AND failure. Dispatch by signed intent BEFORE sign-in account resolution; link/reauth must never fall through to signin. For intent signin, linked active identity: set session and redirect /. Unknown identity with registered email: refuse with 409 email_in_use and the account sign-in providers, instruct sign-in then connect in Settings; never auto-link or create a second account. New email: redirect to fixed confirmation page without creating an account. For intent link or intent reauth, resolve the active initiating session by the signed cookie binding (SameSite=Strict may omit the session cookie); refuse expired/revoked or mismatched bindings with 401 unauthorized, including a different presented session. Reauth identity must already belong to that user or return 409 identity_conflict. Do not link identities, rotate/set a session, refresh auth_time or emit a successful link/reauth audit on this GET. Instead issue an independent random 32-byte, one-time completion credential in a signed provider-specific Strict HttpOnly cookie. Its purpose-isolated D1 HMAC record and signed payload bind provider, real/test context, original intent, initiating session hash/user, verified identity and server callback authentication time. Store no provider tokens. Expire no later than the original OAuth flow deadline (at most 10 minutes from start), without renewal. Redirect to the fixed completion page; only POST /api/auth/{provider}/complete may finish link/reauth after the original Strict session and CSRF are proved. Google reauth sends prompt=login and max_age=0; GitHub cannot force authentication, so a fresh OAuth round trip is accepted with this limitation. Intent/provider/session bindings are signed and cannot be switched by query parameters; consume OAuth state once, including on terminal failure. Each provider callback is limited to 10/IP/minute, within general auth limits. Never put credentials in redirect URLs. For browser callbacks accepting text/html, redirect to OAuthBrowserLocation with a validated OAuthBrowserResult fragment for sign-in success or any failure (including rate limits and internal errors), retaining terminal cookie cleanup; test-provider route-absence/key gates keep their 404/401 responses without cookie mutation even for HTML requests. New-account confirmation keeps the fixed confirmation destination. Successful link/reauth callbacks, including JSON clients, instead redirect to the fixed provider completion page: /auth/{provider}/complete (test provider: /auth/test/{provider}/complete), with no query or fragment. Never report connected/reauthenticated before the completion POST succeeds. Provider denial with valid state is provider_denied; expired/invalid/replayed state is invalid_oauth_state. No provider text, codes, state or secrets enter the hand-off. Console erases query/fragment immediately, treats outcomes only as display hints, reads /api/me for authority and offers sign-in/Settings recovery. JSON clients retain the documented error statuses/envelopes. Disabled runtime switch returns 503 sign_in_disabled before provider calls or mutations.",
        "security": [
          {
            "githubOAuthCookie": []
          },
          {
            "googleOAuthCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/provider"
          },
          {
            "name": "code",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 2048,
              "pattern": "^\\S+$"
            },
            "description": "Select x-query-schema-by-provider using the path provider before validating raw transport, then validate the canonical OAuthCallbackQuery: exactly one of code/error and no duplicate keys. Reject unknown provider selectors; never fall back to another provider schema. Provider error text and URLs are untrusted; never render, follow or log."
          },
          {
            "name": "state",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$"
            },
            "description": "Select x-query-schema-by-provider using the path provider before validating raw transport, then validate the canonical OAuthCallbackQuery: exactly one of code/error and no duplicate keys. Reject unknown provider selectors; never fall back to another provider schema. Provider error text and URLs are untrusted; never render, follow or log."
          },
          {
            "name": "error",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[a-zA-Z0-9_]+$"
            },
            "description": "Select x-query-schema-by-provider using the path provider before validating raw transport, then validate the canonical OAuthCallbackQuery: exactly one of code/error and no duplicate keys. Reject unknown provider selectors; never fall back to another provider schema. Provider error text and URLs are untrusted; never render, follow or log."
          },
          {
            "name": "error_description",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 1024
            },
            "description": "Select x-query-schema-by-provider using the path provider before validating raw transport, then validate the canonical OAuthCallbackQuery: exactly one of code/error and no duplicate keys. Reject unknown provider selectors; never fall back to another provider schema. Provider error text and URLs are untrusted; never render, follow or log."
          },
          {
            "name": "error_uri",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 2048,
              "format": "uri"
            },
            "description": "Select x-query-schema-by-provider using the path provider before validating raw transport, then validate the canonical OAuthCallbackQuery: exactly one of code/error and no duplicate keys. Reject unknown provider selectors; never fall back to another provider schema. Provider error text and URLs are untrusted; never render, follow or log."
          },
          {
            "name": "iss",
            "in": "query",
            "required": true,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "const": "https://github.com/login/oauth"
                },
                {
                  "type": "string",
                  "const": "https://accounts.google.com"
                }
              ]
            },
            "x-required-for-providers": [
              "github",
              "google"
            ],
            "x-value-by-provider": {
              "github": "https://github.com/login/oauth",
              "google": "https://accounts.google.com"
            },
            "description": "Required on real success and denial for both path providers; forbidden on beta/gamma fake callbacks. Select x-query-schema-by-provider using the path provider, compare the decoded issuer exactly with that provider's constant, then remove it for canonical validation. No normalization or fallback to another provider/canonical schema; the two-value parameter schema alone cannot authorize either issuer for both providers."
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "x-allowed-for-providers": [
              "google"
            ],
            "description": "Google-only auxiliary metadata; validate with the selected raw transport schema, then discard before canonical validation. Not allowed for GitHub or beta/gamma fake callbacks. hd is a bounded ASCII DNS hostname with no authority; none of these values verifies identity or grants access."
          },
          {
            "name": "authuser",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "x-allowed-for-providers": [
              "google"
            ],
            "description": "Google-only auxiliary metadata; validate with the selected raw transport schema, then discard before canonical validation. Not allowed for GitHub or beta/gamma fake callbacks. hd is a bounded ASCII DNS hostname with no authority; none of these values verifies identity or grants access."
          },
          {
            "name": "prompt",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "x-allowed-for-providers": [
              "google"
            ],
            "description": "Google-only auxiliary metadata; validate with the selected raw transport schema, then discard before canonical validation. Not allowed for GitHub or beta/gamma fake callbacks. hd is a bounded ASCII DNS hostname with no authority; none of these values verifies identity or grants access."
          },
          {
            "name": "hd",
            "in": "query",
            "required": false,
            "schema": {
              "description": "Optional hosted-domain metadata: ASCII DNS hostname, at most 253 characters, labels at most 63 characters; no URL, email, wildcard or trailing dot. Discard after validation. Never grants authority, verifies email, selects an account or replaces ID-token claims.",
              "type": "string",
              "maxLength": 253,
              "allOf": [
                {
                  "pattern": "^(?=.{1,253}\\.?$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[-0-9a-zA-Z]{0,61}[0-9a-zA-Z])?)*\\.?$"
                },
                {
                  "pattern": "[A-Za-z0-9]$(?![\\s\\S])"
                }
              ]
            },
            "x-allowed-for-providers": [
              "google"
            ],
            "description": "Google-only auxiliary metadata; validate with the selected raw transport schema, then discard before canonical validation. Not allowed for GitHub or beta/gamma fake callbacks. hd is a bounded ASCII DNS hostname with no authority; none of these values verifies identity or grants access."
          }
        ],
        "x-query-schema-selector": {
          "in": "path",
          "name": "provider",
          "unknown": "reject"
        },
        "x-query-schema-by-provider": {
          "github": {
            "$ref": "#/components/schemas/GitHubOAuthCallbackTransport"
          },
          "google": {
            "$ref": "#/components/schemas/GoogleOAuthCallbackTransport"
          }
        },
        "x-canonical-query-schema": {
          "$ref": "#/components/schemas/OAuthCallbackQuery"
        },
        "responses": {
          "302": {
            "description": "Top-level navigation to a fixed destination; no response body.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              },
              "Location": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/OAuthBrowserLocation"
                    },
                    {
                      "$ref": "#/components/schemas/OAuthCallbackLocation"
                    }
                  ]
                },
                "examples": {
                  "example1": {
                    "value": "/"
                  },
                  "example2": {
                    "value": "/auth/github/confirm"
                  },
                  "example3": {
                    "value": "/auth/google/confirm"
                  },
                  "example4": {
                    "value": "/auth/github/complete"
                  },
                  "example5": {
                    "value": "/auth/google/complete"
                  }
                }
              },
              "Set-Cookie": {
                "description": "Always clear __Host-sc_{provider}_oauth=<signed provider/intent/sessionBinding/state/PKCE/nonce/issuedAt/expiresAt>; Max-Age=600; Secure; HttpOnly; SameSite=Lax; Path=/ with Max-Age=0. Only signin success sets __Host-sc_session=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA; Secure; HttpOnly; SameSite=Strict; Path=/. New-account confirmation sets __Host-sc_{provider}_pending=<signed one-time browser-bound create credential>; Max-Age=600; Secure; HttpOnly; SameSite=Strict; Path=/. For link/reauth set __Host-sc_{provider}_completion=<signed one-time completion credential bound to provider/context/intent/initiating session/user/verified identity/callback time/original expiry>; Max-Age=<remaining flow seconds, at most 600>; Secure; HttpOnly; SameSite=Strict; Path=/ after validating the initiating binding; this GET never links, sets/rotates a session or refreshes auth_time. Completion expiry cannot outlive the original flow deadline. Each cookie is a separate Set-Cookie field; never comma-join. No Domain attribute.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request, invalid_oauth_state or invalid_pending_credential; no credential/account details.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              },
              "Set-Cookie": {
                "$ref": "#/components/headers/Set-Cookie"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized: missing, expired, revoked or non-active account session.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              },
              "Set-Cookie": {
                "$ref": "#/components/headers/Set-Cookie"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "csrf_failed, verified_email_required or reauth_required as applicable.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              },
              "Set-Cookie": {
                "$ref": "#/components/headers/Set-Cookie"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "email_in_use, identity_conflict",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              },
              "Set-Cookie": {
                "$ref": "#/components/headers/Set-Cookie"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited; mandatory Retry-After. Auth/session routes are excluded from daily PMS/ADS quota and do not emit daily_quota_exceeded.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "Set-Cookie": {
                "$ref": "#/components/headers/Set-Cookie"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "internal_error: no secrets or upstream details.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              },
              "Set-Cookie": {
                "$ref": "#/components/headers/Set-Cookie"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "provider_unavailable: invalid or failed upstream provider response; offer another linked provider.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              },
              "Set-Cookie": {
                "$ref": "#/components/headers/Set-Cookie"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "sign_in_disabled when runtime sign-in is closed; provider_unavailable for temporary provider outage; quota_unavailable specifically when GET /api/me cannot read AccountQuota, with Retry-After: 30. Fail closed.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              },
              "Set-Cookie": {
                "$ref": "#/components/headers/Set-Cookie"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/{provider}/confirm": {
      "post": {
        "operationId": "confirmProviderRegistration",
        "summary": "Manually create a provider account",
        "description": "Require a signed provider-specific create credential bound to this browser and server-side one-time consumption state, expiring in 10 minutes. Never accept email, identity, token, redirect URI or credentials in the body/URL. Recheck active account and both identity uniqueness constraints atomically. No email is sent. Require create-purpose credential and explicit user click. If the email became registered, consume no registration proof as linking authorization: return 409 email_in_use with the account sign-in providers, requiring sign-in then connect in Settings. Never create a second account or auto-link. Create users.id as a ULID and users.email from the verified normalized provider email. Each provider confirm is limited to 10/IP/minute within general auth limits. Disabled runtime switch returns 503 sign_in_disabled before provider calls or mutations. Clear the pending cookie when consumed or terminally rejected; always validate provider matches the path. A cookie for the other provider never satisfies authorization.",
        "security": [
          {
            "githubPendingCookie": []
          },
          {
            "googlePendingCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/provider"
          },
          {
            "$ref": "#/components/parameters/Origin"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF"
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody1"
        },
        "responses": {
          "200": {
            "description": "Success.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              },
              "Set-Cookie": {
                "description": "Set/rotate the session; random ID only, never user/provider data. Sliding lifetime 7 days, absolute maximum 30 days; server enforces both. No Domain attribute.",
                "schema": {
                  "type": "string"
                },
                "example": "__Host-sc_session=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA; Secure; HttpOnly; SameSite=Strict; Path=/"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthSuccessResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Response2"
          },
          "403": {
            "$ref": "#/components/responses/Response5"
          },
          "409": {
            "description": "email_in_use, identity_conflict",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/Response6"
          },
          "429": {
            "$ref": "#/components/responses/Response3"
          },
          "500": {
            "$ref": "#/components/responses/Response1"
          },
          "503": {
            "$ref": "#/components/responses/Response4"
          }
        }
      }
    },
    "/api/auth/{provider}/connect": {
      "post": {
        "operationId": "connectProvider",
        "summary": "Connect a provider from Settings",
        "description": "Require active session and CSRF. Set intent link server-side; sign provider, state, PKCE, nonce, issuedAt/expiresAt and initiating session ID hash/user binding into the OAuth cookie. Callback must verify that session still exists and is active, even without a Strict session cookie, then stage completion without linking. Finish only through the same-origin completion POST with the exact initiating session and CSRF. Identity email may differ from users.email. Return 200 JSON authorizationUrl after setting the OAuth cookie; console validates the response and performs top-level navigation (do not redirect a CSRF-header fetch across origins). No user/identity/email or intent accepted from client. New starts clear and invalidate older completion credentials for that provider/session/context. Limit 10/IP/minute within general auth limits. Disabled runtime switch returns 503 sign_in_disabled before provider calls or mutations.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/provider"
          },
          {
            "$ref": "#/components/parameters/Origin"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "JSON only, at most 64 KiB. Empty actions explicitly send {}. Reject unknown fields.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OAuthConnectRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response7"
          },
          "400": {
            "$ref": "#/components/responses/Response2"
          },
          "401": {
            "$ref": "#/components/responses/Response8"
          },
          "403": {
            "$ref": "#/components/responses/Response5"
          },
          "413": {
            "$ref": "#/components/responses/Response6"
          },
          "429": {
            "$ref": "#/components/responses/Response3"
          },
          "500": {
            "$ref": "#/components/responses/Response1"
          },
          "503": {
            "$ref": "#/components/responses/Response4"
          }
        }
      }
    },
    "/api/auth/{provider}/reauth": {
      "post": {
        "operationId": "reauthenticateProvider",
        "summary": "Refresh current-session authentication",
        "description": "Require active session and CSRF. Set intent reauth server-side and sign the initiating session/user binding with OAuth state; only an already linked identity of that user can refresh auth_time. Google must send prompt=login and max_age=0. GitHub cannot force re-authentication; accept a fresh OAuth round trip and document that limitation. Return 200 JSON authorizationUrl after setting the OAuth cookie; console validates it and performs top-level navigation. Callback only stages completion; the same-origin completion POST proves the original Strict session and CSRF before rotation. New starts clear and invalidate older completion credentials for that provider/session/context. Limit 10/IP/minute within general auth limits. Disabled runtime switch returns 503 sign_in_disabled before provider calls or mutations.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/provider"
          },
          {
            "$ref": "#/components/parameters/Origin"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "JSON only, at most 64 KiB. Empty actions explicitly send {}. Reject unknown fields.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OAuthReauthRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response7"
          },
          "400": {
            "$ref": "#/components/responses/Response2"
          },
          "401": {
            "$ref": "#/components/responses/Response8"
          },
          "403": {
            "$ref": "#/components/responses/Response5"
          },
          "413": {
            "$ref": "#/components/responses/Response6"
          },
          "429": {
            "$ref": "#/components/responses/Response3"
          },
          "500": {
            "$ref": "#/components/responses/Response1"
          },
          "503": {
            "$ref": "#/components/responses/Response4"
          }
        }
      }
    },
    "/api/auth/{provider}/complete": {
      "post": {
        "operationId": "completeProviderAuthentication",
        "summary": "Complete session-bound connect or reauthentication",
        "description": "After the fixed same-origin completion document has loaded, POST {} with credentials, exact current-stage Origin and X-SC-CSRF: 1. A redirect chain alone is insufficient to prove the Strict session. Require BOTH the current Strict session cookie and the matching provider completion cookie; the OAuth/registration cookie or a browser result never substitutes for either. Reject all query parameters and client-supplied intent, identity, session, timestamps, credentials or redirects. Validate the purpose-specific signature and the one-time D1 HMAC record, original expiry, provider, real/test context and signed link/reauth intent. The current persisted active session/user must exactly match the initiating binding, even for another session of the same user. Missing/expired/revoked/inactive/mismatched session: 401 unauthorized. Missing/malformed/tampered/expired/replayed/wrong-provider/wrong-purpose completion: 400 invalid_pending_credential. Malformed body/query: 400 invalid_request; CSRF failure: 403 csrf_failed. Before gates pass, do not consume the credential. Rate-limit, oversized-request and disabled-sign-in gates likewise do not consume or renew it. After authorization/CSRF/input gates, clear the matching completion cookie on success or terminal credential/binding/identity rejection; invalidate any authenticated rejected credential without touching unrelated flows. Atomically recheck active session/user, original expiry and identity ownership, consume completion once, perform the mutation and write its audit; concurrent completions cannot succeed twice. For link, link the verified identity to that user even if emails differ; refuse another owner or a second identity for the same provider with 409 identity_conflict. An existing identical link is idempotent without duplicate audit. Preserve account email, session ID and auth_time. For reauth, the verified identity must still be linked to this user or return 409 identity_conflict; never sign in, create or link an identity. Rotate the session ID and set auth_time to the stored server callback authentication time (not the later completion request), retaining the original absolute session deadline. Return OAuthCompleteResponse with the server-selected outcome. Console reads /api/me, returns to Settings (or the existing token form), and requires a new explicit confirmation for any sensitive action; no automatic delete/token issuance or retry of an uncertain completion. Logout, session revocation/expiry, account deletion and a new provider start invalidate outstanding bound completions. All responses are no-store; no provider tokens or proof enter body, URL, logs or browser storage. Limit 10/IP/minute within general auth and session limits; no PMS/ADS daily quota consumption. Disabled runtime switch returns 503 sign_in_disabled before provider calls or mutations. Select the completion cookie by the path provider; the other provider's cookie never satisfies authorization, including when the other security alternative would otherwise match.",
        "security": [
          {
            "sessionCookie": [],
            "githubCompletionCookie": []
          },
          {
            "sessionCookie": [],
            "googleCompletionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/provider"
          },
          {
            "$ref": "#/components/parameters/Origin"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF"
          }
        ],
        "x-query-schema": {
          "$ref": "#/components/schemas/OAuthStartQuery"
        },
        "requestBody": {
          "required": true,
          "description": "JSON only, at most 64 KiB. Empty actions explicitly send {}. Reject unknown fields.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OAuthCompleteRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              },
              "Set-Cookie": {
                "description": "Clear __Host-sc_{provider}_completion=<signed one-time completion credential bound to provider/context/intent/initiating session/user/verified identity/callback time/original expiry>; Max-Age=<remaining flow seconds, at most 600>; Secure; HttpOnly; SameSite=Strict; Path=/ with Max-Age=0 and identical attributes. Only successful reauth also sets/rotates __Host-sc_session=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA; Secure; HttpOnly; SameSite=Strict; Path=/, preserving its absolute deadline and using the stored callback time for auth_time. Connect retains session ID and auth_time. Separate Set-Cookie fields; no Domain attribute.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthCompleteResponse"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request, invalid_pending_credential",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              },
              "Set-Cookie": {
                "$ref": "#/components/headers/Set-Cookie3"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              },
              "Set-Cookie": {
                "$ref": "#/components/headers/Set-Cookie3"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "csrf_failed",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "identity_conflict",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              },
              "Set-Cookie": {
                "$ref": "#/components/headers/Set-Cookie3"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/Response6"
          },
          "429": {
            "$ref": "#/components/responses/Response3"
          },
          "500": {
            "$ref": "#/components/responses/Response1"
          },
          "503": {
            "description": "sign_in_disabled",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/logout": {
      "post": {
        "operationId": "logout",
        "summary": "End the current session",
        "description": "Delete the current session and clear its cookie.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Origin"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF"
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody1"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response9"
          },
          "400": {
            "$ref": "#/components/responses/Response2"
          },
          "401": {
            "$ref": "#/components/responses/Response8"
          },
          "403": {
            "$ref": "#/components/responses/Response5"
          },
          "413": {
            "$ref": "#/components/responses/Response6"
          },
          "429": {
            "$ref": "#/components/responses/Response3"
          },
          "500": {
            "$ref": "#/components/responses/Response1"
          }
        }
      }
    },
    "/api/auth/logout-all": {
      "post": {
        "operationId": "logoutAll",
        "summary": "End all sessions",
        "description": "Delete all sessions for this user, including this one and any pending session-bound OAuth flows; clear this browser's session cookie.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Origin"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF"
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody1"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response9"
          },
          "400": {
            "$ref": "#/components/responses/Response2"
          },
          "401": {
            "$ref": "#/components/responses/Response8"
          },
          "403": {
            "$ref": "#/components/responses/Response5"
          },
          "413": {
            "$ref": "#/components/responses/Response6"
          },
          "429": {
            "$ref": "#/components/responses/Response3"
          },
          "500": {
            "$ref": "#/components/responses/Response1"
          }
        }
      }
    },
    "/api/me": {
      "get": {
        "operationId": "getMe",
        "summary": "Read the current account and quota",
        "description": "Return the stable user id (ULID, or legacy UUIDv7 before conversion) regardless of login method, free plan, linked identities and authoritative daily quota. used counts reserved leases, possibly exceeding actual calls; outage admissions are extra allowance outside the daily quota and are not included. resetsAt is next UTC midnight in Unix milliseconds. This read neither reserves nor consumes quota; failure to read AccountQuota returns 503 quota_unavailable with Retry-After: 30, never zero usage.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Response8"
          },
          "429": {
            "$ref": "#/components/responses/Response3"
          },
          "500": {
            "$ref": "#/components/responses/Response1"
          },
          "503": {
            "description": "sign_in_disabled when runtime sign-in is closed; provider_unavailable for temporary provider outage; quota_unavailable specifically when GET /api/me cannot read AccountQuota, with Retry-After: 30. Fail closed.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              },
              "Retry-After": {
                "description": "Required for quota_unavailable: 30.",
                "schema": {
                  "$ref": "#/components/schemas/RetryAfter"
                },
                "example": "30"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteAccount",
        "summary": "Delete the account after recent authentication",
        "description": "Require active session, exact current-stage Origin and X-SC-CSRF: 1. Server-side auth_time must satisfy 0 <= now - auth_time <= 600000 milliseconds; missing, future or older timestamps return 403 reauth_required without deletion. Console must initiate a fresh provider round trip with intent reauth and retry only after success. No client timestamp, OTP or bearer proof accepted. Invalid session returns 401 unauthorized; CSRF failure returns 403 csrf_failed. Atomically recheck recency/account state with deletion of identities and dependent services/policies/tokens, revoke every session and pending OAuth flow, capture the owned service and active token IDs, and clear the session cookie. After commit, invalidate those TOKEN_CACHE entries, release the local service snapshot and delete its exact SNAPSHOTS key. A cleanup failure returns 500 but cannot roll back committed deletion or extend cached authorization; a deleted account's token stops working within 60 seconds, the same fixed bound as revocation. Replayed attempts return 401 unauthorized. Success is 204 with no body. Excluded from PMS/ADS daily counting.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Origin"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "JSON only, at most 64 KiB. Empty actions explicitly send {}. Reject unknown fields.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeleteAccountRequest"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Account deleted and all sessions revoked; no response body.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              },
              "Set-Cookie": {
                "$ref": "#/components/headers/Set-Cookie4"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Response10"
          },
          "401": {
            "$ref": "#/components/responses/Response11"
          },
          "403": {
            "$ref": "#/components/responses/Response12"
          },
          "413": {
            "$ref": "#/components/responses/Response6"
          },
          "429": {
            "$ref": "#/components/responses/Response3"
          },
          "500": {
            "$ref": "#/components/responses/Response1"
          }
        }
      }
    },
    "/api/me/identities/{provider}": {
      "delete": {
        "operationId": "unlinkIdentity",
        "summary": "Unlink an identity owned by this account",
        "description": "Delete only this user's identity and audit identity.unlink after recent authentication. Require the same persisted 0 <= now - auth_time <= 600000 milliseconds check as token creation and account deletion; missing, future or stale authentication returns 403 reauth_required with no identity mutation or audit. Recheck recency, session validity and ownership in the atomic batch. Retain at least one other identity and enforce this against concurrent unlinks; 409 last_identity with no mutation if it is the last identity. No email fallback exists. Return 404 not_found if absent. The console offers provider reauthentication and requires an explicit retry after returning.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/provider"
          },
          {
            "$ref": "#/components/parameters/Origin"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF"
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody1"
        },
        "responses": {
          "200": {
            "description": "Success.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthSuccessResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Response2"
          },
          "401": {
            "$ref": "#/components/responses/Response8"
          },
          "403": {
            "$ref": "#/components/responses/Response12"
          },
          "404": {
            "description": "not_found: no such identity owned by this account.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "last_identity",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/Response6"
          },
          "429": {
            "$ref": "#/components/responses/Response3"
          },
          "500": {
            "$ref": "#/components/responses/Response1"
          }
        }
      }
    },
    "/api/me/audit": {
      "get": {
        "operationId": "getMyAuditLog",
        "summary": "Read the current user's audit log",
        "description": "Require a valid active account session and select only audit_log.user_id equal to that session's user; never accept a user selector. Read-only: no CSRF header or step-up required, no PMS/ADS quota consumption. Retention is 180 days. Page size defaults to 50, maximum 100. Order by time DESC then id DESC (binary string order; ULIDs and legacy UUIDv7 IDs sort by time within their form) and use an exclusive last-returned (time,id) keyset boundary. The initial page establishes an inclusive upper (time,id) bound carried through continuation, so newer events do not shift pages; retention/deletion may remove rows during pagination. Cursors are opaque, server-authenticated, bound to user/page size/upper bound/last key, and expire 15 minutes after the first page without renewal. Reject malformed, tampered, expired, wrong-user or inconsistent-limit cursors with 400 invalid_request, never reveal cursor contents or another user's data. Reject duplicate query keys before schema validation. Return at most limit entries, a non-null nextCursor only when another page exists, otherwise null. Return only allowlisted action, internal target ID (ULID or legacy UUIDv7) or null, time and coarse context, plus the entry ID for stable ordering. Never serialize raw audit detail, IP (including hashes), email (including hashes), provider subject/login, secrets, cookies, OTPs or request payloads.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "description": "Page size as a canonical decimal query string, 1–100; omitted means 50. Must match the initial page size when continuing a cursor.",
              "type": "string",
              "pattern": "^(?:[1-9]|[1-9][0-9]|100)$"
            },
            "example": "50",
            "description": "Page size as a canonical decimal query string, 1–100; omitted means 50. Must match the initial page size when continuing a cursor."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 32,
              "maxLength": 1024,
              "pattern": "^[A-Za-z0-9_-]+$",
              "description": "Opaque base64url continuation token, authenticated by the server and bound to the current user, page size, initial upper (time,id) bound and last returned (time,id). Expires 15 minutes after the first page, without renewal. Clients must pass it unchanged; syntax validation does not verify authenticity or expiry."
            },
            "example": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
          }
        ],
        "x-query-schema": {
          "$ref": "#/components/schemas/AuditQuery"
        },
        "responses": {
          "200": {
            "description": "Success.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuditResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Response10"
          },
          "401": {
            "$ref": "#/components/responses/Response11"
          },
          "429": {
            "$ref": "#/components/responses/Response3"
          },
          "500": {
            "$ref": "#/components/responses/Response1"
          }
        }
      }
    },
    "/api/service": {
      "get": {
        "operationId": "getService",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [],
        "description": "Legacy default-service route: it operates on the account's oldest service so a pre-multi-service console keeps working; new clients use /api/services/{serviceId}. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response13"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      },
      "post": {
        "operationId": "createService",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Origin2"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF2"
          }
        ],
        "description": "Legacy default-service route: it operates on the account's oldest service so a pre-multi-service console keeps working; new clients use /api/services/{serviceId}. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody2"
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/Response13"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      },
      "patch": {
        "operationId": "renameService",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Origin2"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF2"
          }
        ],
        "description": "Legacy default-service route: it operates on the account's oldest service so a pre-multi-service console keeps working; new clients use /api/services/{serviceId}. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody2"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response13"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      },
      "delete": {
        "operationId": "deleteService",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Origin2"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF2"
          }
        ],
        "description": "Legacy default-service route: it operates on the account's oldest service so a pre-multi-service console keeps working; new clients use /api/services/{serviceId}. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody3"
        },
        "responses": {
          "204": {
            "$ref": "#/components/responses/Response20"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      }
    },
    "/api/services": {
      "get": {
        "operationId": "listServices",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [],
        "description": "Service collection route: lists or creates services in the signed-in account. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control2"
              },
              "X-Quota-Daily-Remaining": {
                "$ref": "#/components/headers/X-Quota-Daily-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceListResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      },
      "post": {
        "operationId": "createServiceResource",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Origin2"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF2"
          }
        ],
        "description": "Service collection route: lists or creates services in the signed-in account. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody2"
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/Response13"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      }
    },
    "/api/services/{serviceId}": {
      "get": {
        "operationId": "getServiceResource",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceId"
          }
        ],
        "description": "Collection route: serviceId selects a service owned by the signed-in account; unknown, malformed or other-account IDs return 404 not_found. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response13"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      },
      "patch": {
        "operationId": "renameServiceResource",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceId"
          },
          {
            "$ref": "#/components/parameters/Origin2"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF2"
          }
        ],
        "description": "Collection route: serviceId selects a service owned by the signed-in account; unknown, malformed or other-account IDs return 404 not_found. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody2"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response13"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      },
      "delete": {
        "operationId": "deleteServiceResource",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceId"
          },
          {
            "$ref": "#/components/parameters/Origin2"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF2"
          }
        ],
        "description": "Collection route: serviceId selects a service owned by the signed-in account; unknown, malformed or other-account IDs return 404 not_found. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody3"
        },
        "responses": {
          "204": {
            "$ref": "#/components/responses/Response20"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      }
    },
    "/api/service/policies": {
      "get": {
        "operationId": "list-policies",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [],
        "description": "Legacy default-service route: it operates on the account's oldest service so a pre-multi-service console keeps working; new clients use /api/services/{serviceId}. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response21"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      },
      "post": {
        "operationId": "create-policies",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Origin2"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF2"
          }
        ],
        "description": "Legacy default-service route: it operates on the account's oldest service so a pre-multi-service console keeps working; new clients use /api/services/{serviceId}. The collection path selects the policy kind; submit {name, spdl}. Existing create endpoints remain compatible. Kind-less console creation first validates {spdl}, then posts to the detected collection. PUT keeps the stored kind; SPDL accepted only as the other kind returns 400 policy_kind_mismatch with delete-and-create guidance. Import retains its required explicit kind. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody4"
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/Response22"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      }
    },
    "/api/service/policies/{id}": {
      "get": {
        "operationId": "get-policies",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "description": "Legacy default-service route: it operates on the account's oldest service so a pre-multi-service console keeps working; new clients use /api/services/{serviceId}. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response22"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      },
      "put": {
        "operationId": "update-policies",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          },
          {
            "$ref": "#/components/parameters/Origin2"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF2"
          }
        ],
        "description": "Legacy default-service route: it operates on the account's oldest service so a pre-multi-service console keeps working; new clients use /api/services/{serviceId}. The collection path selects the policy kind; submit {name, spdl}. Existing create endpoints remain compatible. Kind-less console creation first validates {spdl}, then posts to the detected collection. PUT keeps the stored kind; SPDL accepted only as the other kind returns 400 policy_kind_mismatch with delete-and-create guidance. Import retains its required explicit kind. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody4"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response22"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      },
      "delete": {
        "operationId": "delete-policies",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          },
          {
            "$ref": "#/components/parameters/Origin2"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF2"
          }
        ],
        "description": "Legacy default-service route: it operates on the account's oldest service so a pre-multi-service console keeps working; new clients use /api/services/{serviceId}. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody3"
        },
        "responses": {
          "204": {
            "$ref": "#/components/responses/Response20"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      }
    },
    "/api/services/{serviceId}/policies": {
      "get": {
        "operationId": "list-service-policies",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceId"
          }
        ],
        "description": "Collection route: serviceId selects a service owned by the signed-in account; unknown, malformed or other-account IDs return 404 not_found. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response21"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      },
      "post": {
        "operationId": "create-service-policies",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceId"
          },
          {
            "$ref": "#/components/parameters/Origin2"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF2"
          }
        ],
        "description": "Collection route: serviceId selects a service owned by the signed-in account; unknown, malformed or other-account IDs return 404 not_found. The collection path selects the policy kind; submit {name, spdl}. Existing create endpoints remain compatible. Kind-less console creation first validates {spdl}, then posts to the detected collection. PUT keeps the stored kind; SPDL accepted only as the other kind returns 400 policy_kind_mismatch with delete-and-create guidance. Import retains its required explicit kind. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody4"
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/Response22"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      }
    },
    "/api/services/{serviceId}/policies/{id}": {
      "get": {
        "operationId": "get-service-policies",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceId"
          },
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "description": "Collection route: serviceId selects a service owned by the signed-in account; unknown, malformed or other-account IDs return 404 not_found. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response22"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      },
      "put": {
        "operationId": "update-service-policies",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceId"
          },
          {
            "$ref": "#/components/parameters/id"
          },
          {
            "$ref": "#/components/parameters/Origin2"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF2"
          }
        ],
        "description": "Collection route: serviceId selects a service owned by the signed-in account; unknown, malformed or other-account IDs return 404 not_found. The collection path selects the policy kind; submit {name, spdl}. Existing create endpoints remain compatible. Kind-less console creation first validates {spdl}, then posts to the detected collection. PUT keeps the stored kind; SPDL accepted only as the other kind returns 400 policy_kind_mismatch with delete-and-create guidance. Import retains its required explicit kind. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody4"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response22"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      },
      "delete": {
        "operationId": "delete-service-policies",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceId"
          },
          {
            "$ref": "#/components/parameters/id"
          },
          {
            "$ref": "#/components/parameters/Origin2"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF2"
          }
        ],
        "description": "Collection route: serviceId selects a service owned by the signed-in account; unknown, malformed or other-account IDs return 404 not_found. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody3"
        },
        "responses": {
          "204": {
            "$ref": "#/components/responses/Response20"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      }
    },
    "/api/service/role-policies": {
      "get": {
        "operationId": "list-role-policies",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [],
        "description": "Legacy default-service route: it operates on the account's oldest service so a pre-multi-service console keeps working; new clients use /api/services/{serviceId}. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response21"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      },
      "post": {
        "operationId": "create-role-policies",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Origin2"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF2"
          }
        ],
        "description": "Legacy default-service route: it operates on the account's oldest service so a pre-multi-service console keeps working; new clients use /api/services/{serviceId}. The collection path selects the policy kind; submit {name, spdl}. Existing create endpoints remain compatible. Kind-less console creation first validates {spdl}, then posts to the detected collection. PUT keeps the stored kind; SPDL accepted only as the other kind returns 400 policy_kind_mismatch with delete-and-create guidance. Import retains its required explicit kind. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody4"
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/Response22"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      }
    },
    "/api/service/role-policies/{id}": {
      "get": {
        "operationId": "get-role-policies",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "description": "Legacy default-service route: it operates on the account's oldest service so a pre-multi-service console keeps working; new clients use /api/services/{serviceId}. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response22"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      },
      "put": {
        "operationId": "update-role-policies",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          },
          {
            "$ref": "#/components/parameters/Origin2"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF2"
          }
        ],
        "description": "Legacy default-service route: it operates on the account's oldest service so a pre-multi-service console keeps working; new clients use /api/services/{serviceId}. The collection path selects the policy kind; submit {name, spdl}. Existing create endpoints remain compatible. Kind-less console creation first validates {spdl}, then posts to the detected collection. PUT keeps the stored kind; SPDL accepted only as the other kind returns 400 policy_kind_mismatch with delete-and-create guidance. Import retains its required explicit kind. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody4"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response22"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      },
      "delete": {
        "operationId": "delete-role-policies",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          },
          {
            "$ref": "#/components/parameters/Origin2"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF2"
          }
        ],
        "description": "Legacy default-service route: it operates on the account's oldest service so a pre-multi-service console keeps working; new clients use /api/services/{serviceId}. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody3"
        },
        "responses": {
          "204": {
            "$ref": "#/components/responses/Response20"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      }
    },
    "/api/services/{serviceId}/role-policies": {
      "get": {
        "operationId": "list-service-role-policies",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceId"
          }
        ],
        "description": "Collection route: serviceId selects a service owned by the signed-in account; unknown, malformed or other-account IDs return 404 not_found. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response21"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      },
      "post": {
        "operationId": "create-service-role-policies",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceId"
          },
          {
            "$ref": "#/components/parameters/Origin2"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF2"
          }
        ],
        "description": "Collection route: serviceId selects a service owned by the signed-in account; unknown, malformed or other-account IDs return 404 not_found. The collection path selects the policy kind; submit {name, spdl}. Existing create endpoints remain compatible. Kind-less console creation first validates {spdl}, then posts to the detected collection. PUT keeps the stored kind; SPDL accepted only as the other kind returns 400 policy_kind_mismatch with delete-and-create guidance. Import retains its required explicit kind. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody4"
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/Response22"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      }
    },
    "/api/services/{serviceId}/role-policies/{id}": {
      "get": {
        "operationId": "get-service-role-policies",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceId"
          },
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "description": "Collection route: serviceId selects a service owned by the signed-in account; unknown, malformed or other-account IDs return 404 not_found. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response22"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      },
      "put": {
        "operationId": "update-service-role-policies",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceId"
          },
          {
            "$ref": "#/components/parameters/id"
          },
          {
            "$ref": "#/components/parameters/Origin2"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF2"
          }
        ],
        "description": "Collection route: serviceId selects a service owned by the signed-in account; unknown, malformed or other-account IDs return 404 not_found. The collection path selects the policy kind; submit {name, spdl}. Existing create endpoints remain compatible. Kind-less console creation first validates {spdl}, then posts to the detected collection. PUT keeps the stored kind; SPDL accepted only as the other kind returns 400 policy_kind_mismatch with delete-and-create guidance. Import retains its required explicit kind. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody4"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response22"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      },
      "delete": {
        "operationId": "delete-service-role-policies",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceId"
          },
          {
            "$ref": "#/components/parameters/id"
          },
          {
            "$ref": "#/components/parameters/Origin2"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF2"
          }
        ],
        "description": "Collection route: serviceId selects a service owned by the signed-in account; unknown, malformed or other-account IDs return 404 not_found. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody3"
        },
        "responses": {
          "204": {
            "$ref": "#/components/responses/Response20"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      }
    },
    "/api/service/validate": {
      "post": {
        "operationId": "validatePolicy",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Origin2"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF2"
          }
        ],
        "description": "Legacy default-service route: it operates on the account's oldest service so a pre-multi-service console keeps working; new clients use /api/services/{serviceId}. Submit {spdl} to detect the kind with both PMS parsers; success returns {kind, parsed}. Exactly one parser must succeed; two successes return 400 ambiguous_policy_kind (no silent priority), neither returns a normal positioned parse/custom-function error. For example grant user alice role editor is ambiguous: omit the optional role prefix for a global role assignment, or parenthesize the principals for authorization. Legacy {kind, spdl} selects that parser, including ambiguous SPDL, and retains its original {parsed} success response; a source accepted only by the other parser returns 400 policy_kind_mismatch. This route is account-scoped and works before any service exists. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "requestBody": {
          "required": true,
          "description": "application/json only, <=64 KiB. SPDL <=4096 UTF-8 bytes and names <=128 Unicode code points are additionally checked at runtime.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ValidatePolicyRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control2"
              },
              "X-Quota-Daily-Remaining": {
                "$ref": "#/components/headers/X-Quota-Daily-Remaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidatePolicyResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      }
    },
    "/api/service/test": {
      "post": {
        "operationId": "testService",
        "x-response-schema-by-operation": {
          "isAllowed": {
            "$ref": "#/components/schemas/ServiceTestDecision"
          },
          "diagnose": {
            "$ref": "#/components/schemas/ServiceTestDiagnosis"
          },
          "allGrantedRoles": {
            "$ref": "#/components/schemas/ServiceTestRoles"
          },
          "allGrantedPermissions": {
            "$ref": "#/components/schemas/ServiceTestPermissions"
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Origin2"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF2"
          }
        ],
        "description": "Legacy default-service route: it operates on the account's oldest service so a pre-multi-service console keeps working; new clients use /api/services/{serviceId}. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57. Every successful Playground response reports the evaluated consistent D1 snapshot's revision in X-Service-Revision.",
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody5"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response23"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      }
    },
    "/api/services/{serviceId}/test": {
      "post": {
        "operationId": "testServiceResource",
        "x-response-schema-by-operation": {
          "isAllowed": {
            "$ref": "#/components/schemas/ServiceTestDecision"
          },
          "diagnose": {
            "$ref": "#/components/schemas/ServiceTestDiagnosis"
          },
          "allGrantedRoles": {
            "$ref": "#/components/schemas/ServiceTestRoles"
          },
          "allGrantedPermissions": {
            "$ref": "#/components/schemas/ServiceTestPermissions"
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceId"
          },
          {
            "$ref": "#/components/parameters/Origin2"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF2"
          }
        ],
        "description": "Collection route: serviceId selects a service owned by the signed-in account; unknown, malformed or other-account IDs return 404 not_found. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57. Every successful Playground response reports the evaluated consistent D1 snapshot's revision in X-Service-Revision.",
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody5"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response23"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      }
    },
    "/api/service/export": {
      "get": {
        "operationId": "exportService",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/format"
          }
        ],
        "description": "Legacy default-service route: it operates on the account's oldest service so a pre-multi-service console keeps working; new clients use /api/services/{serviceId}. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response24"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      }
    },
    "/api/services/{serviceId}/export": {
      "get": {
        "operationId": "exportServiceResource",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceId"
          },
          {
            "$ref": "#/components/parameters/format"
          }
        ],
        "description": "Collection route: serviceId selects a service owned by the signed-in account; unknown, malformed or other-account IDs return 404 not_found. Signed-in active accounts only; bearer tokens never authorize management. GET never creates a service. An account may own up to 100 services with unique names; missing services, wrong policy kinds and unowned IDs return 404. Reject unknown body/query fields and duplicate query keys. POST /api/services creates another service and returns 409 service_exists for a duplicate name or quota_exceeded with limit 100 at the technical cap. The legacy POST /api/service creates a service only when the account has none, preserving its pre-multi-service contract. Create/update always parse SPDL with WASM and persist parsed JSON; successful changes atomically increment revision and audit, failures do neither. Same-name service PATCH is a no-op. PUT replaces name and SPDL. Validate is read-only and works before service creation. DELETE requires JSON {}. Service deletion atomically rechecks persisted session and auth_time within 0–600000 ms and cascades policies/tokens. The combined 200-policy cap and the daily call quota are per account and shared by all of its services; the two-active-token cap is per service. Export reads one consistent D1 snapshot: JSON includes service metadata and both kinds with source/parsed form; SPDL is a text attachment grouping authz then role with JSON-escaped metadata comments and verbatim policy sources. POST .../test accepts {op, request} with exactly isAllowed, diagnose, allGrantedRoles or allGrantedPermissions and a Speedle REST JsonContext (typed attributes array), the same body as POST /authz-check/v1/* (#213). It returns the request-op-correlated Speedle REST body (IsAllowedResponse for isAllowed, EvaluationDebugResponse for diagnose, a string array for allGrantedRoles, a PermissionResponse array for allGrantedPermissions; [] when none is granted) using a consistent D1 snapshot of the selected service; it bypasses KV and its approximately 60-second propagation delay. serviceName is never defaulted: an omitted, empty or different name returns 400 invalid_request with message \"serviceName must be this service's name\". A Speedle SPDL-0004 decoding or attribute error returns 400 invalid_request with the Speedle message. Like Speedle without a token asserter, subject.token and tokenType are ignored; subject tokens are never validated. Playground never writes policy data or publishes snapshots. The per-operation schema map is authoritative for response validation; the non-exclusive union only documents compatible plain bodies. Contract declaration does not establish runtime support for newly declared operations. Token management is tracked in #56. Authenticated matched PMS requests consume account-wide daily quota before business validation for non-5xx outcomes, including successful and rejected handlers; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap. Auth/routing/burst rejections are not charged. Daily quota failures return exact flat errors as specified in #57.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response24"
          },
          "400": {
            "$ref": "#/components/responses/Response14"
          },
          "401": {
            "$ref": "#/components/responses/Response15"
          },
          "403": {
            "$ref": "#/components/responses/Response16"
          },
          "404": {
            "$ref": "#/components/responses/Response15"
          },
          "409": {
            "$ref": "#/components/responses/Response17"
          },
          "413": {
            "$ref": "#/components/responses/Response15"
          },
          "429": {
            "$ref": "#/components/responses/Response18"
          },
          "500": {
            "$ref": "#/components/responses/Response15"
          },
          "503": {
            "$ref": "#/components/responses/Response19"
          }
        }
      }
    },
    "/api/tokens": {
      "get": {
        "operationId": "listTokens",
        "summary": "List this account service's token metadata",
        "description": "Session-only for signed-in active accounts; bearer credentials never authorize management. Only GET accepts the strict TokenListQuery limit/cursor parameters; mutations reject all query parameters. Reject unknown body fields, duplicate query keys and unknown/malformed parameters. Mutations require exact current-stage Origin AND X-SC-CSRF: 1. Apply daily quota only on a matched management handler, after session authentication/authorization, applicable CSRF checks and session/burst limiting, and before business validation. Every matched authenticated non-5xx request entering the handler consumes one PMS/ADS account quota unit, including reads and business rejections; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap, and authentication, routing, CSRF, oversized-body and burst rejections are uncharged. Auth/session/OAuth/account/identity endpoints retain their existing daily-quota exemption. The legacy route addresses the account's oldest service; a missing service returns 404 not_found. GET accepts only limit (canonical decimal 1–100, default 25) and an optional exclusive canonical ID cursor (ULID or legacy UUIDv7). Return this owned service's rows by id DESC with id < cursor when supplied, using an indexed tenant-scoped LIMIT+1 seek and no unbounded read/COUNT. Includes active and revoked history without retention/deletion changes; the two-active-token cap is separate from pagination. Return at most the requested limit; nextCursor is required, equals the last returned id only when another row exists, and is null on empty/final pages. Return each token's fixed scope, with ads omitted (absent means ads) so a pre-#250 strict console keeps parsing while the new Worker is live before Pages catches up; pms:read and pms:write are always present. Optional expiresAt is Unix milliseconds and is omitted for current non-expiring v1 tokens. Never return token, secret, HMAC or secret_hash. Reading does not create a service or token.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/cursor"
          }
        ],
        "x-query-schema": {
          "$ref": "#/components/schemas/TokenListQuery"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response25"
          },
          "400": {
            "$ref": "#/components/responses/Response26"
          },
          "401": {
            "$ref": "#/components/responses/Response27"
          },
          "404": {
            "$ref": "#/components/responses/Response28"
          },
          "413": {
            "$ref": "#/components/responses/Response29"
          },
          "429": {
            "$ref": "#/components/responses/Response30"
          },
          "500": {
            "$ref": "#/components/responses/Response31"
          },
          "503": {
            "$ref": "#/components/responses/Response32"
          }
        }
      },
      "post": {
        "operationId": "createToken",
        "summary": "Issue one service token after recent authentication",
        "description": "Session-only for signed-in active accounts; bearer credentials never authorize management. Only GET accepts the strict TokenListQuery limit/cursor parameters; mutations reject all query parameters. Reject unknown body fields, duplicate query keys and unknown/malformed parameters. Mutations require exact current-stage Origin AND X-SC-CSRF: 1. Apply daily quota only on a matched management handler, after session authentication/authorization, applicable CSRF checks and session/burst limiting, and before business validation. Every matched authenticated non-5xx request entering the handler consumes one PMS/ADS account quota unit, including reads and business rejections; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap, and authentication, routing, CSRF, oversized-body and burst rejections are uncharged. Auth/session/OAuth/account/identity endpoints retain their existing daily-quota exemption. Client submits only {name, scope?}; trim the name, default an omitted scope to ads, and reject unknown fields, including serviceId, service, expiry and authentication proofs. Allowed scopes are ads (authorization checks only), pms:read and pms:write (token-authenticated /policy-mgmt/v1 management). While sign-in is disabled for the stage, pms:read/pms:write issuance returns 403 management_scope_disabled without a token or audit. A missing oldest service returns 404 not_found. Atomically recheck the persisted active account, live session and authoritative 0 <= now - auth_time <= 600000 milliseconds, enforce at most 2 active tokens for the owned service across all scopes, store the fixed scope, insert and audit in the same D1 transaction. Missing, future or stale authentication returns 403 reauth_required without issuance/audit. The cap returns exactly 409 {error: quota_exceeded, limit: 2}. Generate 32 CSPRNG bytes, encode exactly 43 zero-padded base62 digits, and disclose spdl_<canonical ULID id>_<secret> once in the 201 response; its prefix equals the returned id. Persist only HMAC-SHA256(TOKEN_PEPPER, secret); never log or persist the plaintext credential. The scope is immutable, bound to this service and has no intrinsic expiry in v1. Responses may include optional expiresAt in Unix milliseconds and omit it for current non-expiring tokens; it is never a client-selected issuance input. Rotate by create, switch callers, then revoke.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Origin3"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF"
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody6"
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/Response33"
          },
          "400": {
            "$ref": "#/components/responses/Response26"
          },
          "401": {
            "$ref": "#/components/responses/Response27"
          },
          "403": {
            "$ref": "#/components/responses/Response34"
          },
          "404": {
            "$ref": "#/components/responses/Response28"
          },
          "409": {
            "$ref": "#/components/responses/Response35"
          },
          "413": {
            "$ref": "#/components/responses/Response29"
          },
          "429": {
            "$ref": "#/components/responses/Response30"
          },
          "500": {
            "$ref": "#/components/responses/Response31"
          },
          "503": {
            "$ref": "#/components/responses/Response32"
          }
        }
      }
    },
    "/api/services/{serviceId}/tokens": {
      "get": {
        "operationId": "listServiceTokens",
        "summary": "List one owned service's token metadata",
        "description": "Session-only for signed-in active accounts; bearer credentials never authorize management. Only GET accepts the strict TokenListQuery limit/cursor parameters; mutations reject all query parameters. Reject unknown body fields, duplicate query keys and unknown/malformed parameters. Mutations require exact current-stage Origin AND X-SC-CSRF: 1. Apply daily quota only on a matched management handler, after session authentication/authorization, applicable CSRF checks and session/burst limiting, and before business validation. Every matched authenticated non-5xx request entering the handler consumes one PMS/ADS account quota unit, including reads and business rejections; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap, and authentication, routing, CSRF, oversized-body and burst rejections are uncharged. Auth/session/OAuth/account/identity endpoints retain their existing daily-quota exemption. Collection route: serviceId must identify a service owned by the signed-in account; unknown, malformed or other-account IDs return 404 not_found. Accepts only limit (canonical decimal 1–100, default 25) and an optional exclusive canonical ID cursor. Returns this service's rows by id DESC with id < cursor, using an indexed service-scoped LIMIT+1 seek and no unbounded read/COUNT; nextCursor follows the same rules as the legacy route. Includes active and revoked history for this service only. Never returns token, secret, HMAC or secret_hash.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceId2"
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/cursor"
          }
        ],
        "x-query-schema": {
          "$ref": "#/components/schemas/TokenListQuery"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response25"
          },
          "400": {
            "$ref": "#/components/responses/Response26"
          },
          "401": {
            "$ref": "#/components/responses/Response27"
          },
          "404": {
            "$ref": "#/components/responses/Response28"
          },
          "413": {
            "$ref": "#/components/responses/Response29"
          },
          "429": {
            "$ref": "#/components/responses/Response30"
          },
          "500": {
            "$ref": "#/components/responses/Response31"
          },
          "503": {
            "$ref": "#/components/responses/Response32"
          }
        }
      },
      "post": {
        "operationId": "createServiceToken",
        "summary": "Issue one token for the selected owned service",
        "description": "Session-only for signed-in active accounts; bearer credentials never authorize management. Only GET accepts the strict TokenListQuery limit/cursor parameters; mutations reject all query parameters. Reject unknown body fields, duplicate query keys and unknown/malformed parameters. Mutations require exact current-stage Origin AND X-SC-CSRF: 1. Apply daily quota only on a matched management handler, after session authentication/authorization, applicable CSRF checks and session/burst limiting, and before business validation. Every matched authenticated non-5xx request entering the handler consumes one PMS/ADS account quota unit, including reads and business rejections; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap, and authentication, routing, CSRF, oversized-body and burst rejections are uncharged. Auth/session/OAuth/account/identity endpoints retain their existing daily-quota exemption. Collection route: serviceId must identify a service owned by the signed-in account; unknown, malformed or other-account IDs return 404 not_found. Client submits only {name, scope?}; the same recent-authentication, scope gate, two-active-token cap per service, atomic D1 commit and one-time disclosure rules as the legacy route apply. A token is permanently bound to the service selected by serviceId.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceId2"
          },
          {
            "$ref": "#/components/parameters/Origin3"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF"
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody6"
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/Response33"
          },
          "400": {
            "$ref": "#/components/responses/Response26"
          },
          "401": {
            "$ref": "#/components/responses/Response27"
          },
          "403": {
            "$ref": "#/components/responses/Response34"
          },
          "404": {
            "$ref": "#/components/responses/Response28"
          },
          "409": {
            "$ref": "#/components/responses/Response35"
          },
          "413": {
            "$ref": "#/components/responses/Response29"
          },
          "429": {
            "$ref": "#/components/responses/Response30"
          },
          "500": {
            "$ref": "#/components/responses/Response31"
          },
          "503": {
            "$ref": "#/components/responses/Response32"
          }
        }
      }
    },
    "/api/services/{serviceId}/tokens/{id}": {
      "delete": {
        "operationId": "deleteServiceToken",
        "summary": "Revoke one token of the selected owned service",
        "description": "Session-only for signed-in active accounts; bearer credentials never authorize management. Only GET accepts the strict TokenListQuery limit/cursor parameters; mutations reject all query parameters. Reject unknown body fields, duplicate query keys and unknown/malformed parameters. Mutations require exact current-stage Origin AND X-SC-CSRF: 1. Apply daily quota only on a matched management handler, after session authentication/authorization, applicable CSRF checks and session/burst limiting, and before business validation. Every matched authenticated non-5xx request entering the handler consumes one PMS/ADS account quota unit, including reads and business rejections; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap, and authentication, routing, CSRF, oversized-body and burst rejections are uncharged. Auth/session/OAuth/account/identity endpoints retain their existing daily-quota exemption. Collection route: serviceId must identify a service owned by the signed-in account and the token must belong to that service. Requires JSON {} and a canonical ID (ULID or legacy UUIDv7). Return empty 204 only for the first revocation of an active token of that service; a missing service, another service's token, an unowned, unknown or already-revoked ID all return the same 404 not_found. Revocation and audit commit atomically; invalidate existing verification-cache entries. Token revocation takes effect within 60 seconds: another isolate or edge may use a previously verified record only until its fixed authority deadline, never longer. Cache-cleanup failure after the committed revocation returns 500 but cannot extend that deadline. No recent-authentication step is required for revocation. Never disclose token secrets or HMACs.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceId2"
          },
          {
            "$ref": "#/components/parameters/id2"
          },
          {
            "$ref": "#/components/parameters/Origin3"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF"
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody7"
        },
        "responses": {
          "204": {
            "$ref": "#/components/responses/Response36"
          },
          "400": {
            "$ref": "#/components/responses/Response26"
          },
          "401": {
            "$ref": "#/components/responses/Response27"
          },
          "403": {
            "$ref": "#/components/responses/Response37"
          },
          "404": {
            "$ref": "#/components/responses/Response28"
          },
          "413": {
            "$ref": "#/components/responses/Response29"
          },
          "429": {
            "$ref": "#/components/responses/Response30"
          },
          "500": {
            "$ref": "#/components/responses/Response31"
          },
          "503": {
            "$ref": "#/components/responses/Response32"
          }
        }
      }
    },
    "/api/tokens/{id}": {
      "delete": {
        "operationId": "revokeToken",
        "summary": "Revoke an owned active token through the legacy default-service route",
        "description": "Session-only for signed-in active accounts; bearer credentials never authorize management. Only GET accepts the strict TokenListQuery limit/cursor parameters; mutations reject all query parameters. Reject unknown body fields, duplicate query keys and unknown/malformed parameters. Mutations require exact current-stage Origin AND X-SC-CSRF: 1. Apply daily quota only on a matched management handler, after session authentication/authorization, applicable CSRF checks and session/burst limiting, and before business validation. Every matched authenticated non-5xx request entering the handler consumes one PMS/ADS account quota unit, including reads and business rejections; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap, and authentication, routing, CSRF, oversized-body and burst rejections are uncharged. Auth/session/OAuth/account/identity endpoints retain their existing daily-quota exemption. Legacy route: it addresses the account's oldest service, so a token of another service returns the same 404 not_found as an unowned or unknown ID; the collection route revokes a token of any selected service. Requires JSON {} and a canonical ID (ULID or legacy UUIDv7). Return empty 204 only for the first revocation of an owned active token; missing service, unowned, unknown or already-revoked IDs return the same 404 not_found. Revocation and audit commit atomically; invalidate existing verification-cache entries. Token revocation takes effect within 60 seconds: another isolate or edge may use a previously verified record only until its fixed authority deadline, never longer. Cache-cleanup failure after the committed revocation returns 500 but cannot extend that deadline. No recent-authentication step is required for revocation. Never disclose token secrets or HMACs.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/id2"
          },
          {
            "$ref": "#/components/parameters/Origin3"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF"
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody7"
        },
        "responses": {
          "204": {
            "$ref": "#/components/responses/Response36"
          },
          "400": {
            "$ref": "#/components/responses/Response26"
          },
          "401": {
            "$ref": "#/components/responses/Response27"
          },
          "403": {
            "$ref": "#/components/responses/Response37"
          },
          "404": {
            "$ref": "#/components/responses/Response28"
          },
          "413": {
            "$ref": "#/components/responses/Response29"
          },
          "429": {
            "$ref": "#/components/responses/Response30"
          },
          "500": {
            "$ref": "#/components/responses/Response31"
          },
          "503": {
            "$ref": "#/components/responses/Response32"
          }
        }
      }
    },
    "/authz-check/v1/is-allowed": {
      "post": {
        "operationId": "ads-is-allowed",
        "summary": "Check authorization for the token's service",
        "description": "Authenticated ADS contract B (#100); declaration is not evidence of runtime cutover. Accept only Authorization: Bearer <AdsBearerToken> (scheme is case-insensitive; the credential remains case-sensitive). Anonymous requests and cookie-session-only requests return 401; no session/CSRF authorization and no CORS. For admissible-size matched POSTs, verify the credential before parsing JSON, then apply per-token burst limiting, account-wide daily quota and business validation in that order. The free account cap is 10,000 matched PMS+ADS chargeable handler calls per UTC day, shared across its services, tokens and sessions, resetting at 00:00 UTC. The request and response bodies are the Speedle REST contract (pkg/svcs/adsrest, #213): the same JsonContext fields, typed attributes and bodies, decoded by the same Go decoder, so unknown fields are ignored and errors carry the Speedle message. The token principal supplies the trusted accountId/serviceId/serviceName; never take ownership from the request. Send serviceName, the token's service name, in every request, as with Speedle; it is never defaulted. An omitted or empty serviceName names no service and gets Speedle's SERVICE_NOT_FOUND answer; a different non-empty name returns 403 service_mismatch. Like Speedle without a token asserter, subject.token and tokenType are accepted and ignored: Speedle Cloud does not validate subject tokens, and decisions use subject.principals only. Consume one daily unit for admitted outcomes, including invalid business inputs, policy denials and Speedle-compatible engine errors caused by the request; Cloud-side handler 5xx (for example snapshot_unavailable, internal_error or another Cloud error code) is refunded only after the independent QuotaRelief ledger approves it within a per-account daily cap, and auth/routing/burst/size/quota-reservation rejections are uncharged. While the counter cannot confirm a lease, bearer ADS decisions for the account are throttled to two per second on that isolate: over-rate requests wait in a FIFO queue for at most ten seconds (at most 128 waiting callers per isolate across accounts), a longer wait returns quota_unavailable with Retry-After: 30 before the handler, and throttling lasts until a lease is confirmed again, never crossing UTC midnight. Every throttled admission first consumes one unit of the independent QuotaRelief outage allowance (10,000 per account per UTC day by default); outage admissions are extra allowance outside the daily quota, are not written to reserved and never cause a 429 on recovery. They are separately metered, and fail closed when the ledger is unavailable or the allowance is exhausted. Without a confirmed lease from the current UTC day, for example on a cold isolate, quota_unavailable fails closed. Use only a valid scoped KV snapshot and scoped/revision-aware evaluator; never a bundled demo or D1 policy-evaluation fallback. Snapshot propagation remains approximately 60 seconds; the session-only Playground can read current D1 data separately. lastUsedAt updates asynchronously at most once per hour and never changes authorization. Speedle RESTService.IsAllowed. Both an allow and a policy denial return 200 IsAllowedResponse; evaluator errors are a 200 denial with errorMessage, exactly as Speedle returns them.",
        "security": [
          {
            "apiToken": []
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody8"
        },
        "responses": {
          "200": {
            "description": "Upstream ADS result, including successful policy denials.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control4"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-Quota-Daily-Remaining": {
                "$ref": "#/components/headers/X-Quota-Daily-Remaining3"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdsDecision"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Response38"
          },
          "401": {
            "$ref": "#/components/responses/Response39"
          },
          "403": {
            "$ref": "#/components/responses/Response40"
          },
          "405": {
            "$ref": "#/components/responses/Response41"
          },
          "413": {
            "$ref": "#/components/responses/Response42"
          },
          "429": {
            "$ref": "#/components/responses/Response43"
          },
          "500": {
            "$ref": "#/components/responses/Response44"
          },
          "503": {
            "$ref": "#/components/responses/Response45"
          }
        }
      }
    },
    "/authz-check/v1/all-granted-roles": {
      "post": {
        "operationId": "ads-all-granted-roles",
        "summary": "List granted roles for the token's service",
        "description": "Authenticated ADS contract B (#100); declaration is not evidence of runtime cutover. Accept only Authorization: Bearer <AdsBearerToken> (scheme is case-insensitive; the credential remains case-sensitive). Anonymous requests and cookie-session-only requests return 401; no session/CSRF authorization and no CORS. For admissible-size matched POSTs, verify the credential before parsing JSON, then apply per-token burst limiting, account-wide daily quota and business validation in that order. The free account cap is 10,000 matched PMS+ADS chargeable handler calls per UTC day, shared across its services, tokens and sessions, resetting at 00:00 UTC. The request and response bodies are the Speedle REST contract (pkg/svcs/adsrest, #213): the same JsonContext fields, typed attributes and bodies, decoded by the same Go decoder, so unknown fields are ignored and errors carry the Speedle message. The token principal supplies the trusted accountId/serviceId/serviceName; never take ownership from the request. Send serviceName, the token's service name, in every request, as with Speedle; it is never defaulted. An omitted or empty serviceName names no service and gets Speedle's SERVICE_NOT_FOUND answer; a different non-empty name returns 403 service_mismatch. Like Speedle without a token asserter, subject.token and tokenType are accepted and ignored: Speedle Cloud does not validate subject tokens, and decisions use subject.principals only. Consume one daily unit for admitted outcomes, including invalid business inputs, policy denials and Speedle-compatible engine errors caused by the request; Cloud-side handler 5xx (for example snapshot_unavailable, internal_error or another Cloud error code) is refunded only after the independent QuotaRelief ledger approves it within a per-account daily cap, and auth/routing/burst/size/quota-reservation rejections are uncharged. While the counter cannot confirm a lease, bearer ADS decisions for the account are throttled to two per second on that isolate: over-rate requests wait in a FIFO queue for at most ten seconds (at most 128 waiting callers per isolate across accounts), a longer wait returns quota_unavailable with Retry-After: 30 before the handler, and throttling lasts until a lease is confirmed again, never crossing UTC midnight. Every throttled admission first consumes one unit of the independent QuotaRelief outage allowance (10,000 per account per UTC day by default); outage admissions are extra allowance outside the daily quota, are not written to reserved and never cause a 429 on recovery. They are separately metered, and fail closed when the ledger is unavailable or the allowance is exhausted. Without a confirmed lease from the current UTC day, for example on a cold isolate, quota_unavailable fails closed. Use only a valid scoped KV snapshot and scoped/revision-aware evaluator; never a bundled demo or D1 policy-evaluation fallback. Snapshot propagation remains approximately 60 seconds; the session-only Playground can read current D1 data separately. lastUsedAt updates asynchronously at most once per hour and never changes authorization. Speedle RESTService.GetAllGrantedRoles: a string array, [] when none. Evaluator errors use the Speedle status mapping with {error: message}.",
        "security": [
          {
            "apiToken": []
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody8"
        },
        "responses": {
          "200": {
            "description": "Upstream ADS result, including successful policy denials.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control4"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-Quota-Daily-Remaining": {
                "$ref": "#/components/headers/X-Quota-Daily-Remaining3"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdsRoles"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Response38"
          },
          "401": {
            "$ref": "#/components/responses/Response39"
          },
          "403": {
            "$ref": "#/components/responses/Response40"
          },
          "405": {
            "$ref": "#/components/responses/Response41"
          },
          "413": {
            "$ref": "#/components/responses/Response42"
          },
          "429": {
            "$ref": "#/components/responses/Response43"
          },
          "500": {
            "$ref": "#/components/responses/Response44"
          },
          "503": {
            "$ref": "#/components/responses/Response45"
          }
        }
      }
    },
    "/authz-check/v1/all-granted-permissions": {
      "post": {
        "operationId": "ads-all-granted-permissions",
        "summary": "List granted permissions for the token's service",
        "description": "Authenticated ADS contract B (#100); declaration is not evidence of runtime cutover. Accept only Authorization: Bearer <AdsBearerToken> (scheme is case-insensitive; the credential remains case-sensitive). Anonymous requests and cookie-session-only requests return 401; no session/CSRF authorization and no CORS. For admissible-size matched POSTs, verify the credential before parsing JSON, then apply per-token burst limiting, account-wide daily quota and business validation in that order. The free account cap is 10,000 matched PMS+ADS chargeable handler calls per UTC day, shared across its services, tokens and sessions, resetting at 00:00 UTC. The request and response bodies are the Speedle REST contract (pkg/svcs/adsrest, #213): the same JsonContext fields, typed attributes and bodies, decoded by the same Go decoder, so unknown fields are ignored and errors carry the Speedle message. The token principal supplies the trusted accountId/serviceId/serviceName; never take ownership from the request. Send serviceName, the token's service name, in every request, as with Speedle; it is never defaulted. An omitted or empty serviceName names no service and gets Speedle's SERVICE_NOT_FOUND answer; a different non-empty name returns 403 service_mismatch. Like Speedle without a token asserter, subject.token and tokenType are accepted and ignored: Speedle Cloud does not validate subject tokens, and decisions use subject.principals only. Consume one daily unit for admitted outcomes, including invalid business inputs, policy denials and Speedle-compatible engine errors caused by the request; Cloud-side handler 5xx (for example snapshot_unavailable, internal_error or another Cloud error code) is refunded only after the independent QuotaRelief ledger approves it within a per-account daily cap, and auth/routing/burst/size/quota-reservation rejections are uncharged. While the counter cannot confirm a lease, bearer ADS decisions for the account are throttled to two per second on that isolate: over-rate requests wait in a FIFO queue for at most ten seconds (at most 128 waiting callers per isolate across accounts), a longer wait returns quota_unavailable with Retry-After: 30 before the handler, and throttling lasts until a lease is confirmed again, never crossing UTC midnight. Every throttled admission first consumes one unit of the independent QuotaRelief outage allowance (10,000 per account per UTC day by default); outage admissions are extra allowance outside the daily quota, are not written to reserved and never cause a 429 on recovery. They are separately metered, and fail closed when the ledger is unavailable or the allowance is exhausted. Without a confirmed lease from the current UTC day, for example on a cold isolate, quota_unavailable fails closed. Use only a valid scoped KV snapshot and scoped/revision-aware evaluator; never a bundled demo or D1 policy-evaluation fallback. Snapshot propagation remains approximately 60 seconds; the session-only Playground can read current D1 data separately. lastUsedAt updates asynchronously at most once per hour and never changes authorization. Speedle RESTService.GetAllGrantedPermissions: PermissionResponse {resource, actions} items, [] when none. Evaluator errors use the Speedle status mapping with {error: message}.",
        "security": [
          {
            "apiToken": []
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody8"
        },
        "responses": {
          "200": {
            "description": "Upstream ADS result, including successful policy denials.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control4"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-Quota-Daily-Remaining": {
                "$ref": "#/components/headers/X-Quota-Daily-Remaining3"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdsPermissions"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Response38"
          },
          "401": {
            "$ref": "#/components/responses/Response39"
          },
          "403": {
            "$ref": "#/components/responses/Response40"
          },
          "405": {
            "$ref": "#/components/responses/Response41"
          },
          "413": {
            "$ref": "#/components/responses/Response42"
          },
          "429": {
            "$ref": "#/components/responses/Response43"
          },
          "500": {
            "$ref": "#/components/responses/Response44"
          },
          "503": {
            "$ref": "#/components/responses/Response45"
          }
        }
      }
    },
    "/policy-mgmt/v1/service": {
      "get": {
        "operationId": "pmsListServices",
        "summary": "List the token's service",
        "description": "Token-authenticated Speedle PMS compatibility API (#250). Requires `Authorization: Bearer <token>`; session cookies and CSRF headers are never accepted. Order: bearer verification (401), fixed-scope authorization (403 insufficient_scope), per-token burst limit (429 rate_limited), account daily quota (429 daily_quota_exceeded or 503 quota_unavailable), then business validation. Every admitted non-5xx request consumes one PMS/ADS account quota unit, including reads and business rejections; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap, and auth, scope, routing, size, burst and reservation rejections are uncharged. Mutations and their audit rows commit atomically and republish the service snapshot. Never give manage mode (pms:write) to an agent governed by these policies: it can change the policies that decide its own access. Upstream GET /service. A valid token sees only its own service, because every token is bound to one service; returns [] if it no longer exists. Each service includes its policies and role policies in upstream PMS shape.",
        "security": [
          {
            "pmsToken": []
          }
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control5"
              },
              "X-Quota-Daily-Remaining": {
                "$ref": "#/components/headers/X-Quota-Daily-Remaining4"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit2"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PmsServiceList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Response46"
          },
          "401": {
            "$ref": "#/components/responses/Response47"
          },
          "403": {
            "$ref": "#/components/responses/Response48"
          },
          "404": {
            "$ref": "#/components/responses/Response49"
          },
          "409": {
            "$ref": "#/components/responses/Response50"
          },
          "413": {
            "$ref": "#/components/responses/Response51"
          },
          "429": {
            "$ref": "#/components/responses/Response52"
          },
          "500": {
            "$ref": "#/components/responses/Response53"
          },
          "503": {
            "$ref": "#/components/responses/Response54"
          }
        }
      },
      "post": {
        "operationId": "pmsCreateService",
        "summary": "Create another service in the token's account",
        "description": "Token-authenticated Speedle PMS compatibility API (#250). Requires `Authorization: Bearer <token>`; session cookies and CSRF headers are never accepted. Order: bearer verification (401), fixed-scope authorization (403 insufficient_scope), per-token burst limit (429 rate_limited), account daily quota (429 daily_quota_exceeded or 503 quota_unavailable), then business validation. Every admitted non-5xx request consumes one PMS/ADS account quota unit, including reads and business rejections; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap, and auth, scope, routing, size, burst and reservation rejections are uncharged. Mutations and their audit rows commit atomically and republish the service snapshot. Never give manage mode (pms:write) to an agent governed by these policies: it can change the policies that decide its own access. Upstream POST /service. The management token must still resolve to its own bound service, which proves the credential is live; the new service joins the same account. An account may own up to 100 services with unique names: a duplicate name returns 409 service_exists and the technical cap returns 409 quota_exceeded with limit 100. Success stores the service, writes service.create audit and publishes its empty snapshot.",
        "security": [
          {
            "pmsToken": []
          }
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "description": "application/json only, at most 64 KiB. The body is upstream PMS JSON, not SPDL; it is rendered to one SPDL statement and re-parsed before writing. Reject unknown fields.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PmsServiceWrite"
              }
            }
          }
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/Response55"
          },
          "400": {
            "$ref": "#/components/responses/Response46"
          },
          "401": {
            "$ref": "#/components/responses/Response47"
          },
          "403": {
            "$ref": "#/components/responses/Response48"
          },
          "404": {
            "$ref": "#/components/responses/Response49"
          },
          "409": {
            "$ref": "#/components/responses/Response50"
          },
          "413": {
            "$ref": "#/components/responses/Response51"
          },
          "429": {
            "$ref": "#/components/responses/Response52"
          },
          "500": {
            "$ref": "#/components/responses/Response53"
          },
          "503": {
            "$ref": "#/components/responses/Response54"
          }
        }
      }
    },
    "/policy-mgmt/v1/service/{serviceName}": {
      "get": {
        "operationId": "pmsGetService",
        "summary": "Get the token's service",
        "description": "Token-authenticated Speedle PMS compatibility API (#250). Requires `Authorization: Bearer <token>`; session cookies and CSRF headers are never accepted. Order: bearer verification (401), fixed-scope authorization (403 insufficient_scope), per-token burst limit (429 rate_limited), account daily quota (429 daily_quota_exceeded or 503 quota_unavailable), then business validation. Every admitted non-5xx request consumes one PMS/ADS account quota unit, including reads and business rejections; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap, and auth, scope, routing, size, burst and reservation rejections are uncharged. Mutations and their audit rows commit atomically and republish the service snapshot. Never give manage mode (pms:write) to an agent governed by these policies: it can change the policies that decide its own access. Upstream GET /service/{serviceName}. serviceName must exactly equal the token's service name; anything else is 404 not_found, including another account's service.",
        "security": [
          {
            "pmsToken": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceName"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response55"
          },
          "400": {
            "$ref": "#/components/responses/Response46"
          },
          "401": {
            "$ref": "#/components/responses/Response47"
          },
          "403": {
            "$ref": "#/components/responses/Response48"
          },
          "404": {
            "$ref": "#/components/responses/Response49"
          },
          "409": {
            "$ref": "#/components/responses/Response50"
          },
          "413": {
            "$ref": "#/components/responses/Response51"
          },
          "429": {
            "$ref": "#/components/responses/Response52"
          },
          "500": {
            "$ref": "#/components/responses/Response53"
          },
          "503": {
            "$ref": "#/components/responses/Response54"
          }
        }
      },
      "delete": {
        "operationId": "pmsDeleteService",
        "summary": "Delete the token's service",
        "description": "Token-authenticated Speedle PMS compatibility API (#250). Requires `Authorization: Bearer <token>`; session cookies and CSRF headers are never accepted. Order: bearer verification (401), fixed-scope authorization (403 insufficient_scope), per-token burst limit (429 rate_limited), account daily quota (429 daily_quota_exceeded or 503 quota_unavailable), then business validation. Every admitted non-5xx request consumes one PMS/ADS account quota unit, including reads and business rejections; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap, and auth, scope, routing, size, burst and reservation rejections are uncharged. Mutations and their audit rows commit atomically and republish the service snapshot. Never give manage mode (pms:write) to an agent governed by these policies: it can change the policies that decide its own access. Upstream DELETE /service/{serviceName}. serviceName must exactly equal the token's service name. Deletes the service, its policies and its tokens, writes service.delete audit and removes the KV snapshot. Unlike the signed-in console route this token-authenticated command has no separate recent-authentication step; the management credential is the authorization. Never give manage mode to an agent governed by these policies.",
        "security": [
          {
            "pmsToken": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceName"
          }
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/Response56"
          },
          "400": {
            "$ref": "#/components/responses/Response46"
          },
          "401": {
            "$ref": "#/components/responses/Response47"
          },
          "403": {
            "$ref": "#/components/responses/Response48"
          },
          "404": {
            "$ref": "#/components/responses/Response49"
          },
          "409": {
            "$ref": "#/components/responses/Response50"
          },
          "413": {
            "$ref": "#/components/responses/Response51"
          },
          "429": {
            "$ref": "#/components/responses/Response52"
          },
          "500": {
            "$ref": "#/components/responses/Response53"
          },
          "503": {
            "$ref": "#/components/responses/Response54"
          }
        }
      }
    },
    "/policy-mgmt/v1/service/{serviceName}/policy": {
      "get": {
        "operationId": "pmsPoliciesList",
        "summary": "List policies",
        "description": "Token-authenticated Speedle PMS compatibility API (#250). Requires `Authorization: Bearer <token>`; session cookies and CSRF headers are never accepted. Order: bearer verification (401), fixed-scope authorization (403 insufficient_scope), per-token burst limit (429 rate_limited), account daily quota (429 daily_quota_exceeded or 503 quota_unavailable), then business validation. Every admitted non-5xx request consumes one PMS/ADS account quota unit, including reads and business rejections; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap, and auth, scope, routing, size, burst and reservation rejections are uncharged. Mutations and their audit rows commit atomically and republish the service snapshot. Never give manage mode (pms:write) to an agent governed by these policies: it can change the policies that decide its own access. Upstream GET /service/{serviceName}/policy. Returns the token service's stored authz policies in upstream PMS shape, including the Cloud policy ID used by GET and DELETE.",
        "security": [
          {
            "pmsToken": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceName"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control5"
              },
              "X-Quota-Daily-Remaining": {
                "$ref": "#/components/headers/X-Quota-Daily-Remaining4"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit2"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PmsPolicyList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Response46"
          },
          "401": {
            "$ref": "#/components/responses/Response47"
          },
          "403": {
            "$ref": "#/components/responses/Response48"
          },
          "404": {
            "$ref": "#/components/responses/Response49"
          },
          "409": {
            "$ref": "#/components/responses/Response50"
          },
          "413": {
            "$ref": "#/components/responses/Response51"
          },
          "429": {
            "$ref": "#/components/responses/Response52"
          },
          "500": {
            "$ref": "#/components/responses/Response53"
          },
          "503": {
            "$ref": "#/components/responses/Response54"
          }
        }
      },
      "post": {
        "operationId": "pmsPoliciesCreate",
        "summary": "Create a policy",
        "description": "Token-authenticated Speedle PMS compatibility API (#250). Requires `Authorization: Bearer <token>`; session cookies and CSRF headers are never accepted. Order: bearer verification (401), fixed-scope authorization (403 insufficient_scope), per-token burst limit (429 rate_limited), account daily quota (429 daily_quota_exceeded or 503 quota_unavailable), then business validation. Every admitted non-5xx request consumes one PMS/ADS account quota unit, including reads and business rejections; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap, and auth, scope, routing, size, burst and reservation rejections are uncharged. Mutations and their audit rows commit atomically and republish the service snapshot. Never give manage mode (pms:write) to an agent governed by these policies: it can change the policies that decide its own access. Upstream POST /service/{serviceName}/policy. The JSON body is rendered to one SPDL statement and parsed by the same WASM authz parser as the console; unrepresentable bodies and parser errors return 400 without writing. Success atomically stores the policy, increments the service revision and writes policy.create audit, then republishes the service snapshot (KV is eventually consistent, typically within 60 seconds). Duplicate names return 409 policy_exists; the 200-policy combined cap returns 409 quota_exceeded with limit 200.",
        "security": [
          {
            "pmsToken": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceName"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "application/json only, at most 64 KiB. The body is upstream PMS JSON, not SPDL; it is rendered to one SPDL statement and re-parsed before writing. Reject unknown fields.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PmsPolicyWrite"
              }
            }
          }
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/Response57"
          },
          "400": {
            "$ref": "#/components/responses/Response46"
          },
          "401": {
            "$ref": "#/components/responses/Response47"
          },
          "403": {
            "$ref": "#/components/responses/Response48"
          },
          "404": {
            "$ref": "#/components/responses/Response49"
          },
          "409": {
            "$ref": "#/components/responses/Response50"
          },
          "413": {
            "$ref": "#/components/responses/Response51"
          },
          "429": {
            "$ref": "#/components/responses/Response52"
          },
          "500": {
            "$ref": "#/components/responses/Response53"
          },
          "503": {
            "$ref": "#/components/responses/Response54"
          }
        }
      }
    },
    "/policy-mgmt/v1/service/{serviceName}/policy/{policyId}": {
      "get": {
        "operationId": "pmsPoliciesGet",
        "summary": "Get one policy",
        "description": "Token-authenticated Speedle PMS compatibility API (#250). Requires `Authorization: Bearer <token>`; session cookies and CSRF headers are never accepted. Order: bearer verification (401), fixed-scope authorization (403 insufficient_scope), per-token burst limit (429 rate_limited), account daily quota (429 daily_quota_exceeded or 503 quota_unavailable), then business validation. Every admitted non-5xx request consumes one PMS/ADS account quota unit, including reads and business rejections; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap, and auth, scope, routing, size, burst and reservation rejections are uncharged. Mutations and their audit rows commit atomically and republish the service snapshot. Never give manage mode (pms:write) to an agent governed by these policies: it can change the policies that decide its own access. Upstream GET /service/{serviceName}/policy/{policyID}. The ID is the Cloud canonical ULID (or a legacy UUIDv7), not a name. Unknown, malformed, wrong-kind or other-service IDs return the same 404.",
        "security": [
          {
            "pmsToken": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceName"
          },
          {
            "$ref": "#/components/parameters/policyId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response57"
          },
          "400": {
            "$ref": "#/components/responses/Response46"
          },
          "401": {
            "$ref": "#/components/responses/Response47"
          },
          "403": {
            "$ref": "#/components/responses/Response48"
          },
          "404": {
            "$ref": "#/components/responses/Response49"
          },
          "409": {
            "$ref": "#/components/responses/Response50"
          },
          "413": {
            "$ref": "#/components/responses/Response51"
          },
          "429": {
            "$ref": "#/components/responses/Response52"
          },
          "500": {
            "$ref": "#/components/responses/Response53"
          },
          "503": {
            "$ref": "#/components/responses/Response54"
          }
        }
      },
      "delete": {
        "operationId": "pmsPoliciesDelete",
        "summary": "Delete one policy",
        "description": "Token-authenticated Speedle PMS compatibility API (#250). Requires `Authorization: Bearer <token>`; session cookies and CSRF headers are never accepted. Order: bearer verification (401), fixed-scope authorization (403 insufficient_scope), per-token burst limit (429 rate_limited), account daily quota (429 daily_quota_exceeded or 503 quota_unavailable), then business validation. Every admitted non-5xx request consumes one PMS/ADS account quota unit, including reads and business rejections; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap, and auth, scope, routing, size, burst and reservation rejections are uncharged. Mutations and their audit rows commit atomically and republish the service snapshot. Never give manage mode (pms:write) to an agent governed by these policies: it can change the policies that decide its own access. Upstream DELETE /service/{serviceName}/policy/{policyID}. Deletes only an owned authz policy with this canonical ID; success is 204, and unknown/malformed/wrong-kind/other-service IDs return 404. The delete and policy.delete audit commit atomically, the revision increments and the snapshot is republished.",
        "security": [
          {
            "pmsToken": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceName"
          },
          {
            "$ref": "#/components/parameters/policyId"
          }
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/Response56"
          },
          "400": {
            "$ref": "#/components/responses/Response46"
          },
          "401": {
            "$ref": "#/components/responses/Response47"
          },
          "403": {
            "$ref": "#/components/responses/Response48"
          },
          "404": {
            "$ref": "#/components/responses/Response49"
          },
          "409": {
            "$ref": "#/components/responses/Response50"
          },
          "413": {
            "$ref": "#/components/responses/Response51"
          },
          "429": {
            "$ref": "#/components/responses/Response52"
          },
          "500": {
            "$ref": "#/components/responses/Response53"
          },
          "503": {
            "$ref": "#/components/responses/Response54"
          }
        }
      }
    },
    "/policy-mgmt/v1/service/{serviceName}/role-policy": {
      "get": {
        "operationId": "pmsRolePoliciesList",
        "summary": "List role policies",
        "description": "Token-authenticated Speedle PMS compatibility API (#250). Requires `Authorization: Bearer <token>`; session cookies and CSRF headers are never accepted. Order: bearer verification (401), fixed-scope authorization (403 insufficient_scope), per-token burst limit (429 rate_limited), account daily quota (429 daily_quota_exceeded or 503 quota_unavailable), then business validation. Every admitted non-5xx request consumes one PMS/ADS account quota unit, including reads and business rejections; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap, and auth, scope, routing, size, burst and reservation rejections are uncharged. Mutations and their audit rows commit atomically and republish the service snapshot. Never give manage mode (pms:write) to an agent governed by these policies: it can change the policies that decide its own access. Upstream GET /service/{serviceName}/role-policy. Returns the token service's stored role policies in upstream PMS shape, including the Cloud policy ID used by GET and DELETE.",
        "security": [
          {
            "pmsToken": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceName"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control5"
              },
              "X-Quota-Daily-Remaining": {
                "$ref": "#/components/headers/X-Quota-Daily-Remaining4"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit2"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PmsRolePolicyList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Response46"
          },
          "401": {
            "$ref": "#/components/responses/Response47"
          },
          "403": {
            "$ref": "#/components/responses/Response48"
          },
          "404": {
            "$ref": "#/components/responses/Response49"
          },
          "409": {
            "$ref": "#/components/responses/Response50"
          },
          "413": {
            "$ref": "#/components/responses/Response51"
          },
          "429": {
            "$ref": "#/components/responses/Response52"
          },
          "500": {
            "$ref": "#/components/responses/Response53"
          },
          "503": {
            "$ref": "#/components/responses/Response54"
          }
        }
      },
      "post": {
        "operationId": "pmsRolePoliciesCreate",
        "summary": "Create a role policy",
        "description": "Token-authenticated Speedle PMS compatibility API (#250). Requires `Authorization: Bearer <token>`; session cookies and CSRF headers are never accepted. Order: bearer verification (401), fixed-scope authorization (403 insufficient_scope), per-token burst limit (429 rate_limited), account daily quota (429 daily_quota_exceeded or 503 quota_unavailable), then business validation. Every admitted non-5xx request consumes one PMS/ADS account quota unit, including reads and business rejections; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap, and auth, scope, routing, size, burst and reservation rejections are uncharged. Mutations and their audit rows commit atomically and republish the service snapshot. Never give manage mode (pms:write) to an agent governed by these policies: it can change the policies that decide its own access. Upstream POST /service/{serviceName}/role-policy. The JSON body is rendered to one SPDL statement and parsed by the same WASM role parser as the console; unrepresentable bodies and parser errors return 400 without writing. Success atomically stores the policy, increments the service revision and writes policy.create audit, then republishes the service snapshot (KV is eventually consistent, typically within 60 seconds). Duplicate names return 409 policy_exists; the 200-policy combined cap returns 409 quota_exceeded with limit 200.",
        "security": [
          {
            "pmsToken": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceName"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "application/json only, at most 64 KiB. The body is upstream PMS JSON, not SPDL; it is rendered to one SPDL statement and re-parsed before writing. Reject unknown fields.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PmsRolePolicyWrite"
              }
            }
          }
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/Response58"
          },
          "400": {
            "$ref": "#/components/responses/Response46"
          },
          "401": {
            "$ref": "#/components/responses/Response47"
          },
          "403": {
            "$ref": "#/components/responses/Response48"
          },
          "404": {
            "$ref": "#/components/responses/Response49"
          },
          "409": {
            "$ref": "#/components/responses/Response50"
          },
          "413": {
            "$ref": "#/components/responses/Response51"
          },
          "429": {
            "$ref": "#/components/responses/Response52"
          },
          "500": {
            "$ref": "#/components/responses/Response53"
          },
          "503": {
            "$ref": "#/components/responses/Response54"
          }
        }
      }
    },
    "/policy-mgmt/v1/service/{serviceName}/role-policy/{policyId}": {
      "get": {
        "operationId": "pmsRolePoliciesGet",
        "summary": "Get one role policy",
        "description": "Token-authenticated Speedle PMS compatibility API (#250). Requires `Authorization: Bearer <token>`; session cookies and CSRF headers are never accepted. Order: bearer verification (401), fixed-scope authorization (403 insufficient_scope), per-token burst limit (429 rate_limited), account daily quota (429 daily_quota_exceeded or 503 quota_unavailable), then business validation. Every admitted non-5xx request consumes one PMS/ADS account quota unit, including reads and business rejections; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap, and auth, scope, routing, size, burst and reservation rejections are uncharged. Mutations and their audit rows commit atomically and republish the service snapshot. Never give manage mode (pms:write) to an agent governed by these policies: it can change the policies that decide its own access. Upstream GET /service/{serviceName}/role-policy/{policyID}. The ID is the Cloud canonical ULID (or a legacy UUIDv7), not a name. Unknown, malformed, wrong-kind or other-service IDs return the same 404.",
        "security": [
          {
            "pmsToken": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceName"
          },
          {
            "$ref": "#/components/parameters/policyId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response58"
          },
          "400": {
            "$ref": "#/components/responses/Response46"
          },
          "401": {
            "$ref": "#/components/responses/Response47"
          },
          "403": {
            "$ref": "#/components/responses/Response48"
          },
          "404": {
            "$ref": "#/components/responses/Response49"
          },
          "409": {
            "$ref": "#/components/responses/Response50"
          },
          "413": {
            "$ref": "#/components/responses/Response51"
          },
          "429": {
            "$ref": "#/components/responses/Response52"
          },
          "500": {
            "$ref": "#/components/responses/Response53"
          },
          "503": {
            "$ref": "#/components/responses/Response54"
          }
        }
      },
      "delete": {
        "operationId": "pmsRolePoliciesDelete",
        "summary": "Delete one role policy",
        "description": "Token-authenticated Speedle PMS compatibility API (#250). Requires `Authorization: Bearer <token>`; session cookies and CSRF headers are never accepted. Order: bearer verification (401), fixed-scope authorization (403 insufficient_scope), per-token burst limit (429 rate_limited), account daily quota (429 daily_quota_exceeded or 503 quota_unavailable), then business validation. Every admitted non-5xx request consumes one PMS/ADS account quota unit, including reads and business rejections; a Cloud-side handler 5xx is refunded only when the independent relief ledger approves it within the per-account daily cap, and auth, scope, routing, size, burst and reservation rejections are uncharged. Mutations and their audit rows commit atomically and republish the service snapshot. Never give manage mode (pms:write) to an agent governed by these policies: it can change the policies that decide its own access. Upstream DELETE /service/{serviceName}/role-policy/{policyID}. Deletes only an owned role policy with this canonical ID; success is 204, and unknown/malformed/wrong-kind/other-service IDs return 404. The delete and policy.delete audit commit atomically, the revision increments and the snapshot is republished.",
        "security": [
          {
            "pmsToken": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/serviceName"
          },
          {
            "$ref": "#/components/parameters/policyId"
          }
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/Response56"
          },
          "400": {
            "$ref": "#/components/responses/Response46"
          },
          "401": {
            "$ref": "#/components/responses/Response47"
          },
          "403": {
            "$ref": "#/components/responses/Response48"
          },
          "404": {
            "$ref": "#/components/responses/Response49"
          },
          "409": {
            "$ref": "#/components/responses/Response50"
          },
          "413": {
            "$ref": "#/components/responses/Response51"
          },
          "429": {
            "$ref": "#/components/responses/Response52"
          },
          "500": {
            "$ref": "#/components/responses/Response53"
          },
          "503": {
            "$ref": "#/components/responses/Response54"
          }
        }
      }
    },
    "/api/service/import": {
      "post": {
        "operationId": "importServicePolicies",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "description": "Import the existing lossless JSON export FILE into the session account's oldest service (legacy default-service route). The client validates that unchanged file locally using ServiceImportFile and projects only policies[].{kind,name,spdl} into the HTTP ServiceImportRequest; never upload the file's redundant ASTs, service metadata, IDs or timestamps. This source-only wire DTO is not an alternative file format. Reject metadata/AST fields at the HTTP boundary before engine or D1 work. Never overwrite, rename or delete existing policies or select a target from exported metadata. Reparse and revalidate every SPDL and assign fresh server ULID IDs. Names retain exact case-sensitive (kind,name) semantics; opposite kinds may share a name. Any item error rejects the entire batch; report one error per failing zero-based index in ascending order, parse before duplicate before existing-name conflict before quota. The combined existing+imported authz/role cap is 200. One D1 transaction/batch rechecks ownership, active session, conflicts and combined cap and atomically commits all policy rows, one revision increment, one audit event and the idempotency receipt. A guarded SQL no-op is not a rollback. Concurrent requests cannot bypass these guards or duplicate a key. Idempotency-Key is scoped to the authenticated account and bound on first successful commit to its target service and SHA-256 of the ordered canonical [kind,name,spdl] tuples; file metadata/AST/timestamps are discarded locally; JSON whitespace/property order do not affect the digest. Successful receipts (digest, service ID, immutable counts/revision and publication state; no source or exported metadata) remain until account deletion, so a key never expires into a second mutation. Failed uncommitted requests do not reserve the key. A replay still requires session+CSRF, consumes one management call and returns the original result; a different source projection, a different target service or a replaced service returns 409 idempotency_conflict. Normal success publishes the service snapshot once after commit. D1 and KV are not transactional: after a publication failure or an unknown/crashed publication state, replay repairs from the current committed service snapshot, never the historical import, with no repeated insert/audit/revision. A completed publication replay needs no new KV write. Exact POST path only: authenticate/CSRF/burst, bounded body admission, daily quota, then business validation. Other paths/methods retain 65536 bytes. No query parameters, bearer management, SPDL-text import, staging/chunk protocol or runtime/deployment claim in this contract PR.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Origin2"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF2"
          },
          {
            "$ref": "#/components/parameters/Idempotency-Key"
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody9"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response59"
          },
          "400": {
            "$ref": "#/components/responses/Response60"
          },
          "401": {
            "$ref": "#/components/responses/Response61"
          },
          "403": {
            "$ref": "#/components/responses/Response62"
          },
          "404": {
            "$ref": "#/components/responses/Response63"
          },
          "409": {
            "$ref": "#/components/responses/Response64"
          },
          "413": {
            "$ref": "#/components/responses/Response65"
          },
          "429": {
            "$ref": "#/components/responses/Response66"
          },
          "500": {
            "$ref": "#/components/responses/Response67"
          },
          "503": {
            "$ref": "#/components/responses/Response68"
          }
        }
      }
    },
    "/api/services/{serviceId}/import": {
      "post": {
        "operationId": "importServicePoliciesResource",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "description": "Import the existing lossless JSON export FILE into the service selected by serviceId, which must belong to the signed-in account. The client validates that unchanged file locally using ServiceImportFile and projects only policies[].{kind,name,spdl} into the HTTP ServiceImportRequest; never upload the file's redundant ASTs, service metadata, IDs or timestamps. This source-only wire DTO is not an alternative file format. Reject metadata/AST fields at the HTTP boundary before engine or D1 work. Never overwrite, rename or delete existing policies or select a target from exported metadata. Reparse and revalidate every SPDL and assign fresh server ULID IDs. Names retain exact case-sensitive (kind,name) semantics; opposite kinds may share a name. Any item error rejects the entire batch; report one error per failing zero-based index in ascending order, parse before duplicate before existing-name conflict before quota. The combined existing+imported authz/role cap is 200. One D1 transaction/batch rechecks ownership, active session, conflicts and combined cap and atomically commits all policy rows, one revision increment, one audit event and the idempotency receipt. A guarded SQL no-op is not a rollback. Concurrent requests cannot bypass these guards or duplicate a key. Idempotency-Key is scoped to the authenticated account and bound on first successful commit to its target service and SHA-256 of the ordered canonical [kind,name,spdl] tuples; file metadata/AST/timestamps are discarded locally; JSON whitespace/property order do not affect the digest. Successful receipts (digest, service ID, immutable counts/revision and publication state; no source or exported metadata) remain until account deletion, so a key never expires into a second mutation. Failed uncommitted requests do not reserve the key. A replay still requires session+CSRF, consumes one management call and returns the original result; a different source projection, a different target service or a replaced service returns 409 idempotency_conflict. Normal success publishes the service snapshot once after commit. D1 and KV are not transactional: after a publication failure or an unknown/crashed publication state, replay repairs from the current committed service snapshot, never the historical import, with no repeated insert/audit/revision. A completed publication replay needs no new KV write. Exact POST path only: authenticate/CSRF/burst, bounded body admission, daily quota, then business validation. Other paths/methods retain 65536 bytes. No query parameters, bearer management, SPDL-text import, staging/chunk protocol or runtime/deployment claim in this contract PR.",
        "parameters": [
          {
            "name": "serviceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Service owned by the signed-in account. Unknown, malformed or other-account IDs return 404 not_found."
          },
          {
            "$ref": "#/components/parameters/Origin2"
          },
          {
            "$ref": "#/components/parameters/X-SC-CSRF2"
          },
          {
            "$ref": "#/components/parameters/Idempotency-Key"
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/RequestBody9"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Response59"
          },
          "400": {
            "$ref": "#/components/responses/Response60"
          },
          "401": {
            "$ref": "#/components/responses/Response61"
          },
          "403": {
            "$ref": "#/components/responses/Response62"
          },
          "404": {
            "$ref": "#/components/responses/Response63"
          },
          "409": {
            "$ref": "#/components/responses/Response64"
          },
          "413": {
            "$ref": "#/components/responses/Response65"
          },
          "429": {
            "$ref": "#/components/responses/Response66"
          },
          "500": {
            "$ref": "#/components/responses/Response67"
          },
          "503": {
            "$ref": "#/components/responses/Response68"
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "operationId": "health",
        "summary": "Read API liveness and the sealed build version",
        "description": "Returns Worker liveness only; it does not check D1, KV, the decision engine or quota. The version identifies the sealed build. This endpoint is public and creates no data.",
        "responses": {
          "200": {
            "description": "The API Worker is running.",
            "headers": {
              "Cache-Control": {
                "description": "Liveness and build metadata are never cached.",
                "schema": {
                  "type": "string",
                  "const": "no-store"
                },
                "example": "no-store"
              },
              "X-Request-Id": {
                "description": "Server-generated request identifier; a client-supplied value is never trusted.",
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "PublicConfigResponse": {
        "type": "object",
        "properties": {
          "signInEnabled": {
            "type": "boolean"
          }
        },
        "required": [
          "signInEnabled"
        ],
        "additionalProperties": false,
        "description": "Public runtime settings only; no-store. Missing or invalid server switch fails closed.",
        "examples": [
          {
            "signInEnabled": true
          },
          {
            "signInEnabled": false
          }
        ]
      },
      "OAuthBrowserLocation": {
        "type": "string",
        "pattern": "^\\/auth\\/result#[^\\s]+$",
        "description": "Fixed /auth/result with an encodeURIComponent(JSON.stringify(OAuthBrowserResult)) fragment. Only public outcome/error and collision provider names; never code, state, identity or credentials. Console validates and immediately replaces this URL. HTML callbacks use this destination for sign-in success/errors; new accounts retain the fixed confirm page. Link/reauth callbacks instead load the fixed provider completion page before a same-origin POST; they cannot report connected/reauthenticated before that POST succeeds. JSON callback clients use the same fixed completion destination for link/reauth and retain error statuses/envelopes.",
        "examples": [
          "/auth/result#%7B%22outcome%22%3A%22signed_in%22%7D"
        ]
      },
      "AuthProvider": {
        "type": "string",
        "enum": [
          "github",
          "google"
        ],
        "examples": [
          "github",
          "google"
        ]
      },
      "AuthSuccessResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          }
        },
        "required": [
          "ok"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "ok": true
          }
        ]
      },
      "OAuthConnectRequest": {
        "type": "object",
        "properties": {},
        "additionalProperties": false,
        "examples": [
          {}
        ]
      },
      "OAuthReauthRequest": {
        "type": "object",
        "properties": {},
        "additionalProperties": false,
        "examples": [
          {}
        ]
      },
      "OAuthCompleteRequest": {
        "type": "object",
        "properties": {},
        "additionalProperties": false,
        "description": "Same-origin POST after the fixed completion page has loaded. Require the initiating Strict session cookie, the matching provider/purpose completion cookie, exact current-stage Origin and X-SC-CSRF: 1. No query parameters or client-supplied intent, session, identity, credential, timestamp or redirect. Schemas alone do not verify these bindings.",
        "examples": [
          {}
        ]
      },
      "OAuthCompleteResponse": {
        "type": "object",
        "properties": {
          "outcome": {
            "type": "string",
            "enum": [
              "connected",
              "reauthenticated"
            ]
          }
        },
        "required": [
          "outcome"
        ],
        "additionalProperties": false,
        "description": "Returned only after atomic one-time completion for the signed intent. Display-only outcome, never authentication proof; the console reads /api/me before showing success or resuming a form. Never automatically retry a sensitive action.",
        "examples": [
          {
            "outcome": "connected"
          },
          {
            "outcome": "reauthenticated"
          }
        ]
      },
      "EmptyAuthRequest": {
        "type": "object",
        "properties": {},
        "additionalProperties": false,
        "examples": [
          {}
        ]
      },
      "DeleteAccountRequest": {
        "type": "object",
        "properties": {},
        "additionalProperties": false,
        "description": "Explicit empty JSON body. Authorization is the current active session with server-side auth_time within 10 minutes, never client-supplied OTPs, timestamps or bearer proofs.",
        "examples": [
          {}
        ]
      },
      "OAuthStartQuery": {
        "type": "object",
        "properties": {},
        "additionalProperties": false,
        "examples": [
          {}
        ]
      },
      "OAuthCallbackQuery": {
        "anyOf": [
          {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2048,
                "pattern": "^\\S+$"
              },
              "state": {
                "type": "string",
                "pattern": "^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$"
              }
            },
            "required": [
              "code",
              "state"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "minLength": 1,
                "maxLength": 128,
                "pattern": "^[a-zA-Z0-9_]+$"
              },
              "state": {
                "type": "string",
                "pattern": "^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$"
              },
              "error_description": {
                "type": "string",
                "maxLength": 1024
              },
              "error_uri": {
                "type": "string",
                "maxLength": 2048,
                "format": "uri"
              }
            },
            "required": [
              "error",
              "state"
            ],
            "additionalProperties": false
          }
        ],
        "description": "Canonical callback core after provider-adapter metadata validation, without coercion. Reject every duplicate query key before normalization. Real callbacks require the path provider's exact issuer on success and denial: GitHub https://github.com/login/oauth, Google https://accounts.google.com. Remove only the validated issuer and allowlisted Google scope/authuser/prompt/hd metadata before constructing this object. Beta fake callbacks use this core directly and accept no issuer or provider metadata. Provider error text/URLs are untrusted and must never be rendered, followed or logged.",
        "examples": [
          {
            "code": "synthetic-code",
            "state": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
          },
          {
            "error": "access_denied",
            "state": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
          }
        ]
      },
      "GitHubOAuthCallbackTransport": {
        "anyOf": [
          {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2048,
                "pattern": "^\\S+$"
              },
              "state": {
                "type": "string",
                "pattern": "^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$"
              },
              "iss": {
                "type": "string",
                "const": "https://github.com/login/oauth"
              }
            },
            "required": [
              "code",
              "state",
              "iss"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "minLength": 1,
                "maxLength": 128,
                "pattern": "^[a-zA-Z0-9_]+$"
              },
              "state": {
                "type": "string",
                "pattern": "^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$"
              },
              "error_description": {
                "type": "string",
                "maxLength": 1024
              },
              "error_uri": {
                "type": "string",
                "maxLength": 2048,
                "format": "uri"
              },
              "iss": {
                "type": "string",
                "const": "https://github.com/login/oauth"
              }
            },
            "required": [
              "error",
              "state",
              "iss"
            ],
            "additionalProperties": false
          }
        ],
        "description": "Raw real-GitHub callback query before adapter normalization. Both success and denial require the exact decoded iss https://github.com/login/oauth. Reject every duplicate query key before constructing this object. Unknown fields remain forbidden. This transport schema does not apply to Google or the beta fake provider; canonical OAuthCallbackQuery remains issuer-free after validation.",
        "examples": [
          {
            "code": "synthetic-code",
            "state": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
            "iss": "https://github.com/login/oauth"
          },
          {
            "error": "access_denied",
            "state": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
            "iss": "https://github.com/login/oauth"
          }
        ]
      },
      "GoogleOAuthCallbackTransport": {
        "anyOf": [
          {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "minLength": 1,
                "maxLength": 2048,
                "pattern": "^\\S+$"
              },
              "state": {
                "type": "string",
                "pattern": "^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$"
              },
              "iss": {
                "type": "string",
                "const": "https://accounts.google.com"
              },
              "scope": {
                "type": "string"
              },
              "authuser": {
                "type": "string"
              },
              "prompt": {
                "type": "string"
              },
              "hd": {
                "description": "Optional hosted-domain metadata: ASCII DNS hostname, at most 253 characters, labels at most 63 characters; no URL, email, wildcard or trailing dot. Discard after validation. Never grants authority, verifies email, selects an account or replaces ID-token claims.",
                "type": "string",
                "maxLength": 253,
                "allOf": [
                  {
                    "pattern": "^(?=.{1,253}\\.?$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[-0-9a-zA-Z]{0,61}[0-9a-zA-Z])?)*\\.?$"
                  },
                  {
                    "pattern": "[A-Za-z0-9]$(?![\\s\\S])"
                  }
                ]
              }
            },
            "required": [
              "code",
              "state",
              "iss"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "minLength": 1,
                "maxLength": 128,
                "pattern": "^[a-zA-Z0-9_]+$"
              },
              "state": {
                "type": "string",
                "pattern": "^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$"
              },
              "error_description": {
                "type": "string",
                "maxLength": 1024
              },
              "error_uri": {
                "type": "string",
                "maxLength": 2048,
                "format": "uri"
              },
              "iss": {
                "type": "string",
                "const": "https://accounts.google.com"
              },
              "scope": {
                "type": "string"
              },
              "authuser": {
                "type": "string"
              },
              "prompt": {
                "type": "string"
              },
              "hd": {
                "description": "Optional hosted-domain metadata: ASCII DNS hostname, at most 253 characters, labels at most 63 characters; no URL, email, wildcard or trailing dot. Discard after validation. Never grants authority, verifies email, selects an account or replaces ID-token claims.",
                "type": "string",
                "maxLength": 253,
                "allOf": [
                  {
                    "pattern": "^(?=.{1,253}\\.?$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[-0-9a-zA-Z]{0,61}[0-9a-zA-Z])?)*\\.?$"
                  },
                  {
                    "pattern": "[A-Za-z0-9]$(?![\\s\\S])"
                  }
                ]
              }
            },
            "required": [
              "error",
              "state",
              "iss"
            ],
            "additionalProperties": false
          }
        ],
        "description": "Raw real-Google callback query. Both success and denial require decoded iss exactly https://accounts.google.com; reject missing issuer, legacy ID-token issuer accounts.google.com, other providers and URL variants without normalization. Reject duplicate keys before object construction. Validate optional hd as a bounded DNS hostname, then discard it together with optional scope/authuser/prompt and the validated issuer before strict canonical validation. Unknown fields remain forbidden. Auxiliary values have no authority and are never rendered, followed or logged. Beta fake callbacks use the issuer-free canonical protocol.",
        "examples": [
          {
            "code": "synthetic-code",
            "state": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
            "iss": "https://accounts.google.com"
          },
          {
            "code": "synthetic-code",
            "state": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
            "iss": "https://accounts.google.com",
            "scope": "openid email",
            "authuser": "0",
            "prompt": "consent",
            "hd": "example.com"
          },
          {
            "error": "access_denied",
            "state": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
            "iss": "https://accounts.google.com"
          }
        ]
      },
      "MeResponse": {
        "type": "object",
        "properties": {
          "id": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 26,
                "maxLength": 26,
                "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
              },
              {
                "type": "string",
                "minLength": 36,
                "maxLength": 36,
                "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
              }
            ]
          },
          "email": {
            "type": "string",
            "maxLength": 254,
            "format": "email",
            "pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
          },
          "plan": {
            "type": "string",
            "const": "free"
          },
          "identities": {
            "minItems": 1,
            "maxItems": 2,
            "type": "array",
            "items": {
              "oneOf": [
                {
                  "type": "object",
                  "properties": {
                    "provider": {
                      "type": "string",
                      "const": "github"
                    },
                    "providerUserId": {
                      "type": "string",
                      "pattern": "^[1-9][0-9]*$"
                    },
                    "providerLogin": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 128
                    }
                  },
                  "required": [
                    "provider",
                    "providerUserId",
                    "providerLogin"
                  ],
                  "additionalProperties": false
                },
                {
                  "type": "object",
                  "properties": {
                    "provider": {
                      "type": "string",
                      "const": "google"
                    },
                    "providerUserId": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 255
                    },
                    "providerLogin": {
                      "type": "null"
                    }
                  },
                  "required": [
                    "provider",
                    "providerUserId",
                    "providerLogin"
                  ],
                  "additionalProperties": false
                }
              ]
            }
          },
          "dailyQuota": {
            "type": "object",
            "properties": {
              "used": {
                "type": "integer",
                "minimum": 0,
                "maximum": 10000
              },
              "limit": {
                "type": "number",
                "const": 10000
              },
              "resetsAt": {
                "type": "integer",
                "minimum": 0,
                "maximum": 8640000000000000,
                "multipleOf": 86400000
              }
            },
            "required": [
              "used",
              "limit",
              "resetsAt"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "id",
          "email",
          "plan",
          "identities",
          "dailyQuota"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "id": "01K94T00000000000000000001",
            "email": "alice@example.com",
            "plan": "free",
            "identities": [
              {
                "provider": "github",
                "providerUserId": "12345",
                "providerLogin": "alice"
              },
              {
                "provider": "google",
                "providerUserId": "google-subject",
                "providerLogin": null
              }
            ],
            "dailyQuota": {
              "used": 32,
              "limit": 10000,
              "resetsAt": 1793750400000
            }
          }
        ]
      },
      "AuditQuery": {
        "type": "object",
        "properties": {
          "limit": {
            "description": "Page size as a canonical decimal query string, 1–100; omitted means 50. Must match the initial page size when continuing a cursor.",
            "type": "string",
            "pattern": "^(?:[1-9]|[1-9][0-9]|100)$"
          },
          "cursor": {
            "type": "string",
            "minLength": 32,
            "maxLength": 1024,
            "pattern": "^[A-Za-z0-9_-]+$",
            "description": "Opaque base64url continuation token, authenticated by the server and bound to the current user, page size, initial upper (time,id) bound and last returned (time,id). Expires 15 minutes after the first page, without renewal. Clients must pass it unchanged; syntax validation does not verify authenticity or expiry."
          }
        },
        "additionalProperties": false,
        "description": "Raw query strings, without coercion. Reject duplicate keys before object construction; no user/account selector, offset, sort or other parameters are accepted.",
        "examples": [
          {},
          {
            "limit": "1"
          },
          {
            "limit": "1",
            "cursor": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
          }
        ]
      },
      "AuditResponse": {
        "type": "object",
        "properties": {
          "entries": {
            "maxItems": 100,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "anyOf": [
                    {
                      "type": "string",
                      "minLength": 26,
                      "maxLength": 26,
                      "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
                    },
                    {
                      "type": "string",
                      "minLength": 36,
                      "maxLength": 36,
                      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
                    }
                  ]
                },
                "action": {
                  "type": "string",
                  "enum": [
                    "user.register",
                    "user.delete",
                    "auth.login",
                    "auth.logout",
                    "auth.logout_all",
                    "identity.link",
                    "identity.unlink",
                    "service.create",
                    "service.update",
                    "service.delete",
                    "policy.create",
                    "policy.update",
                    "policy.delete",
                    "token.create",
                    "token.rotate",
                    "token.revoke"
                  ]
                },
                "target": {
                  "anyOf": [
                    {
                      "anyOf": [
                        {
                          "type": "string",
                          "minLength": 26,
                          "maxLength": 26,
                          "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
                        },
                        {
                          "type": "string",
                          "minLength": 36,
                          "maxLength": 36,
                          "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
                        }
                      ]
                    },
                    {
                      "type": "null"
                    }
                  ],
                  "description": "Public internal resource/account ID (ULID, or legacy UUIDv7), or null when unavailable. Never a name, email, provider subject, API token prefix or credential."
                },
                "time": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 8640000000000000
                },
                "context": {
                  "type": "object",
                  "properties": {
                    "outcome": {
                      "type": "string",
                      "enum": [
                        "success",
                        "failure"
                      ]
                    },
                    "provider": {
                      "type": "string",
                      "enum": [
                        "github",
                        "google"
                      ]
                    }
                  },
                  "required": [
                    "outcome"
                  ],
                  "additionalProperties": false,
                  "description": "Allowlisted coarse context only. Never copy database detail, IP/hash, email/hash, browser/user-agent strings, provider subjects/logins, credentials or request payloads into this object."
                }
              },
              "required": [
                "id",
                "action",
                "target",
                "time",
                "context"
              ],
              "additionalProperties": false
            }
          },
          "nextCursor": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 32,
                "maxLength": 1024,
                "pattern": "^[A-Za-z0-9_-]+$",
                "description": "Opaque base64url continuation token, authenticated by the server and bound to the current user, page size, initial upper (time,id) bound and last returned (time,id). Expires 15 minutes after the first page, without renewal. Clients must pass it unchanged; syntax validation does not verify authenticity or expiry."
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "entries",
          "nextCursor"
        ],
        "additionalProperties": false,
        "description": "Own-account entries only, ordered by time DESC then id DESC, with unique IDs. Return at most the requested limit. nextCursor is null when no further records exist; an empty result has entries: [] and nextCursor: null. Runtime must enforce ordering, ownership, cursor bounds and page-size consistency; JSON Schema cannot express all cross-field constraints.",
        "examples": [
          {
            "entries": [
              {
                "id": "01K94T00000000000000000001",
                "action": "identity.link",
                "target": "01K94T00000000000000000001",
                "time": 1793664000000,
                "context": {
                  "outcome": "success",
                  "provider": "github"
                }
              }
            ],
            "nextCursor": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
          },
          {
            "entries": [
              {
                "id": "01K94T00000000000000000001",
                "action": "identity.link",
                "target": "01K94T00000000000000000001",
                "time": 1793664000000,
                "context": {
                  "outcome": "success",
                  "provider": "github"
                }
              }
            ],
            "nextCursor": null
          },
          {
            "entries": [],
            "nextCursor": null
          }
        ]
      },
      "AuthErrorResponse": {
        "anyOf": [
          {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "enum": [
                  "sign_in_disabled",
                  "sign_in_restricted",
                  "provider_denied",
                  "invalid_request",
                  "request_too_large",
                  "rate_limited",
                  "unauthorized",
                  "csrf_failed",
                  "invalid_oauth_state",
                  "invalid_pending_credential",
                  "provider_unavailable",
                  "verified_email_required",
                  "identity_conflict",
                  "reauth_required",
                  "last_identity",
                  "daily_quota_exceeded",
                  "quota_unavailable",
                  "not_found",
                  "internal_error"
                ]
              }
            },
            "required": [
              "error"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "const": "email_in_use"
              },
              "providers": {
                "minItems": 1,
                "maxItems": 2,
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "github",
                    "google"
                  ]
                }
              }
            },
            "required": [
              "error",
              "providers"
            ],
            "additionalProperties": false,
            "description": "Only after a verified provider callback or create confirmation collision. Show these sign-in providers and instruct the user to sign in, then connect in Settings. Never include email, user id or identity details."
          },
          {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "const": "quota_exceeded"
              },
              "limit": {
                "type": "number",
                "const": 200
              }
            },
            "required": [
              "error",
              "limit"
            ],
            "additionalProperties": false
          }
        ],
        "examples": [
          {
            "error": "sign_in_disabled"
          },
          {
            "error": "sign_in_restricted"
          },
          {
            "error": "provider_denied"
          },
          {
            "error": "invalid_request"
          },
          {
            "error": "request_too_large"
          },
          {
            "error": "rate_limited"
          },
          {
            "error": "unauthorized"
          },
          {
            "error": "csrf_failed"
          },
          {
            "error": "invalid_oauth_state"
          },
          {
            "error": "invalid_pending_credential"
          },
          {
            "error": "provider_unavailable"
          },
          {
            "error": "verified_email_required"
          },
          {
            "error": "identity_conflict"
          },
          {
            "error": "reauth_required"
          },
          {
            "error": "last_identity"
          },
          {
            "error": "email_in_use",
            "providers": [
              "github"
            ]
          },
          {
            "error": "quota_exceeded",
            "limit": 200
          },
          {
            "error": "daily_quota_exceeded"
          },
          {
            "error": "quota_unavailable"
          },
          {
            "error": "not_found"
          },
          {
            "error": "internal_error"
          }
        ]
      },
      "RetryAfter": {
        "type": "string",
        "pattern": "^[1-9][0-9]*$",
        "description": "Required on every 429: positive integer delay-seconds. For daily_quota_exceeded, ceil((next UTC midnight - now)/1000).",
        "examples": [
          "60",
          "86400"
        ]
      },
      "OAuthStartLocation": {
        "type": "string",
        "pattern": "^https:\\/\\/(github\\.com\\/login\\/oauth\\/authorize|accounts\\.google\\.com\\/o\\/oauth2\\/v2\\/auth)\\?[^\\s]+$",
        "examples": [
          "https://github.com/login/oauth/authorize?client_id=example&redirect_uri=https%3A%2F%2Fcloud.speedle.io%2Fapi%2Fauth%2Fgithub%2Fcallback&response_type=code&scope=read%3Auser%20user%3Aemail&state=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&code_challenge=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&code_challenge_method=S256",
          "https://accounts.google.com/o/oauth2/v2/auth?client_id=example&redirect_uri=https%3A%2F%2Fcloud.speedle.io%2Fapi%2Fauth%2Fgoogle%2Fcallback&response_type=code&scope=openid%20email&state=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&code_challenge=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&code_challenge_method=S256&nonce=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
        ]
      },
      "OAuthStartResponse": {
        "type": "object",
        "properties": {
          "authorizationUrl": {
            "type": "string",
            "pattern": "^https:\\/\\/(github\\.com\\/login\\/oauth\\/authorize|accounts\\.google\\.com\\/o\\/oauth2\\/v2\\/auth)\\?[^\\s]+$"
          }
        },
        "required": [
          "authorizationUrl"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "authorizationUrl": "https://github.com/login/oauth/authorize?client_id=example&state=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&code_challenge=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&code_challenge_method=S256"
          }
        ]
      },
      "OAuthCallbackLocation": {
        "type": "string",
        "enum": [
          "/",
          "/auth/github/confirm",
          "/auth/google/confirm",
          "/auth/github/complete",
          "/auth/google/complete"
        ],
        "examples": [
          "/",
          "/auth/github/confirm",
          "/auth/google/confirm",
          "/auth/github/complete",
          "/auth/google/complete"
        ]
      },
      "ServiceWrite": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$"
          }
        },
        "required": [
          "name"
        ],
        "additionalProperties": false
      },
      "ServiceTestRequest": {
        "type": "object",
        "properties": {
          "op": {
            "type": "string",
            "enum": [
              "isAllowed",
              "diagnose",
              "allGrantedRoles",
              "allGrantedPermissions"
            ]
          },
          "request": {
            "description": "A Speedle REST JsonContext, the same body as POST /authz-check/v1/*. serviceName must be the signed-in service's name; an omitted, empty or different name returns 400 invalid_request.",
            "$ref": "#/components/schemas/ServiceTestRequest/$defs/__schema0"
          }
        },
        "required": [
          "op",
          "request"
        ],
        "additionalProperties": false,
        "$defs": {
          "__schema0": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              },
              {
                "type": "boolean"
              },
              {
                "type": "null"
              },
              {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ServiceTestRequest/$defs/__schema0"
                }
              },
              {
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "$ref": "#/components/schemas/ServiceTestRequest/$defs/__schema0"
                }
              }
            ]
          }
        },
        "examples": [
          {
            "op": "isAllowed",
            "request": {
              "subject": {
                "principals": [
                  {
                    "type": "user",
                    "name": "alice"
                  }
                ]
              },
              "serviceName": "books",
              "resource": "/books",
              "action": "get"
            }
          },
          {
            "op": "diagnose",
            "request": {
              "subject": {
                "principals": [
                  {
                    "type": "user",
                    "name": "alice"
                  }
                ]
              },
              "serviceName": "books",
              "resource": "/books",
              "action": "get",
              "attributes": [
                {
                  "name": "team",
                  "type": "string",
                  "value": "engineering"
                }
              ]
            }
          },
          {
            "op": "allGrantedRoles",
            "request": {
              "subject": {
                "principals": [
                  {
                    "type": "user",
                    "name": "alice"
                  }
                ]
              },
              "serviceName": "books"
            }
          },
          {
            "op": "allGrantedPermissions",
            "request": {
              "subject": {
                "principals": [
                  {
                    "type": "user",
                    "name": "alice"
                  }
                ]
              },
              "serviceName": "books"
            }
          }
        ]
      },
      "ServiceTestResponse": {
        "anyOf": [
          {
            "type": "object",
            "properties": {
              "allowed": {
                "type": "boolean"
              },
              "reason": {
                "type": "integer",
                "minimum": 0,
                "maximum": 6
              },
              "errorMessage": {
                "type": "string"
              }
            },
            "required": [
              "allowed",
              "reason"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "allowed": {
                "type": "boolean"
              },
              "reason": {
                "type": "string",
                "enum": [
                  "GRANT_POLICY_FOUND",
                  "DENY_POLICY_FOUND",
                  "SERVICE_NOT_FOUND",
                  "NO_APPLICABLE_POLICIES",
                  "ERROR_IN_EVALUATION",
                  "DISCOVER_MODE",
                  "REASON_NOT_AVAILABLE"
                ]
              },
              "requestContext": {
                "type": "object",
                "properties": {
                  "subject": {
                    "anyOf": [
                      {
                        "type": "object",
                        "properties": {
                          "principals": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "type": {
                                  "type": "string"
                                },
                                "name": {
                                  "type": "string"
                                },
                                "idd": {
                                  "type": "string"
                                }
                              },
                              "additionalProperties": false
                            }
                          },
                          "tokenType": {
                            "type": "string"
                          },
                          "token": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "tokenType",
                          "token"
                        ],
                        "additionalProperties": false
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "serviceName": {
                    "type": "string"
                  },
                  "resource": {
                    "type": "string"
                  },
                  "action": {
                    "type": "string"
                  },
                  "attributes": {
                    "anyOf": [
                      {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "name": {
                              "type": "string"
                            },
                            "type": {
                              "type": "string"
                            },
                            "value": {
                              "$ref": "#/components/schemas/ServiceTestResponse/$defs/__schema0"
                            }
                          },
                          "required": [
                            "name",
                            "type",
                            "value"
                          ],
                          "additionalProperties": false
                        }
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                },
                "required": [
                  "subject",
                  "serviceName",
                  "resource",
                  "action",
                  "attributes"
                ],
                "additionalProperties": false
              },
              "attributes": {
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "$ref": "#/components/schemas/ServiceTestResponse/$defs/__schema1"
                },
                "maxProperties": 61,
                "description": "Diagnostic attributes after conversion: at most 61 properties and 66,560 canonical UTF-8 JSON bytes (the complete 64 KiB request plus 1 KiB built-in overhead). Custom attributes retain upstream override semantics."
              },
              "grantedRoles": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "rolePolicies": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string"
                    },
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "effect": {
                      "type": "string"
                    },
                    "roles": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "principals": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "resources": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "resourceExpressions": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "condition": {
                      "type": "object",
                      "properties": {
                        "conditionExpression": {
                          "type": "string"
                        },
                        "evaluationResult": {
                          "type": "string"
                        }
                      },
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "condition"
                  ],
                  "additionalProperties": false
                }
              },
              "policies": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string"
                    },
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "effect": {
                      "type": "string"
                    },
                    "permissions": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "resource": {
                            "type": "string"
                          },
                          "resourceExpression": {
                            "type": "string"
                          },
                          "actions": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        },
                        "additionalProperties": false
                      }
                    },
                    "principals": {
                      "type": "array",
                      "items": {
                        "anyOf": [
                          {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          {
                            "type": "null"
                          }
                        ]
                      }
                    },
                    "condition": {
                      "type": "object",
                      "properties": {
                        "conditionExpression": {
                          "type": "string"
                        },
                        "evaluationResult": {
                          "type": "string"
                        }
                      },
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "condition"
                  ],
                  "additionalProperties": false
                }
              }
            },
            "required": [
              "allowed",
              "reason",
              "requestContext"
            ],
            "additionalProperties": false
          },
          {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "resource": {
                  "type": "string"
                },
                "actions": {
                  "anyOf": [
                    {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              },
              "required": [
                "resource",
                "actions"
              ],
              "additionalProperties": false
            }
          }
        ],
        "description": "Speedle REST body selected by request.op: isAllowed is IsAllowedResponse, diagnose is EvaluationDebugResponse, allGrantedRoles is string[], allGrantedPermissions is PermissionResponse[]. Validate the exact per-operation schema; [] intentionally overlaps in this non-exclusive union.",
        "$defs": {
          "__schema0": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              },
              {
                "type": "boolean"
              },
              {
                "type": "null"
              },
              {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ServiceTestResponse/$defs/__schema0"
                }
              },
              {
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "$ref": "#/components/schemas/ServiceTestResponse/$defs/__schema0"
                }
              }
            ]
          },
          "__schema1": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              },
              {
                "type": "boolean"
              },
              {
                "type": "null"
              },
              {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ServiceTestResponse/$defs/__schema1"
                }
              },
              {
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "$ref": "#/components/schemas/ServiceTestResponse/$defs/__schema1"
                }
              }
            ]
          }
        },
        "examples": [
          {
            "allowed": true,
            "reason": 0
          },
          {
            "allowed": false,
            "reason": 3
          },
          {
            "allowed": true,
            "reason": "GRANT_POLICY_FOUND",
            "requestContext": {
              "subject": {
                "principals": [
                  {
                    "type": "user",
                    "name": "alice"
                  }
                ],
                "tokenType": "",
                "token": ""
              },
              "serviceName": "books",
              "resource": "/books",
              "action": "get",
              "attributes": [
                {
                  "name": "team",
                  "type": "string",
                  "value": "engineering"
                }
              ]
            },
            "attributes": {
              "team": "engineering",
              "request_user": "alice"
            },
            "policies": [
              {
                "id": "p1",
                "name": "read",
                "effect": "grant",
                "permissions": [
                  {
                    "resource": "/books",
                    "actions": [
                      "get"
                    ]
                  }
                ],
                "principals": [
                  [
                    "user:alice"
                  ]
                ],
                "condition": {}
              }
            ]
          },
          [],
          [
            "reader"
          ],
          [],
          [
            {
              "resource": "/books",
              "actions": [
                "get"
              ]
            }
          ]
        ]
      },
      "ServiceTestDecision": {
        "type": "object",
        "properties": {
          "allowed": {
            "type": "boolean"
          },
          "reason": {
            "type": "integer",
            "minimum": 0,
            "maximum": 6
          },
          "errorMessage": {
            "type": "string"
          }
        },
        "required": [
          "allowed",
          "reason"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "allowed": true,
            "reason": 0
          },
          {
            "allowed": false,
            "reason": 3
          }
        ]
      },
      "ServiceTestDiagnosis": {
        "type": "object",
        "properties": {
          "allowed": {
            "type": "boolean"
          },
          "reason": {
            "type": "string",
            "enum": [
              "GRANT_POLICY_FOUND",
              "DENY_POLICY_FOUND",
              "SERVICE_NOT_FOUND",
              "NO_APPLICABLE_POLICIES",
              "ERROR_IN_EVALUATION",
              "DISCOVER_MODE",
              "REASON_NOT_AVAILABLE"
            ]
          },
          "requestContext": {
            "type": "object",
            "properties": {
              "subject": {
                "anyOf": [
                  {
                    "type": "object",
                    "properties": {
                      "principals": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string"
                            },
                            "name": {
                              "type": "string"
                            },
                            "idd": {
                              "type": "string"
                            }
                          },
                          "additionalProperties": false
                        }
                      },
                      "tokenType": {
                        "type": "string"
                      },
                      "token": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "tokenType",
                      "token"
                    ],
                    "additionalProperties": false
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "serviceName": {
                "type": "string"
              },
              "resource": {
                "type": "string"
              },
              "action": {
                "type": "string"
              },
              "attributes": {
                "anyOf": [
                  {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string"
                        },
                        "type": {
                          "type": "string"
                        },
                        "value": {
                          "$ref": "#/components/schemas/ServiceTestDiagnosis/$defs/__schema0"
                        }
                      },
                      "required": [
                        "name",
                        "type",
                        "value"
                      ],
                      "additionalProperties": false
                    }
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            },
            "required": [
              "subject",
              "serviceName",
              "resource",
              "action",
              "attributes"
            ],
            "additionalProperties": false
          },
          "attributes": {
            "type": "object",
            "propertyNames": {
              "type": "string"
            },
            "additionalProperties": {
              "$ref": "#/components/schemas/ServiceTestDiagnosis/$defs/__schema1"
            },
            "maxProperties": 61,
            "description": "Diagnostic attributes after conversion: at most 61 properties and 66,560 canonical UTF-8 JSON bytes (the complete 64 KiB request plus 1 KiB built-in overhead). Custom attributes retain upstream override semantics."
          },
          "grantedRoles": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "rolePolicies": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "status": {
                  "type": "string"
                },
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "effect": {
                  "type": "string"
                },
                "roles": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "principals": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "resources": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "resourceExpressions": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "condition": {
                  "type": "object",
                  "properties": {
                    "conditionExpression": {
                      "type": "string"
                    },
                    "evaluationResult": {
                      "type": "string"
                    }
                  },
                  "additionalProperties": false
                }
              },
              "required": [
                "condition"
              ],
              "additionalProperties": false
            }
          },
          "policies": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "status": {
                  "type": "string"
                },
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "effect": {
                  "type": "string"
                },
                "permissions": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "resource": {
                        "type": "string"
                      },
                      "resourceExpression": {
                        "type": "string"
                      },
                      "actions": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      }
                    },
                    "additionalProperties": false
                  }
                },
                "principals": {
                  "type": "array",
                  "items": {
                    "anyOf": [
                      {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                },
                "condition": {
                  "type": "object",
                  "properties": {
                    "conditionExpression": {
                      "type": "string"
                    },
                    "evaluationResult": {
                      "type": "string"
                    }
                  },
                  "additionalProperties": false
                }
              },
              "required": [
                "condition"
              ],
              "additionalProperties": false
            }
          }
        },
        "required": [
          "allowed",
          "reason",
          "requestContext"
        ],
        "additionalProperties": false,
        "$defs": {
          "__schema0": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              },
              {
                "type": "boolean"
              },
              {
                "type": "null"
              },
              {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ServiceTestDiagnosis/$defs/__schema0"
                }
              },
              {
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "$ref": "#/components/schemas/ServiceTestDiagnosis/$defs/__schema0"
                }
              }
            ]
          },
          "__schema1": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              },
              {
                "type": "boolean"
              },
              {
                "type": "null"
              },
              {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ServiceTestDiagnosis/$defs/__schema1"
                }
              },
              {
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "$ref": "#/components/schemas/ServiceTestDiagnosis/$defs/__schema1"
                }
              }
            ]
          }
        },
        "examples": [
          {
            "allowed": true,
            "reason": "GRANT_POLICY_FOUND",
            "requestContext": {
              "subject": {
                "principals": [
                  {
                    "type": "user",
                    "name": "alice"
                  }
                ],
                "tokenType": "",
                "token": ""
              },
              "serviceName": "books",
              "resource": "/books",
              "action": "get",
              "attributes": [
                {
                  "name": "team",
                  "type": "string",
                  "value": "engineering"
                }
              ]
            },
            "attributes": {
              "team": "engineering",
              "request_user": "alice"
            },
            "policies": [
              {
                "id": "p1",
                "name": "read",
                "effect": "grant",
                "permissions": [
                  {
                    "resource": "/books",
                    "actions": [
                      "get"
                    ]
                  }
                ],
                "principals": [
                  [
                    "user:alice"
                  ]
                ],
                "condition": {}
              }
            ]
          }
        ]
      },
      "ServiceTestRoles": {
        "type": "array",
        "items": {
          "type": "string"
        },
        "examples": [
          [],
          [
            "reader"
          ]
        ]
      },
      "ServiceTestPermissions": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "resource": {
              "type": "string"
            },
            "actions": {
              "anyOf": [
                {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                {
                  "type": "null"
                }
              ]
            }
          },
          "required": [
            "resource",
            "actions"
          ],
          "additionalProperties": false
        },
        "examples": [
          [],
          [
            {
              "resource": "/books",
              "actions": [
                "get"
              ]
            }
          ]
        ]
      },
      "ServiceResponse": {
        "type": "object",
        "properties": {
          "id": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 26,
                "maxLength": 26,
                "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
              },
              {
                "type": "string",
                "minLength": 36,
                "maxLength": 36,
                "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
              }
            ]
          },
          "name": {
            "type": "string",
            "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$"
          },
          "revision": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "createdAt": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "updatedAt": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          }
        },
        "required": [
          "id",
          "name",
          "revision",
          "createdAt",
          "updatedAt"
        ],
        "additionalProperties": false
      },
      "ServiceListResponse": {
        "type": "object",
        "properties": {
          "services": {
            "maxItems": 100,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "anyOf": [
                    {
                      "type": "string",
                      "minLength": 26,
                      "maxLength": 26,
                      "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
                    },
                    {
                      "type": "string",
                      "minLength": 36,
                      "maxLength": 36,
                      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
                    }
                  ]
                },
                "name": {
                  "type": "string",
                  "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$"
                },
                "revision": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 9007199254740991
                },
                "createdAt": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 9007199254740991
                },
                "updatedAt": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 9007199254740991
                }
              },
              "required": [
                "id",
                "name",
                "revision",
                "createdAt",
                "updatedAt"
              ],
              "additionalProperties": false
            }
          }
        },
        "required": [
          "services"
        ],
        "additionalProperties": false
      },
      "PolicyWrite": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1
          },
          "spdl": {
            "type": "string",
            "minLength": 1,
            "description": "One complete SPDL policy; at most 4096 UTF-8 bytes, checked at runtime."
          }
        },
        "required": [
          "name",
          "spdl"
        ],
        "additionalProperties": false
      },
      "PolicyResponse": {
        "type": "object",
        "properties": {
          "id": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 26,
                "maxLength": 26,
                "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
              },
              {
                "type": "string",
                "minLength": 36,
                "maxLength": 36,
                "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
              }
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "authz",
              "role"
            ]
          },
          "name": {
            "type": "string",
            "minLength": 1
          },
          "spdl": {
            "type": "string",
            "minLength": 1,
            "description": "One complete SPDL policy; at most 4096 UTF-8 bytes, checked at runtime."
          },
          "parsed": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "effect": {
                    "type": "string",
                    "enum": [
                      "grant",
                      "deny"
                    ]
                  },
                  "condition": {
                    "type": "string"
                  },
                  "principals": {
                    "type": "array",
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  },
                  "permissions": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "resource": {
                          "type": "string"
                        },
                        "resourceExpression": {
                          "type": "string"
                        },
                        "actions": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      },
                      "required": [
                        "actions"
                      ],
                      "additionalProperties": false
                    }
                  }
                },
                "required": [
                  "id",
                  "name",
                  "effect",
                  "principals",
                  "permissions"
                ],
                "additionalProperties": false
              },
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "effect": {
                    "type": "string",
                    "enum": [
                      "grant",
                      "deny"
                    ]
                  },
                  "condition": {
                    "type": "string"
                  },
                  "principals": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "roles": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "resources": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "resourceExpressions": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                },
                "required": [
                  "id",
                  "name",
                  "effect",
                  "principals",
                  "roles"
                ],
                "additionalProperties": false
              }
            ]
          },
          "createdAt": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "updatedAt": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          }
        },
        "required": [
          "id",
          "kind",
          "name",
          "spdl",
          "parsed",
          "createdAt",
          "updatedAt"
        ],
        "additionalProperties": false
      },
      "PolicyListResponse": {
        "type": "object",
        "properties": {
          "policies": {
            "maxItems": 200,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "anyOf": [
                    {
                      "type": "string",
                      "minLength": 26,
                      "maxLength": 26,
                      "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
                    },
                    {
                      "type": "string",
                      "minLength": 36,
                      "maxLength": 36,
                      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
                    }
                  ]
                },
                "kind": {
                  "type": "string",
                  "enum": [
                    "authz",
                    "role"
                  ]
                },
                "name": {
                  "type": "string",
                  "minLength": 1
                },
                "spdl": {
                  "type": "string",
                  "minLength": 1,
                  "description": "One complete SPDL policy; at most 4096 UTF-8 bytes, checked at runtime."
                },
                "parsed": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "effect": {
                          "type": "string",
                          "enum": [
                            "grant",
                            "deny"
                          ]
                        },
                        "condition": {
                          "type": "string"
                        },
                        "principals": {
                          "type": "array",
                          "items": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        },
                        "permissions": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "resource": {
                                "type": "string"
                              },
                              "resourceExpression": {
                                "type": "string"
                              },
                              "actions": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              }
                            },
                            "required": [
                              "actions"
                            ],
                            "additionalProperties": false
                          }
                        }
                      },
                      "required": [
                        "id",
                        "name",
                        "effect",
                        "principals",
                        "permissions"
                      ],
                      "additionalProperties": false
                    },
                    {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "effect": {
                          "type": "string",
                          "enum": [
                            "grant",
                            "deny"
                          ]
                        },
                        "condition": {
                          "type": "string"
                        },
                        "principals": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "roles": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "resources": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "resourceExpressions": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      },
                      "required": [
                        "id",
                        "name",
                        "effect",
                        "principals",
                        "roles"
                      ],
                      "additionalProperties": false
                    }
                  ]
                },
                "createdAt": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 9007199254740991
                },
                "updatedAt": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 9007199254740991
                }
              },
              "required": [
                "id",
                "kind",
                "name",
                "spdl",
                "parsed",
                "createdAt",
                "updatedAt"
              ],
              "additionalProperties": false
            }
          }
        },
        "required": [
          "policies"
        ],
        "additionalProperties": false
      },
      "ValidatePolicyRequest": {
        "type": "object",
        "properties": {
          "kind": {
            "description": "Omit to detect with both PMS parsers. Exactly one must succeed; ambiguous SPDL is rejected. Explicit kind retains the legacy parser choice.",
            "type": "string",
            "enum": [
              "authz",
              "role"
            ]
          },
          "spdl": {
            "type": "string",
            "minLength": 1,
            "description": "One complete SPDL policy; at most 4096 UTF-8 bytes, checked at runtime."
          }
        },
        "required": [
          "spdl"
        ],
        "additionalProperties": false
      },
      "ValidatePolicyResponse": {
        "type": "object",
        "properties": {
          "kind": {
            "description": "Always returned for kind-less detection. Legacy explicit-kind requests retain the original {parsed} response.",
            "type": "string",
            "enum": [
              "authz",
              "role"
            ]
          },
          "parsed": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "effect": {
                    "type": "string",
                    "enum": [
                      "grant",
                      "deny"
                    ]
                  },
                  "condition": {
                    "type": "string"
                  },
                  "principals": {
                    "type": "array",
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  },
                  "permissions": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "resource": {
                          "type": "string"
                        },
                        "resourceExpression": {
                          "type": "string"
                        },
                        "actions": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      },
                      "required": [
                        "actions"
                      ],
                      "additionalProperties": false
                    }
                  }
                },
                "required": [
                  "id",
                  "name",
                  "effect",
                  "principals",
                  "permissions"
                ],
                "additionalProperties": false
              },
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "effect": {
                    "type": "string",
                    "enum": [
                      "grant",
                      "deny"
                    ]
                  },
                  "condition": {
                    "type": "string"
                  },
                  "principals": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "roles": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "resources": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "resourceExpressions": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                },
                "required": [
                  "id",
                  "name",
                  "effect",
                  "principals",
                  "roles"
                ],
                "additionalProperties": false
              }
            ]
          }
        },
        "required": [
          "parsed"
        ],
        "additionalProperties": false
      },
      "ServiceExport": {
        "type": "object",
        "properties": {
          "service": {
            "type": "object",
            "properties": {
              "id": {
                "anyOf": [
                  {
                    "type": "string",
                    "minLength": 26,
                    "maxLength": 26,
                    "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
                  },
                  {
                    "type": "string",
                    "minLength": 36,
                    "maxLength": 36,
                    "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
                  }
                ]
              },
              "name": {
                "type": "string",
                "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$"
              },
              "revision": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "createdAt": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "updatedAt": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              }
            },
            "required": [
              "id",
              "name",
              "revision",
              "createdAt",
              "updatedAt"
            ],
            "additionalProperties": false
          },
          "policies": {
            "maxItems": 200,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "anyOf": [
                    {
                      "type": "string",
                      "minLength": 26,
                      "maxLength": 26,
                      "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
                    },
                    {
                      "type": "string",
                      "minLength": 36,
                      "maxLength": 36,
                      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
                    }
                  ]
                },
                "kind": {
                  "type": "string",
                  "enum": [
                    "authz",
                    "role"
                  ]
                },
                "name": {
                  "type": "string",
                  "minLength": 1
                },
                "spdl": {
                  "type": "string",
                  "minLength": 1,
                  "description": "One complete SPDL policy; at most 4096 UTF-8 bytes, checked at runtime."
                },
                "parsed": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "effect": {
                          "type": "string",
                          "enum": [
                            "grant",
                            "deny"
                          ]
                        },
                        "condition": {
                          "type": "string"
                        },
                        "principals": {
                          "type": "array",
                          "items": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        },
                        "permissions": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "resource": {
                                "type": "string"
                              },
                              "resourceExpression": {
                                "type": "string"
                              },
                              "actions": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              }
                            },
                            "required": [
                              "actions"
                            ],
                            "additionalProperties": false
                          }
                        }
                      },
                      "required": [
                        "id",
                        "name",
                        "effect",
                        "principals",
                        "permissions"
                      ],
                      "additionalProperties": false
                    },
                    {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "effect": {
                          "type": "string",
                          "enum": [
                            "grant",
                            "deny"
                          ]
                        },
                        "condition": {
                          "type": "string"
                        },
                        "principals": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "roles": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "resources": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "resourceExpressions": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      },
                      "required": [
                        "id",
                        "name",
                        "effect",
                        "principals",
                        "roles"
                      ],
                      "additionalProperties": false
                    }
                  ]
                },
                "createdAt": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 9007199254740991
                },
                "updatedAt": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 9007199254740991
                }
              },
              "required": [
                "id",
                "kind",
                "name",
                "spdl",
                "parsed",
                "createdAt",
                "updatedAt"
              ],
              "additionalProperties": false
            }
          }
        },
        "required": [
          "service",
          "policies"
        ],
        "additionalProperties": false
      },
      "ServiceEmptyRequest": {
        "type": "object",
        "properties": {},
        "additionalProperties": false
      },
      "ServiceError": {
        "anyOf": [
          {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "enum": [
                  "parse_error",
                  "custom_function_unsupported"
                ]
              },
              "message": {
                "type": "string"
              },
              "line": {
                "type": "integer",
                "exclusiveMinimum": 0,
                "maximum": 9007199254740991
              },
              "column": {
                "type": "integer",
                "exclusiveMinimum": 0,
                "maximum": 9007199254740991
              }
            },
            "required": [
              "error",
              "message",
              "line",
              "column"
            ],
            "additionalProperties": false,
            "description": "One-based line and Unicode-code-point column in the submitted SPDL. Errors without a parser offset point to the start of the condition (or source). No echoed SPDL."
          },
          {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "enum": [
                  "ambiguous_policy_kind",
                  "policy_kind_mismatch"
                ]
              },
              "message": {
                "type": "string"
              }
            },
            "required": [
              "error",
              "message"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "const": "invalid_request"
              },
              "message": {
                "type": "string"
              }
            },
            "required": [
              "error",
              "message"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "const": "quota_exceeded"
              },
              "limit": {
                "anyOf": [
                  {
                    "type": "number",
                    "const": 200
                  },
                  {
                    "type": "number",
                    "const": 100
                  }
                ]
              }
            },
            "required": [
              "error",
              "limit"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "enum": [
                  "invalid_request",
                  "unauthorized",
                  "csrf_failed",
                  "reauth_required",
                  "not_found",
                  "service_exists",
                  "policy_exists",
                  "request_too_large",
                  "rate_limited",
                  "daily_quota_exceeded",
                  "quota_unavailable",
                  "internal_error"
                ]
              }
            },
            "required": [
              "error"
            ],
            "additionalProperties": false
          }
        ]
      },
      "TokenWrite": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128
          },
          "scope": {
            "default": "ads",
            "description": "Optional issuance scope, default ads. No other capability, service, ownership or expiry can be selected. pms:read and pms:write are rejected while sign-in is disabled for the stage.",
            "type": "string",
            "enum": [
              "ads",
              "pms:read",
              "pms:write"
            ]
          }
        },
        "required": [
          "name"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "name": "production",
            "scope": "ads"
          },
          {
            "name": "mcp-read",
            "scope": "pms:read"
          },
          {
            "name": "mcp-manage",
            "scope": "pms:write"
          }
        ]
      },
      "TokenListQuery": {
        "type": "object",
        "properties": {
          "limit": {
            "description": "Canonical decimal query string, 1–100; omitted means 25. No coercion, signs, leading zeroes or whitespace.",
            "default": "25",
            "type": "string",
            "pattern": "^(?:[1-9]|[1-9][0-9]|100)(?![\\s\\S])"
          },
          "cursor": {
            "description": "Exclusive canonical ID (ULID or legacy UUIDv7) boundary in descending id order: return owned rows with id < cursor. A position only, never an ownership selector; every query must remain tenant-scoped.",
            "anyOf": [
              {
                "type": "string",
                "minLength": 26,
                "maxLength": 26,
                "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
              },
              {
                "type": "string",
                "minLength": 36,
                "maxLength": 36,
                "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
              }
            ]
          }
        },
        "additionalProperties": false,
        "description": "GET only. Raw query strings; reject duplicate keys before object construction and reject unknown/malformed parameters. No offset, sort, account or service selector.",
        "examples": [
          {},
          {
            "limit": "25"
          },
          {
            "limit": "100",
            "cursor": "01HF7YAT000000000000000002"
          }
        ]
      },
      "TokenListResponse": {
        "type": "object",
        "properties": {
          "tokens": {
            "maxItems": 100,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "anyOf": [
                    {
                      "type": "string",
                      "minLength": 26,
                      "maxLength": 26,
                      "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
                    },
                    {
                      "type": "string",
                      "minLength": 36,
                      "maxLength": 36,
                      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
                    }
                  ]
                },
                "serviceId": {
                  "anyOf": [
                    {
                      "type": "string",
                      "minLength": 26,
                      "maxLength": 26,
                      "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
                    },
                    {
                      "type": "string",
                      "minLength": 36,
                      "maxLength": 36,
                      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
                    }
                  ]
                },
                "name": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 128
                },
                "scope": {
                  "description": "Fixed scope. Absent means ads: pre-#250 responses and the #250 rollout window omit it for ads tokens so a console built before #250 keeps parsing strict responses while the new Worker is already live.",
                  "type": "string",
                  "enum": [
                    "ads",
                    "pms:read",
                    "pms:write"
                  ]
                },
                "expiresAt": {
                  "description": "Optional expiration time in Unix milliseconds. Omitted for current v1 tokens, which have no intrinsic expiry; creation does not accept a client-selected expiry.",
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 9007199254740991
                },
                "createdAt": {
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 9007199254740991
                },
                "lastUsedAt": {
                  "anyOf": [
                    {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "revokedAt": {
                  "anyOf": [
                    {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              },
              "required": [
                "id",
                "serviceId",
                "name",
                "createdAt",
                "lastUsedAt",
                "revokedAt"
              ],
              "additionalProperties": false
            }
          },
          "nextCursor": {
            "anyOf": [
              {
                "anyOf": [
                  {
                    "type": "string",
                    "minLength": 26,
                    "maxLength": 26,
                    "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
                  },
                  {
                    "type": "string",
                    "minLength": 36,
                    "maxLength": 36,
                    "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
                  }
                ]
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "tokens",
          "nextCursor"
        ],
        "additionalProperties": false,
        "description": "At most the requested limit (default 25, maximum 100), ordered by canonical ID DESC (binary string order), including active and revoked history. Use a tenant-scoped seek query with LIMIT+1, never an unbounded read or COUNT. nextCursor is required: last returned id only when more rows exist, otherwise null; empty/final pages return null. Runtime must enforce the requested page size, exclusive boundary, ownership and more-row condition; JSON Schema cannot express all ordering/cross-field checks.",
        "examples": [
          {
            "tokens": [],
            "nextCursor": null
          },
          {
            "tokens": [
              {
                "id": "01HF7YAT000000000000000002",
                "serviceId": "01HF7YAT000000000000000002",
                "name": "production",
                "createdAt": 1700000000000,
                "lastUsedAt": null,
                "revokedAt": null,
                "scope": "pms:read"
              },
              {
                "id": "01HF7YAT000000000000000001",
                "serviceId": "01HF7YAT000000000000000002",
                "name": "production",
                "createdAt": 1700000000000,
                "lastUsedAt": null,
                "revokedAt": 1700000001000
              }
            ],
            "nextCursor": null
          },
          {
            "tokens": [
              {
                "id": "01HF7YAT000000000000000002",
                "serviceId": "01HF7YAT000000000000000002",
                "name": "production",
                "createdAt": 1700000000000,
                "lastUsedAt": null,
                "revokedAt": null,
                "scope": "pms:read"
              }
            ],
            "nextCursor": "01HF7YAT000000000000000002"
          }
        ]
      },
      "TokenCreateResponse": {
        "type": "object",
        "properties": {
          "id": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 26,
                "maxLength": 26,
                "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
              },
              {
                "type": "string",
                "minLength": 36,
                "maxLength": 36,
                "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
              }
            ]
          },
          "serviceId": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 26,
                "maxLength": 26,
                "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
              },
              {
                "type": "string",
                "minLength": 36,
                "maxLength": 36,
                "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
              }
            ]
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128
          },
          "scope": {
            "description": "Fixed scope. Absent means ads: pre-#250 responses and the #250 rollout window omit it for ads tokens so a console built before #250 keeps parsing strict responses while the new Worker is already live.",
            "type": "string",
            "enum": [
              "ads",
              "pms:read",
              "pms:write"
            ]
          },
          "expiresAt": {
            "description": "Optional expiration time in Unix milliseconds. Omitted for current v1 tokens, which have no intrinsic expiry; creation does not accept a client-selected expiry.",
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "createdAt": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "lastUsedAt": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              {
                "type": "null"
              }
            ]
          },
          "revokedAt": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              {
                "type": "null"
              }
            ]
          },
          "token": {
            "type": "string",
            "pattern": "^spdl_(?:[0-7][0-9A-HJKMNP-TV-Z]{25}|[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})_[0-9A-Za-z]{43}$",
            "description": "One-time credential: spdl_<canonical uppercase ULID ID>_<43 base62 digits> (75 characters). Credentials issued before #217 keep their legacy canonical lowercase UUIDv7 ID prefix (85 characters). Its single scope binds it to one service: ads (authorization checks only), pms:read or pms:write (token-authenticated /policy-mgmt/v1 management). No intrinsic expiry in v1."
          }
        },
        "required": [
          "id",
          "serviceId",
          "name",
          "createdAt",
          "lastUsedAt",
          "revokedAt",
          "token"
        ],
        "additionalProperties": false,
        "description": "The token field is disclosed only on successful creation. Its ID prefix must equal id (also checked at runtime; JSON Schema cannot express that relationship). Only HMAC-SHA256(TOKEN_PEPPER, secret) is persisted; never return the HMAC or secret separately.",
        "examples": [
          {
            "id": "01HF7YAT000000000000000001",
            "serviceId": "01HF7YAT000000000000000002",
            "name": "production",
            "createdAt": 1700000000000,
            "lastUsedAt": null,
            "revokedAt": null,
            "token": "spdl_01HF7YAT000000000000000001_0000000000000000000000000000000000000000000"
          },
          {
            "id": "01HF7YAT000000000000000002",
            "serviceId": "01HF7YAT000000000000000002",
            "name": "production",
            "createdAt": 1700000000000,
            "lastUsedAt": null,
            "revokedAt": null,
            "scope": "pms:read",
            "token": "spdl_01HF7YAT000000000000000002_0000000000000000000000000000000000000000000"
          }
        ]
      },
      "TokenEmptyRequest": {
        "type": "object",
        "properties": {},
        "additionalProperties": false,
        "examples": [
          {}
        ]
      },
      "TokenInvalidRequestError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "invalid_request"
            ]
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "invalid_request"
          }
        ]
      },
      "TokenUnauthorizedError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "unauthorized"
            ]
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "unauthorized"
          }
        ]
      },
      "TokenCsrfError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "csrf_failed"
            ]
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "csrf_failed"
          }
        ]
      },
      "TokenIssuanceForbiddenError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "csrf_failed",
              "reauth_required",
              "management_scope_disabled"
            ]
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "csrf_failed"
          },
          {
            "error": "reauth_required"
          },
          {
            "error": "management_scope_disabled"
          }
        ]
      },
      "TokenNotFoundError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "not_found"
            ]
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "not_found"
          }
        ]
      },
      "TokenQuotaExceededError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "const": "quota_exceeded"
          },
          "limit": {
            "type": "number",
            "const": 2
          }
        },
        "required": [
          "error",
          "limit"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "quota_exceeded",
            "limit": 2
          }
        ]
      },
      "TokenRateLimitError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "rate_limited",
              "daily_quota_exceeded"
            ]
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "rate_limited"
          },
          {
            "error": "daily_quota_exceeded"
          }
        ]
      },
      "TokenRequestTooLargeError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "request_too_large"
            ]
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "request_too_large"
          }
        ]
      },
      "TokenInternalError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "internal_error"
            ]
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "internal_error"
          }
        ]
      },
      "TokenQuotaUnavailableError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "quota_unavailable"
            ]
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "quota_unavailable"
          }
        ]
      },
      "AdsRequest": {
        "type": "object",
        "properties": {
          "subject": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "principals": {
                    "anyOf": [
                      {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string"
                            },
                            "name": {
                              "type": "string"
                            },
                            "idd": {
                              "type": "string"
                            }
                          },
                          "additionalProperties": {}
                        }
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "tokenType": {
                    "type": "string"
                  },
                  "token": {
                    "type": "string"
                  }
                },
                "additionalProperties": {}
              },
              {
                "type": "null"
              }
            ]
          },
          "serviceName": {
            "type": "string",
            "description": "Send in every request, as with Speedle: the bearer token's service name, never defaulted. An omitted or empty name gets Speedle's SERVICE_NOT_FOUND answer; a different name returns 403 service_mismatch."
          },
          "resource": {
            "type": "string"
          },
          "action": {
            "type": "string"
          },
          "attributes": {
            "description": "Speedle typed attributes. Cloud bounds them to 50 items and 16,384 canonical UTF-8 JSON bytes.",
            "anyOf": [
              {
                "maxItems": 50,
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "type": {
                      "anyOf": [
                        {
                          "type": "string",
                          "enum": [
                            "string",
                            "numeric",
                            "bool",
                            "datetime"
                          ],
                          "description": "string: JSON string. numeric: JSON number. bool: true or false. datetime: JSON string in RFC 3339, Ruby date or Unix date layout, converted to Unix seconds. An omitted or empty type passes the JSON value through unconverted."
                        },
                        {
                          "type": "string",
                          "const": ""
                        }
                      ]
                    },
                    "value": {
                      "description": "A single value of the type, or an array of such values. null, null items and items of different types return 400.",
                      "$ref": "#/components/schemas/AdsRequest/$defs/__schema0"
                    }
                  },
                  "required": [
                    "name",
                    "value"
                  ],
                  "additionalProperties": {},
                  "description": "One typed attribute: Speedle JsonAttribute."
                }
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "serviceName"
        ],
        "additionalProperties": {},
        "description": "Speedle REST JsonContext.",
        "$defs": {
          "__schema0": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              },
              {
                "type": "boolean"
              },
              {
                "type": "null"
              },
              {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/AdsRequest/$defs/__schema0"
                }
              },
              {
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "$ref": "#/components/schemas/AdsRequest/$defs/__schema0"
                }
              }
            ]
          }
        },
        "examples": [
          {
            "subject": {
              "principals": [
                {
                  "type": "user",
                  "name": "alice"
                }
              ]
            },
            "serviceName": "books",
            "resource": "/books/123",
            "action": "get"
          },
          {
            "subject": {
              "principals": [
                {
                  "type": "user",
                  "name": "alice"
                }
              ]
            },
            "serviceName": "books",
            "resource": "/books/123",
            "action": "get",
            "attributes": [
              {
                "name": "team",
                "type": "string",
                "value": "engineering"
              },
              {
                "name": "level",
                "type": "numeric",
                "value": 3
              },
              {
                "name": "active",
                "type": "bool",
                "value": true
              },
              {
                "name": "when",
                "type": "datetime",
                "value": "2026-11-05T03:00:00Z"
              },
              {
                "name": "tags",
                "type": "string",
                "value": [
                  "a",
                  "b"
                ]
              }
            ]
          }
        ]
      },
      "AdsDecision": {
        "type": "object",
        "properties": {
          "allowed": {
            "type": "boolean"
          },
          "reason": {
            "type": "integer",
            "minimum": 0,
            "maximum": 6
          },
          "errorMessage": {
            "type": "string"
          }
        },
        "required": [
          "allowed",
          "reason"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "allowed": true,
            "reason": 0
          },
          {
            "allowed": false,
            "reason": 3
          }
        ]
      },
      "AdsRoles": {
        "type": "array",
        "items": {
          "type": "string"
        },
        "examples": [
          [],
          [
            "reader"
          ]
        ]
      },
      "AdsPermissions": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "resource": {
              "type": "string"
            },
            "actions": {
              "anyOf": [
                {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                {
                  "type": "null"
                }
              ]
            }
          },
          "required": [
            "resource",
            "actions"
          ],
          "additionalProperties": false
        },
        "examples": [
          [],
          [
            {
              "resource": "/books",
              "actions": [
                "get"
              ]
            }
          ]
        ]
      },
      "AdsInvalidRequestError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "SPDL-0004 value \"2026-11-05\" is not a supported date time"
          },
          {
            "error": "SPDL-0004 unable to decode request: json: cannot unmarshal object into Go struct field JsonContext.attributes of type []*adsrest.JsonAttribute"
          }
        ]
      },
      "AdsUnauthorizedError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "unauthorized"
            ]
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "unauthorized"
          }
        ]
      },
      "AdsMethodNotAllowedError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "method_not_allowed"
            ]
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "method_not_allowed"
          }
        ]
      },
      "AdsServiceMismatchError": {
        "anyOf": [
          {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "enum": [
                  "service_mismatch"
                ]
              }
            },
            "required": [
              "error"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "enum": [
                  "insufficient_scope"
                ]
              }
            },
            "required": [
              "error"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "error": {
                "type": "string"
              }
            },
            "required": [
              "error"
            ],
            "additionalProperties": false
          }
        ],
        "examples": [
          {
            "error": "service_mismatch"
          },
          {
            "error": "insufficient_scope"
          }
        ]
      },
      "AdsRequestTooLargeError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "request_too_large"
            ]
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "request_too_large"
          }
        ]
      },
      "AdsRateLimitError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "rate_limited",
              "daily_quota_exceeded"
            ]
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "rate_limited"
          },
          {
            "error": "daily_quota_exceeded"
          }
        ]
      },
      "AdsInternalError": {
        "anyOf": [
          {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "enum": [
                  "internal_error"
                ]
              }
            },
            "required": [
              "error"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "error": {
                "type": "string"
              }
            },
            "required": [
              "error"
            ],
            "additionalProperties": false
          }
        ],
        "examples": [
          {
            "error": "internal_error"
          },
          {
            "error": "SPDL-2001 Application books is not found "
          }
        ]
      },
      "AdsUnavailableError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "quota_unavailable",
              "snapshot_unavailable"
            ]
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "quota_unavailable"
          },
          {
            "error": "snapshot_unavailable"
          }
        ]
      },
      "PmsPolicyWrite": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1
          },
          "effect": {
            "type": "string",
            "enum": [
              "grant",
              "deny"
            ]
          },
          "permissions": {
            "minItems": 1,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "resource": {
                  "type": "string",
                  "minLength": 1
                },
                "resourceExpression": {
                  "type": "string",
                  "minLength": 1
                },
                "actions": {
                  "minItems": 1,
                  "type": "array",
                  "items": {
                    "type": "string",
                    "minLength": 1
                  }
                }
              },
              "required": [
                "actions"
              ],
              "additionalProperties": false,
              "description": "One PMS permission. The Cloud requires exactly one of resource (exact) or resourceExpression (regular expression) so the response round-trips through SPDL."
            }
          },
          "principals": {
            "minItems": 1,
            "type": "array",
            "items": {
              "minItems": 1,
              "type": "array",
              "items": {
                "type": "string",
                "pattern": "^(?:idd=[^\\r\\n\\u2028\\u2029]+:(?:user|group|role|entity):[^\\r\\n\\u2028\\u2029]+|(?:user|group|role|entity):[^\\r\\n\\u2028\\u2029]+)$",
                "description": "An encoded PMS principal: \"<type>:<name>\" or \"idd=<idd>:<type>:<name>\", type one of user, group, role or entity."
              }
            }
          },
          "condition": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "effect",
          "permissions",
          "principals"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "name": "read-books",
            "effect": "grant",
            "permissions": [
              {
                "resource": "/books",
                "actions": [
                  "get",
                  "list"
                ]
              }
            ],
            "principals": [
              [
                "user:alice"
              ],
              [
                "role:librarian"
              ]
            ]
          },
          {
            "name": "expired-writes",
            "effect": "deny",
            "permissions": [
              {
                "resourceExpression": "/books/.*",
                "actions": [
                  "write",
                  "delete"
                ]
              }
            ],
            "principals": [
              [
                "user:bob"
              ]
            ],
            "condition": "request_time < '2026-01-01 00:00:00'"
          }
        ]
      },
      "PmsPolicy": {
        "type": "object",
        "properties": {
          "id": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 26,
                "maxLength": 26,
                "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
              },
              {
                "type": "string",
                "minLength": 36,
                "maxLength": 36,
                "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
              }
            ]
          },
          "name": {
            "type": "string",
            "minLength": 1
          },
          "effect": {
            "type": "string",
            "enum": [
              "grant",
              "deny"
            ]
          },
          "permissions": {
            "minItems": 1,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "resource": {
                  "type": "string",
                  "minLength": 1
                },
                "resourceExpression": {
                  "type": "string",
                  "minLength": 1
                },
                "actions": {
                  "minItems": 1,
                  "type": "array",
                  "items": {
                    "type": "string",
                    "minLength": 1
                  }
                }
              },
              "required": [
                "actions"
              ],
              "additionalProperties": false,
              "description": "One PMS permission. The Cloud requires exactly one of resource (exact) or resourceExpression (regular expression) so the response round-trips through SPDL."
            }
          },
          "principals": {
            "minItems": 1,
            "type": "array",
            "items": {
              "minItems": 1,
              "type": "array",
              "items": {
                "type": "string",
                "pattern": "^(?:idd=[^\\r\\n\\u2028\\u2029]+:(?:user|group|role|entity):[^\\r\\n\\u2028\\u2029]+|(?:user|group|role|entity):[^\\r\\n\\u2028\\u2029]+)$",
                "description": "An encoded PMS principal: \"<type>:<name>\" or \"idd=<idd>:<type>:<name>\", type one of user, group, role or entity."
              }
            }
          },
          "condition": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "effect",
          "permissions",
          "principals"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "id": "01HF7YAT000000000000000001",
            "name": "read-books",
            "effect": "grant",
            "permissions": [
              {
                "resource": "/books",
                "actions": [
                  "get",
                  "list"
                ]
              }
            ],
            "principals": [
              [
                "user:alice"
              ],
              [
                "role:librarian"
              ]
            ]
          }
        ]
      },
      "PmsPolicyList": {
        "maxItems": 200,
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "id": {
              "anyOf": [
                {
                  "type": "string",
                  "minLength": 26,
                  "maxLength": 26,
                  "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
                },
                {
                  "type": "string",
                  "minLength": 36,
                  "maxLength": 36,
                  "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
                }
              ]
            },
            "name": {
              "type": "string",
              "minLength": 1
            },
            "effect": {
              "type": "string",
              "enum": [
                "grant",
                "deny"
              ]
            },
            "permissions": {
              "minItems": 1,
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "resource": {
                    "type": "string",
                    "minLength": 1
                  },
                  "resourceExpression": {
                    "type": "string",
                    "minLength": 1
                  },
                  "actions": {
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1
                    }
                  }
                },
                "required": [
                  "actions"
                ],
                "additionalProperties": false,
                "description": "One PMS permission. The Cloud requires exactly one of resource (exact) or resourceExpression (regular expression) so the response round-trips through SPDL."
              }
            },
            "principals": {
              "minItems": 1,
              "type": "array",
              "items": {
                "minItems": 1,
                "type": "array",
                "items": {
                  "type": "string",
                  "pattern": "^(?:idd=[^\\r\\n\\u2028\\u2029]+:(?:user|group|role|entity):[^\\r\\n\\u2028\\u2029]+|(?:user|group|role|entity):[^\\r\\n\\u2028\\u2029]+)$",
                  "description": "An encoded PMS principal: \"<type>:<name>\" or \"idd=<idd>:<type>:<name>\", type one of user, group, role or entity."
                }
              }
            },
            "condition": {
              "type": "string"
            }
          },
          "required": [
            "id",
            "name",
            "effect",
            "permissions",
            "principals"
          ],
          "additionalProperties": false
        },
        "examples": [
          [],
          [
            {
              "id": "01HF7YAT000000000000000001",
              "name": "read-books",
              "effect": "grant",
              "permissions": [
                {
                  "resource": "/books",
                  "actions": [
                    "get",
                    "list"
                  ]
                }
              ],
              "principals": [
                [
                  "user:alice"
                ],
                [
                  "role:librarian"
                ]
              ]
            }
          ]
        ]
      },
      "PmsRolePolicyWrite": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1
          },
          "effect": {
            "type": "string",
            "enum": [
              "grant",
              "deny"
            ]
          },
          "roles": {
            "minItems": 1,
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1
            }
          },
          "principals": {
            "minItems": 1,
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^(?:idd=[^\\r\\n\\u2028\\u2029]+:(?:user|group|role|entity):[^\\r\\n\\u2028\\u2029]+|(?:user|group|role|entity):[^\\r\\n\\u2028\\u2029]+)$",
              "description": "An encoded PMS principal: \"<type>:<name>\" or \"idd=<idd>:<type>:<name>\", type one of user, group, role or entity."
            }
          },
          "resources": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1
            }
          },
          "resourceExpressions": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1
            }
          },
          "condition": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "effect",
          "roles",
          "principals"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "name": "librarian",
            "effect": "grant",
            "roles": [
              "librarian"
            ],
            "principals": [
              "user:alice"
            ],
            "resources": [
              "/docs"
            ],
            "condition": "request_time > '2026-01-01 00:00:00'"
          },
          {
            "name": "contractors",
            "effect": "deny",
            "roles": [
              "editor"
            ],
            "principals": [
              "group:contractors"
            ],
            "resourceExpressions": [
              "/prod/.*"
            ]
          }
        ]
      },
      "PmsRolePolicy": {
        "type": "object",
        "properties": {
          "id": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 26,
                "maxLength": 26,
                "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
              },
              {
                "type": "string",
                "minLength": 36,
                "maxLength": 36,
                "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
              }
            ]
          },
          "name": {
            "type": "string",
            "minLength": 1
          },
          "effect": {
            "type": "string",
            "enum": [
              "grant",
              "deny"
            ]
          },
          "roles": {
            "minItems": 1,
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1
            }
          },
          "principals": {
            "minItems": 1,
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^(?:idd=[^\\r\\n\\u2028\\u2029]+:(?:user|group|role|entity):[^\\r\\n\\u2028\\u2029]+|(?:user|group|role|entity):[^\\r\\n\\u2028\\u2029]+)$",
              "description": "An encoded PMS principal: \"<type>:<name>\" or \"idd=<idd>:<type>:<name>\", type one of user, group, role or entity."
            }
          },
          "resources": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1
            }
          },
          "resourceExpressions": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1
            }
          },
          "condition": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "effect",
          "roles",
          "principals"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "id": "01HF7YAT000000000000000002",
            "name": "librarian",
            "effect": "grant",
            "roles": [
              "librarian"
            ],
            "principals": [
              "user:alice"
            ],
            "resources": [
              "/docs"
            ],
            "condition": "request_time > '2026-01-01 00:00:00'"
          }
        ]
      },
      "PmsRolePolicyList": {
        "maxItems": 200,
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "id": {
              "anyOf": [
                {
                  "type": "string",
                  "minLength": 26,
                  "maxLength": 26,
                  "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
                },
                {
                  "type": "string",
                  "minLength": 36,
                  "maxLength": 36,
                  "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
                }
              ]
            },
            "name": {
              "type": "string",
              "minLength": 1
            },
            "effect": {
              "type": "string",
              "enum": [
                "grant",
                "deny"
              ]
            },
            "roles": {
              "minItems": 1,
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1
              }
            },
            "principals": {
              "minItems": 1,
              "type": "array",
              "items": {
                "type": "string",
                "pattern": "^(?:idd=[^\\r\\n\\u2028\\u2029]+:(?:user|group|role|entity):[^\\r\\n\\u2028\\u2029]+|(?:user|group|role|entity):[^\\r\\n\\u2028\\u2029]+)$",
                "description": "An encoded PMS principal: \"<type>:<name>\" or \"idd=<idd>:<type>:<name>\", type one of user, group, role or entity."
              }
            },
            "resources": {
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1
              }
            },
            "resourceExpressions": {
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1
              }
            },
            "condition": {
              "type": "string"
            }
          },
          "required": [
            "id",
            "name",
            "effect",
            "roles",
            "principals"
          ],
          "additionalProperties": false
        },
        "examples": [
          [],
          [
            {
              "id": "01HF7YAT000000000000000002",
              "name": "librarian",
              "effect": "grant",
              "roles": [
                "librarian"
              ],
              "principals": [
                "user:alice"
              ],
              "resources": [
                "/docs"
              ],
              "condition": "request_time > '2026-01-01 00:00:00'"
            }
          ]
        ]
      },
      "PmsServiceWrite": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$"
          },
          "type": {
            "default": "application",
            "description": "Only application is supported; upstream k8s-cluster services have no Cloud snapshot semantics.",
            "type": "string",
            "const": "application"
          }
        },
        "required": [
          "name"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "name": "books",
            "type": "application"
          }
        ]
      },
      "PmsService": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$"
          },
          "type": {
            "type": "string",
            "const": "application"
          },
          "policies": {
            "maxItems": 200,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "anyOf": [
                    {
                      "type": "string",
                      "minLength": 26,
                      "maxLength": 26,
                      "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
                    },
                    {
                      "type": "string",
                      "minLength": 36,
                      "maxLength": 36,
                      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
                    }
                  ]
                },
                "name": {
                  "type": "string",
                  "minLength": 1
                },
                "effect": {
                  "type": "string",
                  "enum": [
                    "grant",
                    "deny"
                  ]
                },
                "permissions": {
                  "minItems": 1,
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "resource": {
                        "type": "string",
                        "minLength": 1
                      },
                      "resourceExpression": {
                        "type": "string",
                        "minLength": 1
                      },
                      "actions": {
                        "minItems": 1,
                        "type": "array",
                        "items": {
                          "type": "string",
                          "minLength": 1
                        }
                      }
                    },
                    "required": [
                      "actions"
                    ],
                    "additionalProperties": false,
                    "description": "One PMS permission. The Cloud requires exactly one of resource (exact) or resourceExpression (regular expression) so the response round-trips through SPDL."
                  }
                },
                "principals": {
                  "minItems": 1,
                  "type": "array",
                  "items": {
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "pattern": "^(?:idd=[^\\r\\n\\u2028\\u2029]+:(?:user|group|role|entity):[^\\r\\n\\u2028\\u2029]+|(?:user|group|role|entity):[^\\r\\n\\u2028\\u2029]+)$",
                      "description": "An encoded PMS principal: \"<type>:<name>\" or \"idd=<idd>:<type>:<name>\", type one of user, group, role or entity."
                    }
                  }
                },
                "condition": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "name",
                "effect",
                "permissions",
                "principals"
              ],
              "additionalProperties": false
            }
          },
          "rolePolicies": {
            "maxItems": 200,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "anyOf": [
                    {
                      "type": "string",
                      "minLength": 26,
                      "maxLength": 26,
                      "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
                    },
                    {
                      "type": "string",
                      "minLength": 36,
                      "maxLength": 36,
                      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
                    }
                  ]
                },
                "name": {
                  "type": "string",
                  "minLength": 1
                },
                "effect": {
                  "type": "string",
                  "enum": [
                    "grant",
                    "deny"
                  ]
                },
                "roles": {
                  "minItems": 1,
                  "type": "array",
                  "items": {
                    "type": "string",
                    "minLength": 1
                  }
                },
                "principals": {
                  "minItems": 1,
                  "type": "array",
                  "items": {
                    "type": "string",
                    "pattern": "^(?:idd=[^\\r\\n\\u2028\\u2029]+:(?:user|group|role|entity):[^\\r\\n\\u2028\\u2029]+|(?:user|group|role|entity):[^\\r\\n\\u2028\\u2029]+)$",
                    "description": "An encoded PMS principal: \"<type>:<name>\" or \"idd=<idd>:<type>:<name>\", type one of user, group, role or entity."
                  }
                },
                "resources": {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "minLength": 1
                  }
                },
                "resourceExpressions": {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "minLength": 1
                  }
                },
                "condition": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "name",
                "effect",
                "roles",
                "principals"
              ],
              "additionalProperties": false
            }
          }
        },
        "required": [
          "name",
          "type",
          "policies",
          "rolePolicies"
        ],
        "additionalProperties": false,
        "description": "One Cloud service in the upstream GET /service shape. The token's own service is the only service it can see.",
        "examples": [
          {
            "name": "books",
            "type": "application",
            "policies": [
              {
                "id": "01HF7YAT000000000000000001",
                "name": "read-books",
                "effect": "grant",
                "permissions": [
                  {
                    "resource": "/books",
                    "actions": [
                      "get",
                      "list"
                    ]
                  }
                ],
                "principals": [
                  [
                    "user:alice"
                  ],
                  [
                    "role:librarian"
                  ]
                ]
              }
            ],
            "rolePolicies": [
              {
                "id": "01HF7YAT000000000000000002",
                "name": "librarian",
                "effect": "grant",
                "roles": [
                  "librarian"
                ],
                "principals": [
                  "user:alice"
                ],
                "resources": [
                  "/docs"
                ],
                "condition": "request_time > '2026-01-01 00:00:00'"
              }
            ]
          }
        ]
      },
      "PmsServiceList": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "name": {
              "type": "string",
              "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$"
            },
            "type": {
              "type": "string",
              "const": "application"
            },
            "policies": {
              "maxItems": 200,
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "id": {
                    "anyOf": [
                      {
                        "type": "string",
                        "minLength": 26,
                        "maxLength": 26,
                        "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
                      },
                      {
                        "type": "string",
                        "minLength": 36,
                        "maxLength": 36,
                        "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
                      }
                    ]
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1
                  },
                  "effect": {
                    "type": "string",
                    "enum": [
                      "grant",
                      "deny"
                    ]
                  },
                  "permissions": {
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "resource": {
                          "type": "string",
                          "minLength": 1
                        },
                        "resourceExpression": {
                          "type": "string",
                          "minLength": 1
                        },
                        "actions": {
                          "minItems": 1,
                          "type": "array",
                          "items": {
                            "type": "string",
                            "minLength": 1
                          }
                        }
                      },
                      "required": [
                        "actions"
                      ],
                      "additionalProperties": false,
                      "description": "One PMS permission. The Cloud requires exactly one of resource (exact) or resourceExpression (regular expression) so the response round-trips through SPDL."
                    }
                  },
                  "principals": {
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "minItems": 1,
                      "type": "array",
                      "items": {
                        "type": "string",
                        "pattern": "^(?:idd=[^\\r\\n\\u2028\\u2029]+:(?:user|group|role|entity):[^\\r\\n\\u2028\\u2029]+|(?:user|group|role|entity):[^\\r\\n\\u2028\\u2029]+)$",
                        "description": "An encoded PMS principal: \"<type>:<name>\" or \"idd=<idd>:<type>:<name>\", type one of user, group, role or entity."
                      }
                    }
                  },
                  "condition": {
                    "type": "string"
                  }
                },
                "required": [
                  "id",
                  "name",
                  "effect",
                  "permissions",
                  "principals"
                ],
                "additionalProperties": false
              }
            },
            "rolePolicies": {
              "maxItems": 200,
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "id": {
                    "anyOf": [
                      {
                        "type": "string",
                        "minLength": 26,
                        "maxLength": 26,
                        "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
                      },
                      {
                        "type": "string",
                        "minLength": 36,
                        "maxLength": 36,
                        "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
                      }
                    ]
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1
                  },
                  "effect": {
                    "type": "string",
                    "enum": [
                      "grant",
                      "deny"
                    ]
                  },
                  "roles": {
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1
                    }
                  },
                  "principals": {
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "pattern": "^(?:idd=[^\\r\\n\\u2028\\u2029]+:(?:user|group|role|entity):[^\\r\\n\\u2028\\u2029]+|(?:user|group|role|entity):[^\\r\\n\\u2028\\u2029]+)$",
                      "description": "An encoded PMS principal: \"<type>:<name>\" or \"idd=<idd>:<type>:<name>\", type one of user, group, role or entity."
                    }
                  },
                  "resources": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1
                    }
                  },
                  "resourceExpressions": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1
                    }
                  },
                  "condition": {
                    "type": "string"
                  }
                },
                "required": [
                  "id",
                  "name",
                  "effect",
                  "roles",
                  "principals"
                ],
                "additionalProperties": false
              }
            }
          },
          "required": [
            "name",
            "type",
            "policies",
            "rolePolicies"
          ],
          "additionalProperties": false,
          "description": "One Cloud service in the upstream GET /service shape. The token's own service is the only service it can see."
        },
        "examples": [
          [],
          [
            {
              "name": "books",
              "type": "application",
              "policies": [
                {
                  "id": "01HF7YAT000000000000000001",
                  "name": "read-books",
                  "effect": "grant",
                  "permissions": [
                    {
                      "resource": "/books",
                      "actions": [
                        "get",
                        "list"
                      ]
                    }
                  ],
                  "principals": [
                    [
                      "user:alice"
                    ],
                    [
                      "role:librarian"
                    ]
                  ]
                }
              ],
              "rolePolicies": [
                {
                  "id": "01HF7YAT000000000000000002",
                  "name": "librarian",
                  "effect": "grant",
                  "roles": [
                    "librarian"
                  ],
                  "principals": [
                    "user:alice"
                  ],
                  "resources": [
                    "/docs"
                  ],
                  "condition": "request_time > '2026-01-01 00:00:00'"
                }
              ]
            }
          ]
        ]
      },
      "PmsInvalidRequestError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "invalid_request"
          },
          {
            "error": "SPDL-0004 no viable alternative at input 'if'"
          }
        ]
      },
      "PmsUnauthorizedError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "const": "unauthorized"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "unauthorized"
          }
        ]
      },
      "PmsForbiddenError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "const": "insufficient_scope"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "insufficient_scope"
          }
        ]
      },
      "PmsNotFoundError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "const": "not_found"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "not_found"
          }
        ]
      },
      "PmsConflictError": {
        "anyOf": [
          {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "enum": [
                  "service_exists",
                  "policy_exists"
                ]
              }
            },
            "required": [
              "error"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "const": "quota_exceeded"
              },
              "limit": {
                "anyOf": [
                  {
                    "type": "number",
                    "const": 200
                  },
                  {
                    "type": "number",
                    "const": 100
                  }
                ]
              }
            },
            "required": [
              "error",
              "limit"
            ],
            "additionalProperties": false
          }
        ],
        "examples": [
          {
            "error": "service_exists"
          },
          {
            "error": "policy_exists"
          },
          {
            "error": "quota_exceeded",
            "limit": 200
          },
          {
            "error": "quota_exceeded",
            "limit": 100
          }
        ]
      },
      "PmsRequestTooLargeError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "const": "request_too_large"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "request_too_large"
          }
        ]
      },
      "PmsRateLimitError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "rate_limited",
              "daily_quota_exceeded"
            ]
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "rate_limited"
          },
          {
            "error": "daily_quota_exceeded"
          }
        ]
      },
      "PmsInternalError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "const": "internal_error"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "internal_error"
          }
        ]
      },
      "PmsQuotaUnavailableError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "const": "quota_unavailable"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "quota_unavailable"
          }
        ]
      },
      "ImportIdempotencyKey": {
        "type": "string",
        "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[47][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
        "description": "Canonical lowercase UUIDv4 or UUIDv7. Generate once per confirmed import; reuse the same key and ordered policy sources after any unknown outcome.",
        "examples": [
          "018bcfe5-6800-7000-8000-000000000001",
          "b17db639-6108-43c5-b4fd-c116d48fc6ad"
        ]
      },
      "ServiceImportRequest": {
        "type": "object",
        "properties": {
          "policies": {
            "minItems": 1,
            "maxItems": 200,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "kind": {
                  "type": "string",
                  "enum": [
                    "authz",
                    "role"
                  ],
                  "description": "Required explicit parser choice for imports. Omission never triggers detection; the selected parser also accepts ambiguous SPDL."
                },
                "spdl": {
                  "type": "string",
                  "minLength": 1,
                  "description": "One complete SPDL policy; at most 4096 UTF-8 bytes, checked at runtime."
                },
                "name": {
                  "type": "string",
                  "minLength": 1
                }
              },
              "required": [
                "kind",
                "spdl",
                "name"
              ],
              "additionalProperties": false
            }
          }
        },
        "required": [
          "policies"
        ],
        "additionalProperties": false,
        "description": "One nonempty ordered source projection from the unchanged JSON export file. Reject all exported service metadata, policy IDs, parsed ASTs, timestamps and unknown fields at the HTTP boundary; the session alone selects the target. This wire DTO is not an alternative file format.",
        "examples": [
          {
            "policies": [
              {
                "kind": "authz",
                "name": "read-books",
                "spdl": "grant user alice get /books"
              }
            ]
          }
        ]
      },
      "ServiceImportResponse": {
        "type": "object",
        "properties": {
          "serviceId": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 26,
                "maxLength": 26,
                "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
              },
              {
                "type": "string",
                "minLength": 36,
                "maxLength": 36,
                "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
              }
            ]
          },
          "revision": {
            "type": "integer",
            "exclusiveMinimum": 0,
            "maximum": 9007199254740991
          },
          "imported": {
            "type": "object",
            "properties": {
              "authz": {
                "type": "integer",
                "minimum": 0,
                "maximum": 200
              },
              "role": {
                "type": "integer",
                "minimum": 0,
                "maximum": 200
              },
              "total": {
                "type": "integer",
                "minimum": 1,
                "maximum": 200
              }
            },
            "required": [
              "authz",
              "role",
              "total"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "serviceId",
          "revision",
          "imported"
        ],
        "additionalProperties": false,
        "description": "Immutable result recorded with the atomic commit, returned unchanged on a successful replay. revision is the import's single revision increment, even if later edits have advanced the service.",
        "examples": [
          {
            "serviceId": "01HF7YAT000000000000000001",
            "revision": 3,
            "imported": {
              "authz": 1,
              "role": 0,
              "total": 1
            }
          }
        ]
      },
      "ServiceImportBadRequest": {
        "anyOf": [
          {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "const": "invalid_request"
              }
            },
            "required": [
              "error"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "const": "import_rejected"
              },
              "errors": {
                "minItems": 1,
                "maxItems": 200,
                "type": "array",
                "items": {
                  "oneOf": [
                    {
                      "type": "object",
                      "properties": {
                        "index": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 199
                        },
                        "error": {
                          "type": "string",
                          "enum": [
                            "name_conflict",
                            "duplicate_name"
                          ]
                        }
                      },
                      "required": [
                        "index",
                        "error"
                      ],
                      "additionalProperties": false
                    },
                    {
                      "type": "object",
                      "properties": {
                        "error": {
                          "type": "string",
                          "enum": [
                            "parse_error",
                            "custom_function_unsupported"
                          ]
                        },
                        "message": {
                          "type": "string"
                        },
                        "line": {
                          "type": "integer",
                          "exclusiveMinimum": 0,
                          "maximum": 9007199254740991
                        },
                        "column": {
                          "type": "integer",
                          "exclusiveMinimum": 0,
                          "maximum": 9007199254740991
                        },
                        "index": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 199
                        }
                      },
                      "required": [
                        "error",
                        "message",
                        "line",
                        "column",
                        "index"
                      ],
                      "additionalProperties": false
                    },
                    {
                      "type": "object",
                      "properties": {
                        "index": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 199
                        },
                        "error": {
                          "type": "string",
                          "const": "quota_exceeded"
                        },
                        "limit": {
                          "type": "number",
                          "const": 200
                        }
                      },
                      "required": [
                        "index",
                        "error",
                        "limit"
                      ],
                      "additionalProperties": false
                    }
                  ]
                },
                "contains": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "enum": [
                        "parse_error",
                        "custom_function_unsupported"
                      ]
                    }
                  }
                },
                "minContains": 1
              }
            },
            "required": [
              "error",
              "errors"
            ],
            "additionalProperties": false
          }
        ],
        "examples": [
          {
            "error": "invalid_request"
          },
          {
            "error": "import_rejected",
            "errors": [
              {
                "index": 0,
                "error": "parse_error",
                "message": "Invalid effect",
                "line": 1,
                "column": 1
              }
            ]
          }
        ]
      },
      "ServiceImportConflict": {
        "anyOf": [
          {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "const": "idempotency_conflict"
              }
            },
            "required": [
              "error"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "const": "import_rejected"
              },
              "errors": {
                "minItems": 1,
                "maxItems": 200,
                "type": "array",
                "items": {
                  "oneOf": [
                    {
                      "type": "object",
                      "properties": {
                        "index": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 199
                        },
                        "error": {
                          "type": "string",
                          "enum": [
                            "name_conflict",
                            "duplicate_name"
                          ]
                        }
                      },
                      "required": [
                        "index",
                        "error"
                      ],
                      "additionalProperties": false
                    },
                    {
                      "type": "object",
                      "properties": {
                        "index": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 199
                        },
                        "error": {
                          "type": "string",
                          "const": "quota_exceeded"
                        },
                        "limit": {
                          "type": "number",
                          "const": 200
                        }
                      },
                      "required": [
                        "index",
                        "error",
                        "limit"
                      ],
                      "additionalProperties": false
                    }
                  ]
                }
              }
            },
            "required": [
              "error",
              "errors"
            ],
            "additionalProperties": false
          }
        ],
        "examples": [
          {
            "error": "idempotency_conflict"
          },
          {
            "error": "import_rejected",
            "errors": [
              {
                "index": 0,
                "error": "quota_exceeded",
                "limit": 200
              }
            ]
          }
        ]
      },
      "ServiceImportUnauthorized": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "unauthorized"
            ]
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "unauthorized"
          }
        ]
      },
      "ServiceImportForbidden": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "csrf_failed"
            ]
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "csrf_failed"
          }
        ]
      },
      "ServiceImportNotFound": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "not_found"
            ]
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "not_found"
          }
        ]
      },
      "ServiceImportTooLarge": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "request_too_large"
            ]
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "request_too_large"
          }
        ]
      },
      "ServiceImportRateLimited": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "rate_limited",
              "daily_quota_exceeded"
            ]
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "rate_limited"
          },
          {
            "error": "daily_quota_exceeded"
          }
        ]
      },
      "ServiceImportInternalError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "internal_error"
            ]
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "error": "internal_error"
          }
        ]
      },
      "ServiceImportUnavailable": {
        "anyOf": [
          {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "enum": [
                  "quota_unavailable"
                ]
              }
            },
            "required": [
              "error"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "const": "import_publication_pending"
              },
              "result": {
                "type": "object",
                "properties": {
                  "serviceId": {
                    "anyOf": [
                      {
                        "type": "string",
                        "minLength": 26,
                        "maxLength": 26,
                        "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
                      },
                      {
                        "type": "string",
                        "minLength": 36,
                        "maxLength": 36,
                        "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
                      }
                    ]
                  },
                  "revision": {
                    "type": "integer",
                    "exclusiveMinimum": 0,
                    "maximum": 9007199254740991
                  },
                  "imported": {
                    "type": "object",
                    "properties": {
                      "authz": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 200
                      },
                      "role": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 200
                      },
                      "total": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 200
                      }
                    },
                    "required": [
                      "authz",
                      "role",
                      "total"
                    ],
                    "additionalProperties": false
                  }
                },
                "required": [
                  "serviceId",
                  "revision",
                  "imported"
                ],
                "additionalProperties": false,
                "description": "Immutable result recorded with the atomic commit, returned unchanged on a successful replay. revision is the import's single revision increment, even if later edits have advanced the service."
              }
            },
            "required": [
              "error",
              "result"
            ],
            "additionalProperties": false
          }
        ],
        "examples": [
          {
            "error": "quota_unavailable"
          },
          {
            "error": "import_publication_pending",
            "result": {
              "serviceId": "01HF7YAT000000000000000001",
              "revision": 3,
              "imported": {
                "authz": 1,
                "role": 0,
                "total": 1
              }
            }
          }
        ]
      },
      "HealthResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "const": "ok"
          },
          "version": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128
          }
        },
        "required": [
          "status",
          "version"
        ],
        "additionalProperties": false,
        "examples": [
          {
            "status": "ok",
            "version": "0123456789abcdef0123456789abcdef01234567"
          }
        ]
      }
    },
    "securitySchemes": {
      "pmsToken": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "spdl_<canonical uppercase ULID>_<43 base62 digits>",
        "description": "A policy-management credential issued in the console with recent authentication. Fixed scope pms:read (list/get) or pms:write (also create/delete and service delete); ads tokens are rejected with 403 insufficient_scope, and management tokens are rejected by /authz-check/v1. Management scopes cannot be created while the stage has sign-in disabled (today prod). HMAC-only storage, one-time disclosure, revocable, no intrinsic expiry in v1. Never give manage mode to an agent governed by these policies."
      },
      "apiToken": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "spdl_<canonical uppercase ULID>_<43 base62 digits>",
        "description": "ADS-only for the token's own service; never authorizes PMS. pms:read/pms:write credentials return 403 insufficient_scope on this API. Credential shape is AdsBearerToken; require a single Bearer scheme plus the exact case-sensitive credential. One-time disclosure on issuance, HMAC-only persisted verification, no intrinsic expiry in v1. Positive verification caches have fixed verifiedAt/validUntil authority of at most 60000 ms from the START of the authoritative D1 lookup. Cache hits, KV refills and late writes racing revocation never extend that deadline; expired records require fresh D1 verification and cannot authorize on storage failure. KV failure may fall back to D1 for token verification only, never for policy evaluation. Revocation invalidates existing local/KV entries; account/service deletion and account suspension use the same bounded authority. This contract does not ship a verifier or enable runtime cutover; #81 requires accepted #98/#99, #79 stage credentials and probe/rollback compatibility."
      },
      "sessionCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "__Host-sc_session",
        "description": "__Host-sc_session=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA; Secure; HttpOnly; SameSite=Strict; Path=/"
      },
      "githubOAuthCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "__Host-sc_github_oauth",
        "description": "__Host-sc_github_oauth=<signed provider/intent/sessionBinding/state/PKCE/nonce/issuedAt/expiresAt>; Max-Age=600; Secure; HttpOnly; SameSite=Lax; Path=/"
      },
      "googleOAuthCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "__Host-sc_google_oauth",
        "description": "__Host-sc_google_oauth=<signed provider/intent/sessionBinding/state/PKCE/nonce/issuedAt/expiresAt>; Max-Age=600; Secure; HttpOnly; SameSite=Lax; Path=/"
      },
      "githubPendingCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "__Host-sc_github_pending",
        "description": "__Host-sc_github_pending=<signed one-time browser-bound create credential>; Max-Age=600; Secure; HttpOnly; SameSite=Strict; Path=/"
      },
      "googlePendingCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "__Host-sc_google_pending",
        "description": "__Host-sc_google_pending=<signed one-time browser-bound create credential>; Max-Age=600; Secure; HttpOnly; SameSite=Strict; Path=/"
      },
      "githubCompletionCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "__Host-sc_github_completion",
        "description": "__Host-sc_github_completion=<signed one-time completion credential bound to provider/context/intent/initiating session/user/verified identity/callback time/original expiry>; Max-Age=<remaining flow seconds, at most 600>; Secure; HttpOnly; SameSite=Strict; Path=/"
      },
      "googleCompletionCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "__Host-sc_google_completion",
        "description": "__Host-sc_google_completion=<signed one-time completion credential bound to provider/context/intent/initiating session/user/verified identity/callback time/original expiry>; Max-Age=<remaining flow seconds, at most 600>; Secure; HttpOnly; SameSite=Strict; Path=/"
      }
    },
    "headers": {
      "Cache-Control": {
        "description": "Credentials and authentication responses must not be cached.",
        "schema": {
          "type": "string",
          "const": "no-store"
        },
        "example": "no-store"
      },
      "Retry-After": {
        "description": "Required on every 429. Positive delay-seconds; daily quota uses seconds until next UTC midnight, rounded up.",
        "schema": {
          "$ref": "#/components/schemas/RetryAfter"
        }
      },
      "Set-Cookie": {
        "description": "Clear the matching provider OAuth cookie on every callback failure, with Max-Age=0 and its original attributes (isolated test cookie name for beta/gamma test callbacks).",
        "schema": {
          "type": "string"
        }
      },
      "Set-Cookie2": {
        "description": "Set __Host-sc_{provider}_oauth=<signed provider/intent/sessionBinding/state/PKCE/nonce/issuedAt/expiresAt>; Max-Age=600; Secure; HttpOnly; SameSite=Lax; Path=/; bind signed intent/provider/initiating session and enforce server expiry. Return authorizationUrl in JSON for top-level navigation. No Domain attribute.",
        "schema": {
          "type": "string"
        }
      },
      "Set-Cookie3": {
        "description": "After session/CSRF/input gates, clear __Host-sc_{provider}_completion=<signed one-time completion credential bound to provider/context/intent/initiating session/user/verified identity/callback time/original expiry>; Max-Age=<remaining flow seconds, at most 600>; Secure; HttpOnly; SameSite=Strict; Path=/ with Max-Age=0 on terminal credential/binding/identity rejection. Invalidate the authenticated rejected credential, never unrelated flows. Pre-gate rejection does not consume or renew completion state. No Domain attribute.",
        "schema": {
          "type": "string"
        }
      },
      "Set-Cookie4": {
        "description": "Clear the session cookie with identical attributes and Max-Age=0.",
        "schema": {
          "type": "string"
        },
        "example": "__Host-sc_session=; Max-Age=0; Secure; HttpOnly; SameSite=Strict; Path=/"
      },
      "Cache-Control2": {
        "schema": {
          "type": "string",
          "const": "no-store"
        }
      },
      "X-Quota-Daily-Remaining": {
        "schema": {
          "type": "string",
          "pattern": "^(0|[1-9][0-9]*)$"
        },
        "description": "Present after handler admission, including Cloud-side 5xx responses whose refund the independent relief ledger approved within the per-account daily cap. Estimate based on the latest lease's unreserved snapshot, local remaining/credits and unsettled throttled usage; other isolates and abandoned reservations make it approximate. Informational only."
      },
      "Retry-After2": {
        "schema": {
          "type": "string",
          "pattern": "^[1-9][0-9]*$"
        },
        "description": "Required positive delay-seconds; for daily_quota_exceeded, ceil((next UTC midnight - now)/1000)."
      },
      "Retry-After3": {
        "schema": {
          "type": "string",
          "pattern": "^[1-9][0-9]*$"
        },
        "description": "Required for quota_unavailable: 30."
      },
      "X-Service-Revision": {
        "schema": {
          "type": "string",
          "pattern": "^(0|[1-9][0-9]*)$"
        },
        "description": "Canonical decimal revision of the consistent D1 snapshot the Playground request was evaluated against. The service row and policy source are read in one D1 transaction, so this is the exact policy version behind the response body; a save committed after the client loaded its policy list changes it. Present on every 200; error responses omit it because no evaluation result exists."
      },
      "Cache-Control3": {
        "schema": {
          "type": "string",
          "const": "no-store"
        },
        "example": "no-store",
        "description": "Credentials and management responses must not be cached."
      },
      "X-Quota-Daily-Remaining2": {
        "schema": {
          "type": "string",
          "pattern": "^(0|[1-9][0-9]*)$"
        },
        "description": "Present after handler admission, including Cloud-side 5xx responses whose refund the independent relief ledger approved within the per-account daily cap. Estimate based on the latest lease's unreserved snapshot, local remaining/credits and unsettled throttled usage; other isolates and abandoned reservations make it approximate. Omitted on routing/auth/CSRF/burst/reservation rejections.",
        "example": "9999"
      },
      "Retry-After4": {
        "schema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "type": "string",
          "pattern": "^[1-9][0-9]*$",
          "description": "Required on every 429: positive integer delay-seconds. For daily_quota_exceeded, ceil((next UTC midnight - now)/1000)."
        },
        "description": "Required positive integer delay-seconds. Session/burst limit: 60; daily_quota_exceeded: ceil((next 00:00 UTC - now)/1000).",
        "example": "60"
      },
      "Retry-After5": {
        "schema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "type": "string",
          "pattern": "^[1-9][0-9]*$",
          "description": "Required on every 429: positive integer delay-seconds. For daily_quota_exceeded, ceil((next UTC midnight - now)/1000)."
        },
        "description": "Required for quota_unavailable: 30.",
        "example": "30"
      },
      "Cache-Control4": {
        "schema": {
          "type": "string",
          "const": "no-store"
        },
        "example": "no-store"
      },
      "X-RateLimit-Limit": {
        "schema": {
          "type": "string",
          "const": "600"
        },
        "example": "600",
        "description": "Present after bearer verification. Per-token 600 requests per 60 seconds, approximate per location. The binding exposes no remaining count; X-RateLimit-Remaining is omitted. Early size/verification failures may omit this header."
      },
      "X-Quota-Daily-Remaining3": {
        "schema": {
          "type": "string",
          "pattern": "^(0|[1-9][0-9]*)$"
        },
        "example": "9999",
        "description": "Present after handler admission, including Cloud-side 5xx responses whose refund the independent relief ledger approved within the per-account daily cap; omitted on authentication, scope, routing, size, burst and quota-reservation rejections. Estimate based on the latest lease's unreserved snapshot, local remaining and approved refund credits. Outage admissions are extra allowance outside the daily quota and are not deducted. Other isolates and abandoned reservations make it approximate; it resets at UTC midnight. Informational only."
      },
      "WWW-Authenticate": {
        "schema": {
          "type": "string",
          "const": "Bearer"
        },
        "example": "Bearer"
      },
      "Allow": {
        "schema": {
          "type": "string",
          "const": "POST"
        },
        "example": "POST",
        "description": "The only method these paths accept."
      },
      "Retry-After6": {
        "schema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "type": "string",
          "pattern": "^[1-9][0-9]*$",
          "description": "Required on every 429: positive integer delay-seconds. For daily_quota_exceeded, ceil((next UTC midnight - now)/1000)."
        },
        "description": "Required positive integer delay-seconds. rate_limited: 60; daily_quota_exceeded: ceil((next 00:00 UTC - now)/1000).",
        "example": "60"
      },
      "Retry-After7": {
        "schema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "type": "string",
          "pattern": "^[1-9][0-9]*$",
          "description": "Required on every 429: positive integer delay-seconds. For daily_quota_exceeded, ceil((next UTC midnight - now)/1000)."
        },
        "description": "Required for quota_unavailable: 30. Not required for snapshot_unavailable.",
        "example": "30"
      },
      "Cache-Control5": {
        "schema": {
          "type": "string",
          "const": "no-store"
        },
        "example": "no-store",
        "description": "Management responses must not be cached."
      },
      "X-Quota-Daily-Remaining4": {
        "schema": {
          "type": "string",
          "pattern": "^(0|[1-9][0-9]*)$"
        },
        "example": "9999",
        "description": "Present after handler admission, including Cloud-side 5xx responses whose refund the independent relief ledger approved within the per-account daily cap. Account-wide estimate under the same lease semantics as the console API; omitted on auth, scope, routing, size, burst and reservation rejections."
      },
      "X-RateLimit-Limit2": {
        "schema": {
          "type": "string",
          "const": "600"
        },
        "example": "600",
        "description": "Present after bearer verification. Per-token 600 requests per 60 seconds, shared with the ADS limiter; the binding exposes no remaining count, so X-RateLimit-Remaining is omitted."
      },
      "Retry-After8": {
        "schema": {
          "type": "string",
          "pattern": "^[1-9][0-9]*$"
        },
        "description": "Required positive delay-seconds. Burst: 60. daily_quota_exceeded: ceil((next 00:00 UTC - now)/1000).",
        "example": "60"
      },
      "Retry-After9": {
        "schema": {
          "type": "string",
          "pattern": "^[1-9][0-9]*$"
        },
        "description": "Required for quota_unavailable: 30.",
        "example": "30"
      },
      "Retry-After10": {
        "schema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "type": "string",
          "pattern": "^[1-9][0-9]*$",
          "description": "Required on every 429: positive integer delay-seconds. For daily_quota_exceeded, ceil((next UTC midnight - now)/1000)."
        },
        "description": "Required positive delay-seconds; daily quota rounds up to next UTC midnight."
      },
      "Retry-After11": {
        "schema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "type": "string",
          "pattern": "^[1-9][0-9]*$",
          "description": "Required on every 429: positive integer delay-seconds. For daily_quota_exceeded, ceil((next UTC midnight - now)/1000)."
        },
        "description": "Required for quota_unavailable: 30. Not required for import_publication_pending."
      }
    },
    "responses": {
      "Response1": {
        "description": "internal_error: no secrets or upstream details.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AuthErrorResponse"
            }
          }
        }
      },
      "Response2": {
        "description": "invalid_request, invalid_oauth_state or invalid_pending_credential; no credential/account details.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AuthErrorResponse"
            }
          }
        }
      },
      "Response3": {
        "description": "rate_limited; mandatory Retry-After. Auth/session routes are excluded from daily PMS/ADS quota and do not emit daily_quota_exceeded.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AuthErrorResponse"
            }
          }
        }
      },
      "Response4": {
        "description": "sign_in_disabled when runtime sign-in is closed; provider_unavailable for temporary provider outage; quota_unavailable specifically when GET /api/me cannot read AccountQuota, with Retry-After: 30. Fail closed.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AuthErrorResponse"
            }
          }
        }
      },
      "Response5": {
        "description": "csrf_failed, verified_email_required or reauth_required as applicable.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AuthErrorResponse"
            }
          }
        }
      },
      "Response6": {
        "description": "request_too_large: request exceeds 64 KiB before parsing.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AuthErrorResponse"
            }
          }
        }
      },
      "Response7": {
        "description": "Success.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control"
          },
          "Set-Cookie": {
            "$ref": "#/components/headers/Set-Cookie2"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/OAuthStartResponse"
            }
          }
        }
      },
      "Response8": {
        "description": "unauthorized: missing, expired, revoked or non-active account session.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AuthErrorResponse"
            }
          }
        }
      },
      "Response9": {
        "description": "Success.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control"
          },
          "Set-Cookie": {
            "$ref": "#/components/headers/Set-Cookie4"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AuthSuccessResponse"
            }
          }
        }
      },
      "Response10": {
        "description": "invalid_request",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AuthErrorResponse"
            }
          }
        }
      },
      "Response11": {
        "description": "unauthorized",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AuthErrorResponse"
            }
          }
        }
      },
      "Response12": {
        "description": "reauth_required, csrf_failed",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AuthErrorResponse"
            }
          }
        }
      },
      "Response13": {
        "description": "Success",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control2"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ServiceResponse"
            }
          }
        }
      },
      "Response14": {
        "description": "invalid_request or SPDL parse error with one-based line/column.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control2"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ServiceError"
            }
          }
        }
      },
      "Response15": {
        "description": "Request rejected; no internal details.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control2"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ServiceError"
            }
          }
        }
      },
      "Response16": {
        "description": "csrf_failed; service deletion also requires recent authentication (reauth_required).",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control2"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ServiceError"
            }
          }
        }
      },
      "Response17": {
        "description": "service_exists (duplicate service name in this account), policy_exists, or quota_exceeded with limit: 200 (authz + role combined across the account) or limit: 100 (service cap).",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control2"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ServiceError"
            }
          }
        }
      },
      "Response18": {
        "description": "rate_limited (burst/session limit, no daily charge) or daily_quota_exceeded; mandatory Retry-After, rounded up to next UTC midnight for the daily cap.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control2"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After2"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ServiceError"
            },
            "examples": {
              "rate_limited": {
                "value": {
                  "error": "rate_limited"
                }
              },
              "daily_quota_exceeded": {
                "value": {
                  "error": "daily_quota_exceeded"
                }
              }
            }
          }
        }
      },
      "Response19": {
        "description": "quota_unavailable: fail closed before entering the handler when no valid reserved lease is available. Retry-After: 30 is required.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control2"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After3"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ServiceError"
            },
            "example": {
              "error": "quota_unavailable"
            }
          }
        }
      },
      "Response20": {
        "description": "Success",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control2"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining"
          }
        }
      },
      "Response21": {
        "description": "Success",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control2"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PolicyListResponse"
            }
          }
        }
      },
      "Response22": {
        "description": "Success",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control2"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PolicyResponse"
            }
          }
        }
      },
      "Response23": {
        "description": "The Speedle REST body for the same request, selected by request.op. isAllowed: ServiceTestDecision; diagnose: ServiceTestDiagnosis; allGrantedRoles: ServiceTestRoles; allGrantedPermissions: ServiceTestPermissions. [] matches both all-granted result shapes, so the union is non-exclusive; the handler must validate the exact per-operation schema. A Speedle 400 (for example an attribute conversion error) returns 400 {error: \"invalid_request\", message} with the Speedle message.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control2"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining"
          },
          "X-Service-Revision": {
            "$ref": "#/components/headers/X-Service-Revision"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ServiceTestResponse"
            }
          }
        }
      },
      "Response24": {
        "description": "Success",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control2"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ServiceExport"
            }
          },
          "text/plain": {
            "schema": {
              "type": "string"
            }
          }
        }
      },
      "Response25": {
        "description": "Success.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control3"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining2"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/TokenListResponse"
            }
          }
        }
      },
      "Response26": {
        "description": "invalid_request: malformed body/ID/pagination, duplicate or unknown query parameters; mutations reject all queries. No internal details.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control3"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining2"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/TokenInvalidRequestError"
            }
          }
        }
      },
      "Response27": {
        "description": "unauthorized: missing, expired, revoked or inactive-account session. A bearer credential cannot replace the session.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control3"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/TokenUnauthorizedError"
            }
          }
        }
      },
      "Response28": {
        "description": "not_found: no owned service or no owned active token; do not disclose other accounts' records.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control3"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining2"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/TokenNotFoundError"
            }
          }
        }
      },
      "Response29": {
        "description": "request_too_large: exceeds 64 KiB before parsing; no daily charge.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control3"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/TokenRequestTooLargeError"
            }
          }
        }
      },
      "Response30": {
        "description": "rate_limited (session/burst, uncharged) or exactly daily_quota_exceeded (no handler entry); Retry-After is mandatory for both.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control3"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After4"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/TokenRateLimitError"
            }
          }
        }
      },
      "Response31": {
        "description": "internal_error: no credentials or internal details. A Cloud-side handler failure does not consume daily quota and retains the remaining-quota estimate header.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control3"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining2"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/TokenInternalError"
            }
          }
        }
      },
      "Response32": {
        "description": "Exactly quota_unavailable: no confirmed valid reserved lease, fail closed without entering the handler or issuing a token. Existing valid reserved leases may still be consumed. Retry-After: 30 is required.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control3"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After5"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/TokenQuotaUnavailableError"
            }
          }
        }
      },
      "Response33": {
        "description": "Success.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control3"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining2"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/TokenCreateResponse"
            }
          }
        }
      },
      "Response34": {
        "description": "csrf_failed; reauth_required only when creating a token; management_scope_disabled only when management-scope issuance is requested while sign-in is closed.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control3"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining2"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/TokenIssuanceForbiddenError"
            }
          }
        }
      },
      "Response35": {
        "description": "quota_exceeded with mandatory limit: 2; active tokens per service, independent of the policy-count cap.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control3"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining2"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/TokenQuotaExceededError"
            }
          }
        }
      },
      "Response36": {
        "description": "Revoked; no response body.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control3"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining2"
          }
        }
      },
      "Response37": {
        "description": "csrf_failed: missing or mismatched Origin/X-SC-CSRF. Revocation cannot produce reauth_required or management_scope_disabled.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control3"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining2"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/TokenCsrfError"
            }
          }
        }
      },
      "Response38": {
        "description": "Speedle httputils.HandleError body {error: \"SPDL-0004 <message>\"}, byte for byte as Speedle writes it: invalid JSON or a field of the wrong JSON type (Go decoder message), attribute type conversion, null values, unsupported datetime layouts. Cloud-only, in the same shape: null principal or attribute items (Speedle crashes the connection), more than 50 attributes or more than 16,384 canonical UTF-8 JSON attribute bytes.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control4"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining3"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AdsInvalidRequestError"
            }
          }
        }
      },
      "Response39": {
        "description": "unauthorized: missing/malformed/unknown bearer credential or wrong secret. A cookie session alone never authorizes ADS. Revoked credentials and deleted/inactive owners cease authorization within the fixed verification authority window, at most 60 seconds. No daily charge.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control4"
          },
          "WWW-Authenticate": {
            "$ref": "#/components/headers/WWW-Authenticate"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AdsUnauthorizedError"
            }
          }
        }
      },
      "Response40": {
        "description": "insufficient_scope (Cloud-only): a valid credential whose fixed scope is pms:read or pms:write cannot call the ADS API. This check happens after credential verification but before the burst limiter, daily quota or request decoding, and is not charged. service_mismatch (Cloud-only): a non-empty serviceName differs from the service established by bearer verification. Speedle would answer SERVICE_NOT_FOUND; Cloud never evaluates another tenant/service or substitutes a demo. An omitted or empty serviceName is answered exactly as Speedle answers it: 200 SERVICE_NOT_FOUND (reason 2) from is-allowed, 500 {error: \"SPDL-2001 ...\"} from the all-granted operations. all-granted-roles and all-granted-permissions can also return a Speedle 403 {error: message} for an ExceedLimit evaluator error.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control4"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining3"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AdsServiceMismatchError"
            }
          }
        }
      },
      "Response41": {
        "description": "method_not_allowed: these paths accept only POST. The Worker answers unsupported methods with this JSON envelope and Allow: POST before authentication, quota or body decoding. The edge WAF may block a non-POST request earlier with its own response (#82), so clients must also accept a non-JSON block response at the edge.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control4"
          },
          "Allow": {
            "$ref": "#/components/headers/Allow"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AdsMethodNotAllowedError"
            }
          }
        }
      },
      "Response42": {
        "description": "request_too_large: the global 64 KiB request-body guard can reject before verification or JSON parsing; no daily charge.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control4"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AdsRequestTooLargeError"
            }
          }
        }
      },
      "Response43": {
        "description": "rate_limited for the approximate per-token 600/minute/location burst cap, or daily_quota_exceeded for the strict account-wide PMS+ADS daily quota. Both require Retry-After and do not enter the handler.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control4"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After6"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AdsRateLimitError"
            }
          }
        }
      },
      "Response44": {
        "description": "internal_error: fail closed without credential, parser, engine or storage details. A Cloud-side failure after handler entry is refunded only when the independent relief ledger approves it within the per-account daily cap; otherwise it stays charged. all-granted-roles and all-granted-permissions can also return a Speedle 500 {error: message} for an evaluator error the request caused, as Speedle does; that Speedle-compatible engine error is always charged like any 4xx or denial.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control4"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining3"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AdsInternalError"
            }
          }
        }
      },
      "Response45": {
        "description": "quota_unavailable: no confirmed lease for the current UTC day, the throttled ADS queue would wait more than ten seconds, the per-account outage allowance is exhausted, or the relief ledger is unavailable; fail closed before handler entry with Retry-After: 30. snapshot_unavailable: no usable trusted scoped snapshot (missing, malformed, out of bounds or mismatched); fail closed without a daily charge, with no bundled demo or D1 policy-evaluation fallback.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control4"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining3"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After7"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AdsUnavailableError"
            }
          }
        }
      },
      "Response46": {
        "description": "invalid_request, or the engine's parse/custom-function message as {error: string}; no source, credentials or internal details are echoed.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control5"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining4"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PmsInvalidRequestError"
            }
          }
        }
      },
      "Response47": {
        "description": "unauthorized: missing, malformed, expired, revoked or inactive-account bearer credential. A session cookie never authorizes this API.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control5"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PmsUnauthorizedError"
            }
          }
        }
      },
      "Response48": {
        "description": "insufficient_scope: the token is valid but its fixed scope cannot perform this operation (ads tokens cannot manage; pms:read tokens cannot mutate).",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control5"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PmsForbiddenError"
            }
          }
        }
      },
      "Response49": {
        "description": "not_found: the service name does not exactly match this token's service, or the policy ID is unknown, malformed, of another kind or another service. Never discloses other accounts.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control5"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining4"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PmsNotFoundError"
            }
          }
        }
      },
      "Response50": {
        "description": "service_exists (duplicate service name in this account), policy_exists, or quota_exceeded with limit: 200 for the combined authz + role policy cap across the account or limit: 100 for the service cap.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control5"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining4"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PmsConflictError"
            }
          }
        }
      },
      "Response51": {
        "description": "request_too_large: the request exceeds 64 KiB before parsing; no daily charge.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control5"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PmsRequestTooLargeError"
            }
          }
        }
      },
      "Response52": {
        "description": "rate_limited (per-token burst, uncharged) or daily_quota_exceeded (no handler entry); Retry-After is mandatory.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control5"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After8"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PmsRateLimitError"
            }
          }
        }
      },
      "Response53": {
        "description": "internal_error: no credentials or storage details. A Cloud-side handler failure does not consume daily quota and retains the remaining-quota estimate header.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control5"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining4"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PmsInternalError"
            }
          }
        }
      },
      "Response54": {
        "description": "quota_unavailable: no confirmed valid reserved lease; fail closed before entering the handler. Retry-After: 30 is required.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control5"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After9"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PmsQuotaUnavailableError"
            }
          }
        }
      },
      "Response55": {
        "description": "Success.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control5"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining4"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit2"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PmsService"
            }
          }
        }
      },
      "Response56": {
        "description": "Deleted; no response body.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control5"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining4"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit2"
          }
        }
      },
      "Response57": {
        "description": "Success.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control5"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining4"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit2"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PmsPolicy"
            }
          }
        }
      },
      "Response58": {
        "description": "Success.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control5"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining4"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit2"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PmsRolePolicy"
            }
          }
        }
      },
      "Response59": {
        "description": "All policies committed and snapshot published, or an identical successful replay. The immutable result describes the original commit.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control2"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ServiceImportResponse"
            }
          }
        }
      },
      "Response60": {
        "description": "invalid_request for malformed JSON/export, empty batch, invalid/missing Idempotency-Key or any query parameter. import_rejected with item indexes for SPDL/custom-function errors; the whole batch is unchanged.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control2"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ServiceImportBadRequest"
            }
          }
        }
      },
      "Response61": {
        "description": "Missing, expired or revoked session, or inactive account. Bearer never authorizes import.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control2"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ServiceImportUnauthorized"
            }
          }
        }
      },
      "Response62": {
        "description": "csrf_failed: exact stage Origin AND X-SC-CSRF: 1 required.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control2"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ServiceImportForbidden"
            }
          }
        }
      },
      "Response63": {
        "description": "The target service does not exist or is not owned by the signed-in account; import never creates a service.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control2"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ServiceImportNotFound"
            }
          }
        }
      },
      "Response64": {
        "description": "import_rejected with per-item conflicts/combined-cap errors and no writes; idempotency_conflict for a key already committed with a different ordered source projection or target service (including deletion/recreation).",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control2"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ServiceImportConflict"
            }
          }
        }
      },
      "Response65": {
        "description": "request_too_large above 5100000 wire bytes, including streamed bodies without Content-Length; no daily charge or mutation.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control2"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ServiceImportTooLarge"
            }
          }
        }
      },
      "Response66": {
        "description": "rate_limited before daily quota (no charge) or daily_quota_exceeded. Mandatory Retry-After; daily delay rounds up to next UTC midnight.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control2"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After10"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ServiceImportRateLimited"
            }
          }
        }
      },
      "Response67": {
        "description": "Masked internal failure; outcome can be unknown. Retry the same ordered sources and Idempotency-Key, never a new key.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control2"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ServiceImportInternalError"
            }
          }
        }
      },
      "Response68": {
        "description": "quota_unavailable: fail closed before handler, no charge, Retry-After: 30 required. import_publication_pending: the entire D1 batch is already committed; result is durable, retry the same key/sources to repair publication from current D1 without inserting or bumping revision again.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control2"
          },
          "X-Quota-Daily-Remaining": {
            "$ref": "#/components/headers/X-Quota-Daily-Remaining"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After11"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ServiceImportUnavailable"
            }
          }
        }
      }
    },
    "parameters": {
      "provider": {
        "name": "provider",
        "in": "path",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/AuthProvider"
        }
      },
      "Origin": {
        "name": "Origin",
        "in": "header",
        "required": true,
        "schema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "type": "string",
          "enum": [
            "https://b.speedle.io",
            "https://g.speedle.io",
            "https://cloud.speedle.io"
          ]
        },
        "description": "Must exactly equal the current stage's console origin; other stages are rejected.",
        "example": "https://cloud.speedle.io"
      },
      "X-SC-CSRF": {
        "name": "X-SC-CSRF",
        "in": "header",
        "required": true,
        "schema": {
          "type": "string",
          "const": "1"
        },
        "example": "1"
      },
      "Origin2": {
        "name": "Origin",
        "in": "header",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uri"
        },
        "description": "Must exactly equal the current stage console origin."
      },
      "X-SC-CSRF2": {
        "name": "X-SC-CSRF",
        "in": "header",
        "required": true,
        "schema": {
          "type": "string",
          "const": "1"
        }
      },
      "serviceId": {
        "name": "serviceId",
        "in": "path",
        "required": true,
        "schema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "anyOf": [
            {
              "type": "string",
              "minLength": 26,
              "maxLength": 26,
              "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
            },
            {
              "type": "string",
              "minLength": 36,
              "maxLength": 36,
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
            }
          ]
        },
        "description": "Service owned by the signed-in account. Unknown, malformed or other-account IDs return 404 not_found."
      },
      "id": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "anyOf": [
            {
              "type": "string",
              "minLength": 26,
              "maxLength": 26,
              "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
            },
            {
              "type": "string",
              "minLength": 36,
              "maxLength": 36,
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
            }
          ]
        }
      },
      "format": {
        "name": "format",
        "in": "query",
        "required": true,
        "schema": {
          "type": "string",
          "enum": [
            "spdl",
            "json"
          ]
        }
      },
      "limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "schema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "description": "Canonical decimal query string, 1–100; omitted means 25. No coercion, signs, leading zeroes or whitespace.",
          "default": "25",
          "type": "string",
          "pattern": "^(?:[1-9]|[1-9][0-9]|100)(?![\\s\\S])"
        },
        "example": "25"
      },
      "cursor": {
        "name": "cursor",
        "in": "query",
        "required": false,
        "schema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "description": "Exclusive canonical ID (ULID or legacy UUIDv7) boundary in descending id order: return owned rows with id < cursor. A position only, never an ownership selector; every query must remain tenant-scoped.",
          "anyOf": [
            {
              "type": "string",
              "minLength": 26,
              "maxLength": 26,
              "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
            },
            {
              "type": "string",
              "minLength": 36,
              "maxLength": 36,
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
            }
          ]
        },
        "example": "01HF7YAT000000000000000002"
      },
      "Origin3": {
        "name": "Origin",
        "in": "header",
        "required": true,
        "schema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "type": "string",
          "enum": [
            "https://b.speedle.io",
            "https://g.speedle.io",
            "https://cloud.speedle.io"
          ]
        },
        "description": "Must exactly equal the CURRENT stage's console origin; another listed stage is rejected.",
        "example": "https://cloud.speedle.io"
      },
      "serviceId2": {
        "name": "serviceId",
        "in": "path",
        "required": true,
        "schema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "anyOf": [
            {
              "type": "string",
              "minLength": 26,
              "maxLength": 26,
              "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
            },
            {
              "type": "string",
              "minLength": 36,
              "maxLength": 36,
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
            }
          ]
        },
        "description": "Service owned by the signed-in account. Unknown, malformed or other-account IDs return 404 not_found.",
        "example": "01HF7YAT000000000000000002"
      },
      "id2": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "anyOf": [
            {
              "type": "string",
              "minLength": 26,
              "maxLength": 26,
              "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
            },
            {
              "type": "string",
              "minLength": 36,
              "maxLength": 36,
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
            }
          ]
        },
        "example": "01HF7YAT000000000000000001"
      },
      "serviceName": {
        "name": "serviceName",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$"
        },
        "description": "Must exactly equal the token's service name (case-sensitive); otherwise 404.",
        "example": "books"
      },
      "policyId": {
        "name": "policyId",
        "in": "path",
        "required": true,
        "schema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "anyOf": [
            {
              "type": "string",
              "minLength": 26,
              "maxLength": 26,
              "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$"
            },
            {
              "type": "string",
              "minLength": 36,
              "maxLength": 36,
              "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
            }
          ]
        },
        "description": "Cloud policy ID (canonical uppercase ULID, or legacy lowercase UUIDv7). Never a policy name.",
        "example": "01HF7YAT000000000000000001"
      },
      "Idempotency-Key": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/ImportIdempotencyKey"
        },
        "description": "Generate once per confirmed batch; retain the same key and file until the outcome is known. Reject missing, duplicate/comma-joined or invalid values. Never log the key or policy source."
      }
    },
    "requestBodies": {
      "RequestBody1": {
        "required": true,
        "description": "JSON only, at most 64 KiB. Empty actions explicitly send {}. Reject unknown fields.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/EmptyAuthRequest"
            }
          }
        }
      },
      "RequestBody2": {
        "required": true,
        "description": "application/json only, <=64 KiB. SPDL <=4096 UTF-8 bytes and names <=128 Unicode code points are additionally checked at runtime.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ServiceWrite"
            }
          }
        }
      },
      "RequestBody3": {
        "required": true,
        "description": "application/json only, <=64 KiB. SPDL <=4096 UTF-8 bytes and names <=128 Unicode code points are additionally checked at runtime.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ServiceEmptyRequest"
            }
          }
        }
      },
      "RequestBody4": {
        "required": true,
        "description": "application/json only, <=64 KiB. SPDL <=4096 UTF-8 bytes and names <=128 Unicode code points are additionally checked at runtime.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/PolicyWrite"
            }
          }
        }
      },
      "RequestBody5": {
        "required": true,
        "description": "application/json only, <=64 KiB. SPDL <=4096 UTF-8 bytes and names <=128 Unicode code points are additionally checked at runtime.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ServiceTestRequest"
            }
          }
        }
      },
      "RequestBody6": {
        "required": true,
        "description": "application/json only, at most 64 KiB; DELETE explicitly sends {}. Reject unknown fields.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/TokenWrite"
            }
          }
        }
      },
      "RequestBody7": {
        "required": true,
        "description": "application/json only, at most 64 KiB; DELETE explicitly sends {}. Reject unknown fields.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/TokenEmptyRequest"
            }
          }
        }
      },
      "RequestBody8": {
        "required": true,
        "description": "application/json Speedle JsonContext, at most 64 KiB before parsing. Attributes are an array of {name, type, value} with Speedle types string, numeric, bool and datetime (or an omitted type for an unconverted value), each a single value or an array of values of that type; Cloud bounds them to 50 items and 16,384 canonical UTF-8 JSON bytes. The Go decoder, not this schema, decides validity: unknown fields are ignored and field names match case-insensitively, as in Speedle.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AdsRequest"
            }
          }
        }
      },
      "RequestBody9": {
        "required": true,
        "description": "application/json only; {policies:[{kind,name,spdl}]} projected locally from the unchanged JSON export file, 1–200 policies. Dedicated maximum 5100000 bytes includes worst-case JSON escaping and names; never transmit exported ASTs or service/row metadata. This does not change the 4096 UTF-8 source bytes / 128 Unicode-code-point name bounds or the 65536-byte limit on other endpoints.",
        "x-max-body-bytes": 5100000,
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ServiceImportRequest"
            }
          }
        }
      }
    }
  }
}
