{
  "openapi": "3.1.0",
  "info": {
    "title": "RightOS Right API",
    "version": "0.9.0",
    "summary": "Privacy-preserving rights verification API — digital QR tickets and authority gates for queues, multi-agent systems, EV, and pickup.",
    "description": "RightOS is a verification infrastructure for places, time slots, queue positions, usage rights, and other durable rights.\n\n**Core principle: Assignment is not authority.** RightOS answers MAY (rights / policy / proof). Pair with **RightFlow** (https://rightos.i-s3.com/rightflow-openapi.json) for NEXT (tasks, assignment, reassignment). Your execution system answers HOW. Accepting a RightFlow proposal does not create a RightOS right — re-check rights at execution time when policy requires.\n\nFor AI agents: `@i-s3/rightos-mcp` (MAY) and `@i-s3/rightflow-mcp` (NEXT) are separate on purpose. Use MCP for tools and A2A (or similar) for agent-to-agent messaging as complements — do not merge layers into one central agent.\n\nCore ticket concept: a **Right Token** is a digital right identified by a `tokenId` and proven by a `verificationCode` handed to the holder exactly once at issuance. The server stores only the SHA-256 hash (`secretHash`). Verifiers confirm that *a valid right is present*, not *who the person is*.\n\nTransfers use re-keying: a new code is issued and the old one is invalidated immediately.\n\n**What RightOS is NOT**: not a taxi or ride-hailing service; does not arrange vehicles, set fares, assign drivers, or broker dispatch.\n\n日本語: RightOSは場所・時間・順番・利用権のプライバシー保護型検証基盤です。「割当は権限ではない」。タクシー営業・配車サービスではありません。",
    "termsOfService": "https://rightos.i-s3.com/software/rightos/legal/terms",
    "contact": {
      "name": "I-S3 Software Division",
      "url": "https://rightos.i-s3.com/software/rightos"
    }
  },
  "servers": [
    {
      "url": "https://rightos.i-s3.com",
      "description": "Production"
    }
  ],
  "security": [],
  "tags": [
    {
      "name": "tokens",
      "description": "Issue, verify, use, cancel, and transfer Right Tokens"
    },
    {
      "name": "locations",
      "description": "Locations (shops, EV chargers, pickup points) belonging to an organization"
    },
    {
      "name": "organizations",
      "description": "Organization registration and account operations"
    },
    {
      "name": "plans",
      "description": "Pricing plans (globally uniform pricing)"
    },
    {
      "name": "billing",
      "description": "Subscription billing via Stripe hosted Checkout / Billing Portal (card data never touches RightOS servers)"
    },
    {
      "name": "console",
      "description": "Dashboard data for the Right Console"
    },
    {
      "name": "webhooks",
      "description": "Outbound webhooks: signed event notifications to your system"
    },
    {
      "name": "admin",
      "description": "Operator admin endpoints (Google admin session cookie or cron key). Not for end-customer API keys."
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerApiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key issued exactly once at organization registration (format: rk_live_...). The server stores only its SHA-256 hash. Alternative header: x-rightos-key."
      },
      "headerApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-rightos-key",
        "description": "Same key as bearerApiKey, sent via a dedicated header."
      },
      "adminSessionOrCron": {
        "type": "apiKey",
        "in": "cookie",
        "name": "rightos-admin-session",
        "description": "Admin Google session cookie, or alternatively the cron key headers used by internal ops (x-rightos-cron-key). Organization API keys are rejected."
      }
    },
    "schemas": {
      "PlanId": {
        "type": "string",
        "enum": [
          "free",
          "starter",
          "business",
          "pro",
          "enterprise"
        ]
      },
      "Plan": {
        "type": "object",
        "properties": {
          "id": {
            "$ref": "#/components/schemas/PlanId"
          },
          "name": {
            "type": "string"
          },
          "nameEn": {
            "type": "string"
          },
          "monthlyPrice": {
            "type": [
              "number",
              "null"
            ],
            "description": "Monthly price in JPY. null = custom quote. Pricing is globally uniform."
          },
          "tokenLimit": {
            "type": [
              "number",
              "null"
            ],
            "description": "Monthly Right Token issuance limit. null = unlimited/custom. free=500, starter=3000, business=20000, pro=200000 (+10% soft grace then hard stop)."
          },
          "locationLimit": {
            "type": [
              "number",
              "null"
            ],
            "description": "Location limit. free=1, starter=3, business=20, pro/enterprise=unlimited."
          },
          "apiEnabled": {
            "type": "boolean"
          },
          "features": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "featuresEn": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Organization": {
        "type": "object",
        "description": "Public organization record. The API key hash is never exposed.",
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "org_2f8a1c9d3e4b"
            ]
          },
          "name": {
            "type": "string"
          },
          "country": {
            "type": "string",
            "description": "ISO country code, default JP"
          },
          "contactEmail": {
            "type": "string",
            "format": "email"
          },
          "planId": {
            "$ref": "#/components/schemas/PlanId"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Subscription": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "organizationId": {
            "type": "string"
          },
          "planId": {
            "$ref": "#/components/schemas/PlanId"
          },
          "status": {
            "type": "string",
            "enum": [
              "trialing",
              "active",
              "past_due",
              "cancelled"
            ]
          },
          "startedAt": {
            "type": "string",
            "format": "date-time"
          },
          "currentPeriodEnd": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "LocationType": {
        "type": "string",
        "enum": [
          "shop",
          "clinic",
          "ev_charging",
          "event",
          "logistics",
          "pickup",
          "other"
        ]
      },
      "RightPolicy": {
        "type": "object",
        "description": "Effective policy for a location (ADR-0010/0014, Policy Engine Phase 2). Resolution order: industry preset for the location's type -> country overlay (organization country x location type; conservative operational defaults for JP/US/GB/KR/TW/FR/DE/IT/ES/AU, not legal advice; see GET /api/rightos/policies) -> per-location override. Presets: shop (transferable, max 3, 12h, self-cancel), clinic (non-transferable, 12h, self-cancel), ev_charging (non-transferable, 4h, self-cancel), event (transferable once, 24h, no self-cancel), logistics (transferable, max 3, 24h, no self-cancel), pickup (transferable once, 6h, no self-cancel), other (transferable, max 3, 12h, self-cancel).",
        "properties": {
          "transferable": {
            "type": "boolean",
            "description": "Whether the right can be transferred (re-keying)"
          },
          "maxTransfers": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Maximum number of transfers. null = unlimited"
          },
          "defaultValidityMinutes": {
            "type": "integer",
            "description": "Default validity applied when a token is issued without endTime"
          },
          "verificationRequirement": {
            "type": "string",
            "enum": [
              "none",
              "external_id"
            ],
            "description": "external_id declares that identity checks are delegated to external partners/ID systems — RightOS itself never collects personal data"
          },
          "holderCancellable": {
            "type": "boolean",
            "description": "Whether the current holder (who knows the verificationCode) can self-cancel the token. Operator cancellation via the authenticated cancel endpoint is always available"
          },
          "maxActiveTokens": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Max concurrent active queue tokens (issued + verified, excluding expired). null = unlimited. When full, issue returns 409 queue_full (ADR-0024)"
          },
          "enforceTimeWindow": {
            "type": "boolean",
            "description": "When true, verify before startTime returns not_yet_valid (ADR-0024)"
          },
          "requireVerifyBeforeUse": {
            "type": "boolean",
            "description": "When true, use from issued status returns 409 verify_required (ADR-0024)"
          }
        }
      },
      "LocationQueueSnapshot": {
        "type": "object",
        "description": "Public queue state for a location (no PII). Reference only — RightOS does not auto-call or assign staff (ADR-0024).",
        "properties": {
          "locationId": { "type": "string" },
          "locationName": { "type": "string", "description": "Public store/location name (not PII); used by the display board" },
          "locationActive": { "type": "boolean" },
          "activeCount": { "type": "integer", "description": "issued + verified (not expired)" },
          "waitingCount": { "type": "integer", "description": "issued only" },
          "verifiedCount": { "type": "integer", "description": "verified only" },
          "issueSequenceToday": { "type": "integer", "description": "Max priorityNumber issued today (issue sequence, not queue position)" },
          "headQueuePosition": { "type": ["integer", "null"], "description": "Front queue position among waiting (issued) tokens, if any" },
          "estimatedWaitMinutes": { "type": ["integer", "null"], "description": "Estimated wait from recent used tokens; null if insufficient samples" },
          "aheadCount": { "type": "integer", "description": "Present when tokenId query param is set" },
          "queuePosition": { "type": "integer", "description": "Present when tokenId query param is set" }
        }
      },
      "PolicyChange": {
        "type": "object",
        "description": "Policy change audit record (ADR-0014). Contains no personal data. No delete/update API is provided.",
        "properties": {
          "id": {
            "type": "string"
          },
          "locationId": {
            "type": "string"
          },
          "organizationId": {
            "type": "string"
          },
          "before": {
            "type": [
              "object",
              "null"
            ],
            "description": "Override in effect before the change (null = preset/overlay only)"
          },
          "after": {
            "type": [
              "object",
              "null"
            ],
            "description": "Override in effect after the change (null = reset to preset)"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Location": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "loc_shop"
            ]
          },
          "organizationId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "address": {
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/LocationType"
          },
          "timezone": {
            "type": "string",
            "examples": [
              "Asia/Tokyo"
            ]
          },
          "active": {
            "type": "boolean"
          }
        }
      },
      "RightTokenStatus": {
        "type": "string",
        "enum": [
          "issued",
          "verified",
          "used",
          "cancelled",
          "expired"
        ]
      },
      "RightToken": {
        "type": "object",
        "description": "Public Right Token. The secretHash is never exposed by any endpoint.",
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "tok_2f8a1c9d3e4b5a6f7081"
            ]
          },
          "organizationId": {
            "type": "string"
          },
          "locationId": {
            "type": "string"
          },
          "title": {
            "type": "string",
            "examples": [
              "順番待ち / Queue ticket"
            ]
          },
          "description": {
            "type": "string"
          },
          "startTime": {
            "type": "string",
            "format": "date-time"
          },
          "endTime": {
            "type": "string",
            "format": "date-time"
          },
          "priorityNumber": {
            "type": "integer",
            "description": "Daily issue sequence at this location (includes cancelled/used/expired). Not the live queue position — see queuePosition"
          },
          "queuePosition": {
            "type": "integer",
            "description": "Position in the active queue at issuance (issued + verified, not expired). ADR-0024"
          },
          "status": {
            "$ref": "#/components/schemas/RightTokenStatus"
          },
          "transferCount": {
            "type": "integer",
            "description": "Number of times transferred. 0 = original holder."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "usedAt": {
            "type": "string",
            "format": "date-time"
          },
          "cancelledAt": {
            "type": "string",
            "format": "date-time"
          },
          "transferredAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "VerificationResult": {
        "type": "string",
        "enum": [
          "success",
          "failed",
          "expired",
          "cancelled",
          "already_used",
          "not_yet_valid"
        ]
      },
      "IssuedTokenResponse": {
        "type": "object",
        "properties": {
          "token": {
            "$ref": "#/components/schemas/RightToken"
          },
          "verificationCode": {
            "type": "string",
            "description": "Returned exactly once. The server stores only its SHA-256 hash and cannot re-issue it.",
            "examples": [
              "K7MP-Q2XT9"
            ]
          },
          "walletUrl": {
            "type": "string",
            "description": "Right Wallet URL (QR display) to hand to the end user.",
            "examples": [
              "/software/rightos/wallet/tok_2f8a.../?code=K7MP-Q2XT9"
            ]
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "examples": [
              "missing_api_key",
              "invalid_api_key",
              "rate_limited",
              "confirmation_required",
              "not_found",
              "demo_org"
            ]
          },
          "message": {
            "type": "string"
          }
        }
      },
      "WebhookEventType": {
        "type": "string",
        "enum": [
          "token.verified",
          "token.used",
          "token.cancelled",
          "token.transferred"
        ]
      },
      "WebhookDeliveryResult": {
        "type": "object",
        "description": "Most recent webhook delivery attempt.",
        "properties": {
          "at": { "type": "string", "format": "date-time" },
          "event": { "$ref": "#/components/schemas/WebhookEventType" },
          "ok": { "type": "boolean" },
          "status": { "type": "integer", "description": "HTTP status (absent on connection failure or timeout)" },
          "error": { "type": "string", "description": "Short error description (no secrets)" }
        },
        "required": ["at", "event", "ok"]
      },
      "Webhook": {
        "type": "object",
        "description": "Outbound webhook. The signing secret (whsec_...) is NOT stored server-side; it is derived from a master key and returned exactly once at registration.",
        "properties": {
          "id": {
            "type": "string"
          },
          "organizationId": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastDelivery": {
            "$ref": "#/components/schemas/WebhookDeliveryResult"
          },
          "deliveredCount": {
            "type": "integer",
            "minimum": 0
          },
          "failedCount": {
            "type": "integer",
            "minimum": 0
          }
        },
        "required": ["id", "organizationId", "url", "events", "createdAt"]
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid API key (error: missing_api_key | invalid_api_key)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PlanLimit": {
        "description": "Plan limit exceeded (HTTP 402). Upgrade the plan to continue.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded (HTTP 429). See Retry-After header. Limits: verify/transfer = 10/min per IP+token and 60/min per IP; signup = 5/hour per IP.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds until the window resets"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  },
  "paths": {
    "/api/rightos/billing/config": {
      "get": {
        "tags": [
          "billing"
        ],
        "operationId": "getBillingConfig",
        "summary": "Whether billing (Stripe) is enabled (no auth)",
        "responses": {
          "200": {
            "description": "Billing availability flag, used by UIs to show or hide upgrade actions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "enabled": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/rightos/billing/checkout": {
      "post": {
        "tags": [
          "billing"
        ],
        "operationId": "createCheckoutSession",
        "summary": "Create a Stripe Checkout session for a paid plan (hosted page; card data never touches RightOS servers)",
        "security": [
          {
            "bearerApiKey": []
          },
          {
            "headerApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "planId"
                ],
                "properties": {
                  "planId": {
                    "type": "string",
                    "enum": [
                      "starter",
                      "business",
                      "pro"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Checkout URL to redirect the user to",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_plan"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "demo_org — the demo organization cannot change plans"
          },
          "503": {
            "description": "billing_not_configured — billing is disabled in this environment (MVP mode)"
          }
        }
      }
    },
    "/api/rightos/billing/portal": {
      "post": {
        "tags": [
          "billing"
        ],
        "operationId": "createBillingPortalSession",
        "summary": "Create a Stripe Billing Portal session (plan changes, payment methods, cancellation)",
        "security": [
          {
            "bearerApiKey": []
          },
          {
            "headerApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Portal URL to redirect the user to",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "demo_org"
          },
          "404": {
            "description": "no_billing_account — the organization has no Stripe customer yet"
          },
          "503": {
            "description": "billing_not_configured"
          }
        }
      }
    },
    "/api/rightos/billing/webhook": {
      "post": {
        "tags": [
          "billing"
        ],
        "operationId": "stripeWebhook",
        "summary": "Stripe webhook endpoint (signature-verified; not for direct use)",
        "description": "Verifies the stripe-signature header against the webhook secret. Handles checkout.session.completed, customer.subscription.updated, and customer.subscription.deleted (downgrade to free; data is never deleted).",
        "responses": {
          "200": {
            "description": "Event received"
          },
          "400": {
            "description": "missing_signature / invalid_signature"
          },
          "503": {
            "description": "billing_not_configured"
          }
        }
      }
    },
    "/api/rightos/plans": {
      "get": {
        "tags": [
          "plans"
        ],
        "operationId": "listPlans",
        "summary": "List pricing plans (no auth)",
        "responses": {
          "200": {
            "description": "All plans. Pricing is globally uniform (same price worldwide).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "plans": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Plan"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/rightos/organizations": {
      "post": {
        "tags": [
          "organizations"
        ],
        "operationId": "registerOrganization",
        "summary": "Register an organization (returns the API key exactly once)",
        "description": "Creates an organization with a trialing subscription (no credit card during MVP). The response contains the API key (rk_live_...) exactly once — store it securely; the server keeps only its hash. Rate limit: 5/hour per IP.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "contactEmail",
                  "planId"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "contactEmail": {
                    "type": "string",
                    "format": "email"
                  },
                  "planId": {
                    "$ref": "#/components/schemas/PlanId"
                  },
                  "country": {
                    "type": "string",
                    "default": "JP"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Organization created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "organization": {
                      "$ref": "#/components/schemas/Organization"
                    },
                    "subscription": {
                      "$ref": "#/components/schemas/Subscription"
                    },
                    "apiKey": {
                      "type": "string",
                      "description": "Shown exactly once."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing/invalid fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/rightos/organizations/rotate-key": {
      "post": {
        "tags": [
          "organizations"
        ],
        "operationId": "rotateApiKey",
        "summary": "Re-issue the API key (old key invalidated immediately)",
        "description": "For suspected leaks or periodic rotation. Requires the current key. The new key is returned exactly once. Lost keys (cannot authenticate) can be recovered self-service via /api/rightos/organizations/recover (email link).",
        "security": [
          {
            "bearerApiKey": []
          },
          {
            "headerApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "New key",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "apiKey": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Demo organization keys cannot be rotated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/rightos/organizations/recover": {
      "post": {
        "tags": [
          "organizations"
        ],
        "operationId": "requestApiKeyRecovery",
        "summary": "Request an API key recovery link by registered email (no auth)",
        "description": "Sends a single-use re-issue link (valid 30 minutes) to the registered contact email. Always returns the same 200 response regardless of whether the email is registered, to avoid account enumeration. Rate limited per IP and per email. The API key itself is never sent by email.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["contactEmail"],
                "properties": {
                  "contactEmail": { "type": "string", "format": "email" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Accepted (regardless of registration status)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "ok": { "type": "boolean" } }
                }
              }
            }
          },
          "400": { "description": "Invalid email" },
          "429": { "description": "Rate limited" },
          "503": { "description": "Recovery unavailable (email not configured)" }
        }
      }
    },
    "/api/rightos/organizations/recover/confirm": {
      "post": {
        "tags": [
          "organizations"
        ],
        "operationId": "confirmApiKeyRecovery",
        "summary": "Exchange a recovery token for a new API key (single use)",
        "description": "Verifies the HMAC-signed recovery token from the email link and rotates the key. The new key is returned exactly once; the old key and any other outstanding recovery links are invalidated immediately.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["token"],
                "properties": {
                  "token": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "New key (shown once)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": { "type": "boolean" },
                    "apiKey": { "type": "string" }
                  }
                }
              }
            }
          },
          "400": { "description": "Missing token" },
          "401": { "description": "Invalid or expired token" },
          "403": { "description": "Demo organization keys cannot be recovered" },
          "429": { "description": "Rate limited" },
          "503": { "description": "Recovery unavailable (email not configured)" }
        }
      }
    },
    "/api/rightos/organizations/delete": {
      "post": {
        "tags": [
          "organizations"
        ],
        "operationId": "deleteOrganization",
        "summary": "Permanently delete the organization and all its data (irreversible)",
        "description": "Deletes the organization, its locations, tokens, and verification logs. body.confirm must exactly match the organization name. Export your data first via GET /api/rightos/export if needed.",
        "security": [
          {
            "bearerApiKey": []
          },
          {
            "headerApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "confirm"
                ],
                "properties": {
                  "confirm": {
                    "type": "string",
                    "description": "Exact organization name"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "confirmation_required (name mismatch — nothing deleted)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Demo organization cannot be deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/rightos/export": {
      "get": {
        "tags": [
          "organizations"
        ],
        "operationId": "exportOrganizationData",
        "summary": "Export all organization data as JSON (no lock-in)",
        "description": "Returns organization, subscriptions, locations, tokens, and verification logs. Contains no secret values (no key hashes, no code hashes).",
        "security": [
          {
            "bearerApiKey": []
          },
          {
            "headerApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Full export (Content-Disposition: attachment)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "exportedAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "organization": {
                      "$ref": "#/components/schemas/Organization"
                    },
                    "subscriptions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Subscription"
                      }
                    },
                    "locations": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Location"
                      }
                    },
                    "tokens": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/RightToken"
                      }
                    },
                    "logs": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/rightos/locations": {
      "get": {
        "tags": [
          "locations"
        ],
        "operationId": "listLocations",
        "summary": "List your organization's locations",
        "security": [
          {
            "bearerApiKey": []
          },
          {
            "headerApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Locations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "locations": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Location"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "locations"
        ],
        "operationId": "createLocation",
        "summary": "Create a location (402 when plan limit exceeded)",
        "security": [
          {
            "bearerApiKey": []
          },
          {
            "headerApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "address": {
                    "type": "string"
                  },
                  "type": {
                    "$ref": "#/components/schemas/LocationType"
                  },
                  "timezone": {
                    "type": "string",
                    "default": "Asia/Tokyo"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "location": {
                      "$ref": "#/components/schemas/Location"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          }
        }
      }
    },
    "/api/rightos/locations/{locationId}/queue": {
      "get": {
        "tags": ["locations"],
        "operationId": "getLocationQueue",
        "summary": "Public queue snapshot for a location (no PII)",
        "description": "Returns active/waiting counts and optional aheadCount for a tokenId query param. Reference only — RightOS does not auto-call or assign staff.",
        "parameters": [
          {
            "name": "locationId",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          },
          {
            "name": "tokenId",
            "in": "query",
            "required": false,
            "schema": { "type": "string" },
            "description": "If set, includes aheadCount and queuePosition for this token"
          }
        ],
        "responses": {
          "200": {
            "description": "Queue snapshot",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/LocationQueueSnapshot" }
              }
            }
          },
          "404": { "description": "Location not found" }
        }
      }
    },
    "/api/rightos/locations/{locationId}/policy": {
      "get": {
        "tags": [
          "locations"
        ],
        "operationId": "getLocationPolicy",
        "summary": "Get the location's effective policy (public, for transparency)",
        "parameters": [
          {
            "name": "locationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Effective policy (override merged over industry preset)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "locationId": {
                      "type": "string"
                    },
                    "locationType": {
                      "$ref": "#/components/schemas/LocationType"
                    },
                    "policy": {
                      "$ref": "#/components/schemas/RightPolicy"
                    },
                    "hasOverride": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Location not found"
          }
        }
      },
      "put": {
        "tags": [
          "locations"
        ],
        "operationId": "setLocationPolicy",
        "summary": "Override the location policy (own organization only; partial update; null resets to preset)",
        "security": [
          {
            "bearerApiKey": []
          },
          {
            "headerApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "locationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "transferable": {
                        "type": "boolean"
                      },
                      "maxTransfers": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "minimum": 0,
                        "maximum": 1000
                      },
                      "defaultValidityMinutes": {
                        "type": "integer",
                        "minimum": 5,
                        "maximum": 43200
                      },
                      "verificationRequirement": {
                        "type": "string",
                        "enum": [
                          "none",
                          "external_id"
                        ]
                      },
                      "holderCancellable": {
                        "type": "boolean"
                      }
                    }
                  }
                ],
                "description": "Partial policy override, or null to reset to the industry preset"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated effective policy",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "locationId": {
                      "type": "string"
                    },
                    "locationType": {
                      "$ref": "#/components/schemas/LocationType"
                    },
                    "policy": {
                      "$ref": "#/components/schemas/RightPolicy"
                    },
                    "hasOverride": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid policy field or value"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Location belongs to another organization"
          },
          "404": {
            "description": "Location not found"
          }
        }
      }
    },
    "/api/rightos/status": {
      "get": {
        "tags": [
          "meta"
        ],
        "operationId": "getStatus",
        "summary": "Service status: self-checks and measured uptime (public)",
        "description": "Returns overall status (operational/degraded), component self-checks (API, data store latency, billing/email configuration), and measured uptime fractions (24h/7d/30d) from external multi-region uptime checks via Google Cloud Monitoring. The uptime field is null when monitoring data is unavailable. Human-readable status page: https://rightos.i-s3.com/software/rightos/status",
        "responses": {
          "200": {
            "description": "Current status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "operational",
                        "degraded"
                      ]
                    },
                    "time": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "revision": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "checks": {
                      "type": "object"
                    },
                    "uptime": {
                      "type": [
                        "array",
                        "null"
                      ],
                      "items": {
                        "type": "object",
                        "properties": {
                          "label": {
                            "type": "string",
                            "enum": [
                              "24h",
                              "7d",
                              "30d"
                            ]
                          },
                          "fraction": {
                            "type": "number"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/rightos/policies": {
      "get": {
        "tags": [
          "locations"
        ],
        "operationId": "listPolicyDefinitions",
        "summary": "All industry presets and country overlays (public, for transparency)",
        "description": "Returns the full policy knowledge base: industry presets per location type and country overlays (organization country x location type). Resolution order for a location's effective policy: preset -> country overlay -> location override. Overlays are conservative operational defaults informed by local laws (e.g. Japan's Ticket Resale Prohibition Act, South Korea's 2026 Performance Act amendment, France's Penal Code art. 313-6-2) — not legal advice. Currently covers JP, US, GB, KR, TW, FR, DE, IT, ES, AU.",
        "responses": {
          "200": {
            "description": "Policy definitions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "resolutionOrder": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "presets": {
                      "type": "object",
                      "additionalProperties": {
                        "$ref": "#/components/schemas/RightPolicy"
                      }
                    },
                    "countryOverlays": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "object"
                      }
                    },
                    "note": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/rightos/locations/{locationId}/policy/history": {
      "get": {
        "tags": [
          "locations"
        ],
        "operationId": "getLocationPolicyHistory",
        "summary": "Policy change audit log for the location (own organization only)",
        "description": "Returns policy override changes (before/after, timestamp) in reverse chronological order. Provides accountability for which rules were in effect at verification time (ADR-0014). Records are append-only; no delete/update API exists.",
        "security": [
          {
            "bearerApiKey": []
          },
          {
            "headerApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "locationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Audit records, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "locationId": {
                      "type": "string"
                    },
                    "changes": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PolicyChange"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Location belongs to another organization"
          },
          "404": {
            "description": "Location not found"
          }
        }
      }
    },
    "/api/rightos/tokens/{tokenId}/holder-cancel": {
      "post": {
        "tags": [
          "tokens"
        ],
        "operationId": "holderCancelToken",
        "summary": "Self-cancel by the current holder (requires the verificationCode; no auth header)",
        "description": "The current holder proves possession with the verificationCode and cancels the token. Rejected with 409 policy_cancel_disabled when the location's policy sets holderCancellable=false. Operator cancellation via the authenticated cancel endpoint is always available regardless of this policy. Rate limited like verify (10/min per token per IP).",
        "parameters": [
          {
            "name": "tokenId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "verificationCode"
                ],
                "properties": {
                  "verificationCode": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Cancelled token",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "token": {
                      "$ref": "#/components/schemas/RightToken"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "verificationCode is required"
          },
          "403": {
            "description": "unauthorized — wrong verificationCode"
          },
          "404": {
            "description": "Token not found"
          },
          "409": {
            "description": "not_cancellable (already used/cancelled/expired) or policy_cancel_disabled"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/rightos/tokens": {
      "get": {
        "tags": ["tokens"],
        "operationId": "listTokens",
        "summary": "List tokens for your organization",
        "description": "Filter by locationId, status, and since (createdAt >= ISO 8601). Never includes secretHash.",
        "security": [
          { "bearerApiKey": [] },
          { "headerApiKey": [] }
        ],
        "parameters": [
          {
            "name": "locationId",
            "in": "query",
            "schema": { "type": "string" }
          },
          {
            "name": "status",
            "in": "query",
            "schema": { "$ref": "#/components/schemas/RightTokenStatus" }
          },
          {
            "name": "since",
            "in": "query",
            "schema": { "type": "string", "format": "date-time" }
          }
        ],
        "responses": {
          "200": {
            "description": "Token list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tokens": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/RightToken" }
                    }
                  }
                }
              }
            }
          },
          "400": { "description": "invalid_status" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/rightos/tokens/issue": {
      "post": {
        "tags": [
          "tokens"
        ],
        "operationId": "issueToken",
        "summary": "Issue a Right Token (the verificationCode is returned exactly once)",
        "description": "Issues a digital QR ticket at one of your locations. The response's verificationCode and walletUrl are shown exactly once — hand the walletUrl (QR) to the end user. Omit endTime to apply the location policy's defaultValidityMinutes; after endTime, verification returns expired. 402 when the plan's monthly token limit is exceeded.",
        "security": [
          {
            "bearerApiKey": []
          },
          {
            "headerApiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "locationId",
                  "title"
                ],
                "properties": {
                  "locationId": {
                    "type": "string"
                  },
                  "title": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "startTime": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "endTime": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Issued",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IssuedTokenResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "403": {
            "description": "Location belongs to another organization, or location_inactive"
          },
          "409": {
            "description": "queue_full — maxActiveTokens policy limit reached"
          }
        }
      }
    },
    "/api/rightos/tokens/{tokenId}": {
      "get": {
        "tags": [
          "tokens"
        ],
        "operationId": "getToken",
        "summary": "Get Right Token details (never includes secretHash; no auth)",
        "parameters": [
          {
            "name": "tokenId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Token",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "token": {
                      "$ref": "#/components/schemas/RightToken"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/rightos/tokens/{tokenId}/verify": {
      "post": {
        "tags": [
          "tokens"
        ],
        "operationId": "verifyToken",
        "summary": "Verify a Right Token by hash-matching its verificationCode (no auth)",
        "description": "Hash-compares the presented verificationCode. The result is recorded in the verification log. Rate limited against brute force: 10/min per IP+token, 60/min per IP.",
        "parameters": [
          {
            "name": "tokenId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "verificationCode"
                ],
                "properties": {
                  "verificationCode": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verification outcome",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "$ref": "#/components/schemas/VerificationResult"
                    },
                    "token": {
                      "$ref": "#/components/schemas/RightToken"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/rightos/tokens/{tokenId}/use": {
      "post": {
        "tags": [
          "tokens"
        ],
        "operationId": "useToken",
        "summary": "Mark a Right Token as used (own organization only)",
        "security": [
          {
            "bearerApiKey": []
          },
          {
            "headerApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "tokenId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Updated token"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token belongs to another organization"
          }
        }
      }
    },
    "/api/rightos/tokens/{tokenId}/cancel": {
      "post": {
        "tags": [
          "tokens"
        ],
        "operationId": "cancelToken",
        "summary": "Cancel a Right Token (own organization only)",
        "security": [
          {
            "bearerApiKey": []
          },
          {
            "headerApiKey": []
          }
        ],
        "parameters": [
          {
            "name": "tokenId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Updated token"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Token belongs to another organization"
          }
        }
      }
    },
    "/api/rightos/tokens/{tokenId}/transfer": {
      "post": {
        "tags": [
          "tokens"
        ],
        "operationId": "transferToken",
        "summary": "Transfer a right (re-keying: new code issued, old code invalidated; no auth)",
        "description": "Only the current holder (who knows the current verificationCode) can transfer. A new code is issued and the old one is invalidated immediately. Paid transfers/auctions are not offered. Rate limited like verify. Subject to the location's policy: returns 409 with error policy_transfer_disabled when the location forbids transfers, or transfer_limit_reached when maxTransfers is exhausted.",
        "parameters": [
          {
            "name": "tokenId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "verificationCode"
                ],
                "properties": {
                  "verificationCode": {
                    "type": "string",
                    "description": "Current code, proving transfer authority"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "New code and wallet URL for the new holder (returned exactly once)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IssuedTokenResponse"
                }
              }
            }
          },
          "409": {
            "description": "Wrong code or token not transferable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/rightos/console": {
      "get": {
        "tags": [
          "console"
        ],
        "operationId": "getConsoleData",
        "summary": "Dashboard data for your organization (stats, locations, tokens)",
        "security": [
          {
            "bearerApiKey": []
          },
          {
            "headerApiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Dashboard data scoped to your organization"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/rightos/webhooks": {
      "get": {
        "tags": [
          "webhooks"
        ],
        "operationId": "listWebhooks",
        "summary": "List your organization's webhooks (auth required; never includes signing secrets)",
        "security": [
          {
            "apiKey": []
          },
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Webhooks",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "webhooks": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Webhook"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "webhooks"
        ],
        "operationId": "createWebhook",
        "summary": "Register a webhook (auth required; up to 3 per organization; https only)",
        "description": "Events token.verified / token.used / token.cancelled / token.transferred are delivered as signed HTTPS POSTs (header x-rightos-signature: t=<unix seconds>,v1=<hex HMAC-SHA256(secret, `${t}.${rawBody}`)>). The signing secret (whsec_...) is returned only once in this response and is never stored server-side. Delivery is best-effort: 3s timeout, no retries — respond 2xx immediately and process asynchronously. Private/internal hosts are rejected (SSRF protection).",
        "security": [
          {
            "apiKey": []
          },
          {
            "bearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "https URL (http allowed for localhost only)"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/WebhookEventType"
                    },
                    "description": "Defaults to all four events"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created. The secret is returned only once.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "webhook": {
                      "$ref": "#/components/schemas/Webhook"
                    },
                    "secret": {
                      "type": "string",
                      "description": "Signing secret (whsec_...). Shown only once; not retrievable later."
                    },
                    "signatureHeader": {
                      "type": "string"
                    },
                    "signatureFormat": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid URL (non-https or internal host) or invalid events",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "webhook_limit_reached (3 per organization)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "demo_org (demo organization cannot register webhooks)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/rightos/webhooks/{webhookId}": {
      "delete": {
        "tags": [
          "webhooks"
        ],
        "operationId": "deleteWebhook",
        "summary": "Delete a webhook (auth required; own organization only)",
        "security": [
          {
            "apiKey": []
          },
          {
            "bearer": []
          }
        ],
        "parameters": [
          {
            "name": "webhookId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/rightos/admin/break-glass": {
      "post": {
        "tags": ["admin"],
        "operationId": "adminBreakGlass",
        "summary": "Audited emergency task mass-action (break-glass)",
        "description": "Admin-only L3 control. Cancels or fails open RightFlow tasks for an organization and may optionally cancel open RightOS tokens. Immediate physical safety remains the execution system's responsibility; this endpoint reconciles coordination/rights state afterward (ADR-0028).",
        "security": [{ "adminSessionOrCron": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["organizationId", "reason"],
                "properties": {
                  "organizationId": { "type": "string" },
                  "reason": { "type": "string", "minLength": 1 },
                  "taskAction": {
                    "type": "string",
                    "enum": ["cancel", "fail"],
                    "default": "cancel"
                  },
                  "cancelOpenRights": {
                    "type": "boolean",
                    "default": false,
                    "description": "If true, also cancel issued/verified RightOS tokens for the organization"
                  },
                  "source": {
                    "type": "string",
                    "description": "Optional audit source label"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Break-glass applied",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized (no admin session / cron key)",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    }
  }
}
