{"openapi":"3.1.0","info":{"title":"ExpiryEdge API","version":"1.0.0","description":"The ExpiryEdge HTTP API used by the web app and customer scripts.\nEvery operation is `/<endpoint>` on a single Cloud Function; most endpoints are RPC-style\n(the endpoint name is the action). See [the API guide](/developers/api/developer-guide/) for walkthroughs.\n\n## Authentication\nSend `Authorization: Bearer <token>` where the token is either:\n- a **Firebase ID token** from the Firebase Auth SDK (expires after 1 hour - refresh it with the SDK), or\n- a **session JWT** returned by `POST /login` (`{email, password}` -> `{accessToken, user, teams}`), HS256, valid for 30 days.\n\nEach handler authenticates itself; operations marked `security: []` are public (some take a share,\nportal or request token instead). Roles are `admin`, `editor` and `viewer` (`member` is an alias of\neditor, `user` of viewer). Each operation's description states the required role.\n\n## Errors\nNewer handlers return `{error, code, requestId}` where `code` is a stable identifier from the catalog\nin the `Error` schema. Older handlers return only `{error}` (sometimes with extra fields such as\n`details`), and `/login` returns `{message}`. Always branch on the HTTP status first, then `code` when present.\nAuthentication failures are `401`, missing permissions `403`, records outside your organization `404`.\n\n## Rate limits\n\nLimits are applied per client IP, per endpoint, per server instance (so they are approximate):\n\n- Default: 60 requests / minute.\n- Strict: 10 requests / 15 minutes - login, register, sendPasswordResetEmail, verifyCodeAndUpdatePassword, resendVerificationEmail, checkPromoCode, sendSampleReminderEmail, submitSignupRequest.\n- Sensitive: 30 requests / 15 minutes - completeInvitation, verifyEmail, invite, resendInvitation, sendExpiryNotificationNow.\n- Extraction (AI): 30 requests / minute - extractDocument, extractDocumentDirect, bulkExtractDocuments, mapImportColumns.\n- High: 500 requests / minute - getAllExpiries, getExpiryMetrics, getPaginatedExpiries.\n\nEvery response carries X-RateLimit-Remaining; a 429 carries Retry-After (seconds). Each operation's x-rate-limit extension gives its limit.\n\n## Idempotency\nCreate and send endpoints accept an optional `Idempotency-Key` header (1-255 characters of letters,\ndigits, `- _ : .`). A retry with the same key and body within 24 hours replays the first 2xx response\nwith `Idempotent-Replayed: true`. Same key with a different body -> `422 IDEMPOTENCY_KEY_REUSED`;\nsame key while the first request is still running -> `409 IDEMPOTENCY_IN_PROGRESS`. Keys are scoped\nper user and endpoint. Only POST requests are covered.\n\n## Dates and times\nAll dates are ISO 8601 strings. Date-only fields such as `expiry_date` and `start_date` are calendar\ndates (`2026-10-15`) interpreted in the organization's timezone (reminders fire at the reminder's local\n`time` in its `timezone`). Timestamps such as `created_at` / `updated_at` are UTC (`2026-09-27T14:05:00.000Z`), never raw Firestore Timestamp objects.\n\n## Versioning\nPrefix paths with `/v1/` (recommended). `/v1/<endpoint>` is identical to the legacy unversioned\n`/<endpoint>` and responses under `/v1/` carry `X-API-Version: 1`. A future `/v2/` may change contracts;\n`/v1/` will not change incompatibly.\n","contact":{"name":"ExpiryEdge Support","url":"https://expiryedge.com"}},"servers":[{"url":"https://api.expiryedge.com/v1","description":"Production (recommended)"},{"url":"https://api.expiryedge.com","description":"Production, legacy unversioned paths (identical behavior)"}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"tags":[{"name":"Auth","description":"Sign in, registration, email verification, passwords and sessions."},{"name":"Users & Team","description":"Current user, organization users, teams, invitations, availability and backup rules."},{"name":"Expiries","description":"Deadlines, renewals, licenses and other dated records."},{"name":"Expiry Types","description":"Organization-defined categories for expiries."},{"name":"Reminders & Notifications","description":"Per-expiry reminder sequences, upcoming notifications, in-app notifications and test sends."},{"name":"Recurrence","description":"Recurring expiries and renewal history."},{"name":"Bulk Operations","description":"Operations that act on many expiries at once."},{"name":"Contacts","description":"People who receive reminders, and contact groups."},{"name":"Folders","description":"Folders for organizing expiries."},{"name":"Templates","description":"Industry templates and custom expiry templates."},{"name":"Email Templates","description":"Custom reminder email templates."},{"name":"Document Collection","description":"Request documents and information from clients, review submissions and automate requests."},{"name":"Document Collection (Public)","description":"Endpoints used by the recipient of a collection request link (request token, optional password)."},{"name":"Compliance Clients & Projects","description":"Clients, projects and the compliance catalog."},{"name":"Compliance Portals","description":"Shareable read-only compliance portals."},{"name":"Directory","description":"Directory of vendors, locations, assets and other entities attached to expiries."},{"name":"Workflows","description":"Workflow checklists, runs, schedules and expiry attachments."},{"name":"Comments & Activity","description":"Expiry comments, workflow comments and the activity log."},{"name":"Change Feeds","description":"Cursor-paginated feeds for sync scripts."},{"name":"Document Intelligence","description":"AI document extraction and import column mapping."},{"name":"Settings","description":"Organization, reminder, escalation, webhook and notification settings."},{"name":"Billing & Add-ons","description":"Subscriptions, pricing, invoices, add-ons and additional licenses."},{"name":"Usage & Limits","description":"Plan limits, usage counters and storage."},{"name":"Integrations","description":"Asana, ClickUp and other third-party integrations."},{"name":"Files & Documents","description":"Files in Documents (folder files), Docs pages and their attachments, and files attached to expiries. Uploads use a 3-step flow: get an upload URL, PUT the file, then complete. See docs/api/guides/."},{"name":"v2 (preview)","description":"Resource-style preview of the next API version (expiries and contacts). REST paths and verbs, one error envelope, 201/204 conventions, cursor pagination. v1 is unchanged. See docs/api/guides/v2-preview.md."}],"paths":{"/checkEmailVerification":{"get":{"operationId":"checkEmailVerification","summary":"Check whether the caller's email is verified","description":"Requires: bearer token; any role (self only).\n`email_verified` is omitted for legacy accounts created before verification existed - treat that as verified.\n","tags":["Auth"],"responses":{"200":{"description":"Verification status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailVerificationStatus"},"example":{"email_verified":false,"email":"priya.shah@northwinddental.com"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"checkEmailVerification_post","summary":"Check whether the caller's email is verified (POST)","description":"Requires: bearer token; any role (self only).\nSame as the GET form.\n","tags":["Auth"],"responses":{"200":{"description":"Verification status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailVerificationStatus"},"example":{"email_verified":true,"email":"priya.shah@northwinddental.com"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/completeInvitation":{"post":{"operationId":"completeInvitation","summary":"Accept a team invitation and create the account","description":"Public (no token); requires the invitation token. An optional Firebase ID token proves ownership of an existing account.\nCreates (or adopts) the Auth user for the invited email and joins the inviting organization with the invitation's\nrole. Idempotent: a repeat call for an already-joined user returns `alreadyCompleted: true`.\n","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompleteInvitationRequest"},"example":{"invitationToken":"inv_Z3kq9Tr1","email":"marco.ruiz@northwinddental.com","firstName":"Marco","lastName":"Ruiz","password":"An0ther!Strong1"}}}},"responses":{"200":{"description":"Invitation accepted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompleteInvitationResult"},"example":{"success":true,"userId":"u_9Pq2Lm","organizationId":"org_northwind","organizationName":"Northwind Dental"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The invitation was sent to a different email address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"This invitation was sent to a different email address.","code":"FORBIDDEN"}}}},"404":{"description":"Unknown invitation token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Invalid or expired invitation token","code":"NOT_FOUND"}}}},"409":{"description":"Invitation already used, or the email already has an account (sign in first).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"This invitation has already been used","code":"CONFLICT"}}}},"410":{"description":"Invitation expired or cancelled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"This invitation has expired. Please ask for a new one."}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[],"x-rate-limit":{"limit":30,"window":"15m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/createApiKey":{"post":{"operationId":"createApiKey","summary":"Create an organization API key","description":"Requires: bearer token of a signed-in person (Firebase ID token or session JWT, not an API key); role admin.\nReturns the full key once - only its SHA-256 hash and first 12 characters are stored. Up to 25 active\nkeys per organization. The key authenticates as `Authorization: Bearer ee_live_...` or `X-API-Key`\nwith the chosen role (editor or viewer). An Idempotency-Key replay returns `key: null`.\n","tags":["Auth"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyInput"},"example":{"name":"Nightly HR import","role":"editor","expires_at":"2027-01-01T00:00:00Z"}}}},"responses":{"201":{"description":"Key created. `key` is shown only in this response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreated"},"example":{"key":"ee_live_4fQ9vB2kLm8XzT1rW6yNp0sHc3dJ5gA7uE9iO2qR4tY","api_key":{"id":"k7Qm2xKpA1","name":"Nightly HR import","prefix":"ee_live_4fQ9","role":"editor","status":"active","created_by":"u_71bXq","created_at":"2026-09-27T14:05:00.000Z","last_used_at":null,"expires_at":"2027-01-01T00:00:00.000Z","revoked_at":null}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"409":{"description":"The organization already has 25 active API keys (`code` LIMIT_REACHED).; or a request with the same Idempotency-Key is still being processed (code IDEMPOTENCY_IN_PROGRESS).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/getApiKeys":{"get":{"operationId":"getApiKeys","summary":"List the organization's API keys","description":"Requires: bearer token of a signed-in person (not an API key); role admin.\nReturns all keys of the caller's organization (active, expired and revoked), newest first.\nNever returns the key or its hash. `last_used_at` is updated at most once a minute.\n","tags":["Auth"],"responses":{"200":{"description":"The organization's API keys.","content":{"application/json":{"schema":{"type":"object","properties":{"api_keys":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}}}},"example":{"api_keys":[{"id":"k7Qm2xKpA1","name":"Nightly HR import","prefix":"ee_live_4fQ9","role":"editor","status":"active","created_by":"u_71bXq","created_at":"2026-09-27T14:05:00.000Z","last_used_at":"2026-09-27T22:00:04.000Z","expires_at":null,"revoked_at":null}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/login":{"post":{"operationId":"login","summary":"Log in and get a 30-day session JWT","description":"Public (no token); email + password, or a Firebase ID token for Google sign-in.\nReturns `accessToken` (HS256 session JWT, 30 days) to send as `Authorization: Bearer`.\nErrors use the legacy `{message}` shape. A Google sign-in for an email with no account auto-registers\nit and returns the `register` 201 response (no token) instead.\n","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"},"example":{"email":"priya.shah@northwinddental.com","password":"Str0ng!Passw0rd"}}}},"responses":{"200":{"description":"Credentials verified.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"},"example":{"accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidV83MWJYcSJ9.sig","user":{"id":"u_71bXq","email":"priya.shah@northwinddental.com","displayName":"Priya Shah","firstName":"Priya","lastName":"Shah","photoURL":null,"user_type":"organization","organization_id":"org_northwind"},"teams":[{"id":"team_org_northwind_general","name":"general","role":"admin"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"201":{"description":"Google sign-in for a new email - the account was registered (same body as `register`; no token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterResponse"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"description":"Invalid email or password (also returned for a rejected Google ID token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LegacyMessageError"},"example":{"message":"Invalid email or password"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"Password verification is temporarily unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LegacyMessageError"},"example":{"message":"Login is temporarily unavailable. Please try again later."}}}}},"security":[],"x-rate-limit":{"limit":10,"window":"15m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/register":{"post":{"operationId":"register","summary":"Register a new user and organization","description":"Public (no token).\nCreates the Firebase Auth user, a new organization on a 14-day trial (or the referral code's trial),\ndefault expiry types, team, contact and reminder settings, and sends a verification email. Returns no token -\nsign in afterwards. Business email domains only (free providers are rejected) unless `googleUid` is sent.\nWith `googleUid`, send the Firebase ID token for that uid as the bearer token to prove ownership.\n","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterRequest"},"example":{"email":"priya.shah@northwinddental.com","password":"Str0ng!Passw0rd","firstName":"Priya","lastName":"Shah","displayName":"Priya Shah","is_organization":true,"organization_name":"Northwind Dental","timezone":"America/Chicago","recaptchaToken":"03AFcWeA6x..."}}}},"responses":{"201":{"description":"User and organization created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterResponse"},"example":{"message":"User registered successfully","user":{"id":"u_71bXq","email":"priya.shah@northwinddental.com","firstName":"Priya","lastName":"Shah","displayName":"Priya Shah","user_type":"organization","organization_id":"org_northwind"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"`googleUid` could not be verified (missing/mismatched ID token or email). Note: an email that already\nhas a Firebase Auth account also currently surfaces as 401 (`TOKEN_INVALID`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Invalid Google user"}}}},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[],"x-rate-limit":{"limit":10,"window":"15m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/resendVerificationEmail":{"post":{"operationId":"resendVerificationEmail","summary":"Resend the email verification link","description":"Requires: bearer token; any role (self only).\n30-second cooldown between sends (429 with `retry_after`). 400 if the email is already verified.\n","tags":["Auth"],"responses":{"200":{"description":"Verification email sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Verification email sent successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":10,"window":"15m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/revokeAllSessions":{"post":{"operationId":"revokeAllSessions","summary":"Sign out all sessions","description":"Requires: bearer token; any role (self only).\nRevokes Firebase refresh tokens and invalidates every session JWT issued before now (including the one used for this call).\n","tags":["Auth"],"responses":{"200":{"description":"Sessions revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"All sessions revoked successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/revokeApiKey":{"post":{"operationId":"revokeApiKey","summary":"Revoke an API key","description":"Requires: bearer token of a signed-in person (not an API key); role admin.\nThe key stops working immediately (401) and cannot be re-enabled. Revoking an already\nrevoked key returns 200. Keys of another organization return 404.\n","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"API key id from createApiKey or getApiKeys."}}},"example":{"id":"k7Qm2xKpA1"}}}},"responses":{"200":{"description":"The revoked key.","content":{"application/json":{"schema":{"type":"object","properties":{"api_key":{"$ref":"#/components/schemas/ApiKey"}}},"example":{"api_key":{"id":"k7Qm2xKpA1","name":"Nightly HR import","prefix":"ee_live_4fQ9","role":"editor","status":"revoked","created_by":"u_71bXq","created_at":"2026-09-27T14:05:00.000Z","last_used_at":"2026-09-27T22:00:04.000Z","expires_at":null,"revoked_at":"2026-09-28T09:12:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/sendPasswordResetEmail":{"post":{"operationId":"sendPasswordResetEmail","summary":"Email a password reset link","description":"Public (no token).\nAlways returns the same message whether or not the email has an account (no user enumeration).\n","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}},"example":{"email":"priya.shah@northwinddental.com"}}}},"responses":{"200":{"description":"Accepted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"If this email exists in our system, a password reset link has been sent."}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[],"x-rate-limit":{"limit":10,"window":"15m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/sendSampleReminderEmail":{"post":{"operationId":"sendSampleReminderEmail","summary":"Send a sample reminder email (landing page widget)","description":"Public (no token).\nSends a sample expiry reminder email to `email`, with a link to a shared demo expiry. Strictly rate limited (10 requests per 15 minutes per IP).\n","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}},"example":{"email":"office@northwind-dental.com"}}}},"responses":{"200":{"description":"Email sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Sample reminder email sent."}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[],"x-rate-limit":{"limit":10,"window":"15m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/sendWelcomeEmail":{"post":{"operationId":"sendWelcomeEmail","summary":"Send the welcome email after signup","description":"Public, optional bearer token (Firebase ID token). With a token the email goes to the token's own address; without one it is only sent\nwhen `email` belongs to an account created in the last 30 minutes. At most one welcome email per account.\nIneligible or already-welcomed addresses get the same 200 without an email being sent.\n","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"},"firstName":{"type":"string","description":"Fallback only; the saved profile name wins."},"lastName":{"type":"string"}}},"example":{"email":"sam.lee@northwinddental.com","firstName":"Sam","lastName":"Lee"}}}},"responses":{"200":{"description":"Accepted (sent, or silently skipped when ineligible).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Welcome email sent successfully."}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{},{"bearerAuth":[]}],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/submitSignupRequest":{"post":{"operationId":"submitSignupRequest","summary":"Request a done-for-you account setup","description":"Public (no token). Multipart form from the marketing site: contact details plus 1-5 files of what you track today.\nEach file max 15MB, 40MB total; allowed types PDF, Word, Excel, CSV, ZIP (checked by MIME type).\n","tags":["Auth"],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["full_name","company_name","work_email","document"],"properties":{"full_name":{"type":"string","maxLength":200},"company_name":{"type":"string","maxLength":200},"work_email":{"type":"string","format":"email"},"document":{"type":"array","minItems":1,"maxItems":5,"description":"Repeat the `document` field once per file.","items":{"type":"string","format":"binary"}}}},"encoding":{"document":{"contentType":"application/pdf, application/msword, application/vnd.openxmlformats-officedocument.wordprocessingml.document, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, text/csv, application/zip, application/x-zip-compressed"}},"example":{"full_name":"Sam Lee","company_name":"Northwind Dental","work_email":"sam.lee@northwinddental.com"}}}},"responses":{"200":{"description":"Request received.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true,"message":"Got it - we'll have your account ready soon."}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[],"x-rate-limit":{"limit":10,"window":"15m","scope":"per IP, per endpoint, per server instance"}}},"/updatePassword":{"post":{"operationId":"updatePassword","summary":"Set a new password and sign out everywhere","description":"Requires: bearer token; any role (self only). Updates the Firebase Auth password, revokes refresh tokens and invalidates all session JWTs issued before now. Send `currentPassword` too: it is required (send it now - it will become required: 400 without it, 403 when wrong, 503 when the check cannot run). ","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["newPassword"],"properties":{"newPassword":{"type":"string","writeOnly":true},"currentPassword":{"type":"string","writeOnly":true,"description":"The account's current password. Send it now; it will become required."}}},"example":{"newPassword":"N3w!Str0ngPass","currentPassword":"0ld!Passw0rd"}}}},"responses":{"200":{"description":"Password updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Password updated successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/verifyCodeAndUpdatePassword":{"post":{"operationId":"verifyCodeAndUpdatePassword","summary":"Reset a password with an emailed code","description":"Public (no token); requires the 6-digit code emailed by `sendPasswordResetEmail`.\nThe code is single-use and invalidated after a limited number of wrong guesses. Unknown email, wrong, expired or locked code all return the same 400.\nPassword must be 6-256 characters.\n","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","code","password"],"properties":{"email":{"type":"string","format":"email"},"code":{"type":"string","pattern":"^[0-9]{6}$"},"password":{"type":"string","minLength":6,"maxLength":256,"writeOnly":true}}},"example":{"email":"sam.lee@northwinddental.com","code":"482915","password":"correct-horse-battery-staple"}}}},"responses":{"200":{"description":"Password updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Password updated successfully."}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[],"x-rate-limit":{"limit":10,"window":"15m","scope":"per IP, per endpoint, per server instance"}}},"/verifyEmail":{"post":{"operationId":"verifyEmail","summary":"Apply an email verification code","description":"Public (no token); requires the `oobCode` from the verification link.\nMarks the Firebase Auth email verified and sets `email_verified: true` on the user profile.\n","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["oobCode"],"properties":{"oobCode":{"type":"string","description":"Action code from the verification link."}}},"example":{"oobCode":"kX9f2Lq0bRz7..."}}}},"responses":{"200":{"description":"Email verified.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Email verified successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Missing, invalid or expired code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Invalid or expired verification code"}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[],"x-rate-limit":{"limit":30,"window":"15m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/addTeamMember":{"post":{"operationId":"addTeamMember","summary":"Add a user to a team","description":"Requires: bearer token; role editor or admin.\nThe user must be in your organization. Any `role` in the body is ignored (membership role is always `member`).\n","tags":["Users & Team"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamMembershipInput"},"example":{"teamId":"tm_3Fh8qLx","userId":"u_71bXq"}}}},"responses":{"201":{"description":"Member added.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"User added to team successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Missing ids, user already a member, or user in another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"User is already a member of this team"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/check-emails":{"post":{"operationId":"check-emails","summary":"Check which emails already have accounts","description":"Requires: bearer token; role admin.\nPre-check before inviting. At most 50 emails per request.\n","tags":["Users & Team"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["emails"],"properties":{"emails":{"type":"array","minItems":1,"maxItems":50,"items":{"type":"string","format":"email"}}}},"example":{"emails":["maria@northwinddental.com","li.wei@contosolegal.com"]}}}},"responses":{"200":{"description":"Emails split into already-registered and available.","content":{"application/json":{"schema":{"type":"object","required":["registered_emails","available_emails"],"properties":{"registered_emails":{"type":"array","items":{"type":"string"}},"available_emails":{"type":"array","items":{"type":"string"}}}},"example":{"registered_emails":["li.wei@contosolegal.com"],"available_emails":["maria@northwinddental.com"]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/createTeam":{"post":{"operationId":"createTeam","summary":"Create a team","description":"Requires: bearer token; role editor or admin.\nTeam names are unique per organization (case-insensitive). The caller becomes a member.\nThe plan's team limit is enforced; reaching it returns 400 with `limitReached: true`.\n","tags":["Users & Team"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1}}},"example":{"name":"Front Desk"}}}},"responses":{"201":{"description":"Team created.","content":{"application/json":{"schema":{"type":"object","required":["message","team"],"properties":{"message":{"type":"string"},"team":{"$ref":"#/components/schemas/Team"}}},"example":{"message":"Team created successfully","team":{"team_id":"tm_3Fh8qLx","team_name":"Front Desk","created_at":"2026-09-27T14:05:00.000Z","created_by":"ops@northwinddental.com"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Name missing, name already used, or plan team limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamLimitError"},"examples":{"duplicate":{"value":{"message":"A team with this name already exists"}},"limit":{"value":{"message":"You've reached your plan's limit of 3 teams. Upgrade your plan to add more.","current":3,"limit":3,"remaining":0,"limitReached":true}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deleteAvailability":{"post":{"operationId":"deleteAvailability","summary":"Delete a leave record","description":"Requires: bearer token; any role for your own records, editor or admin for others in your organization.\n","tags":["Users & Team"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["availability_id"],"properties":{"availability_id":{"type":"string"}}},"example":{"availability_id":"av_6Jc3nTy"}}}},"responses":{"200":{"$ref":"#/components/responses/AvailabilitySuccessOk"},"400":{"$ref":"#/components/responses/LegacyMessageBadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/LegacyMessageForbidden"},"404":{"$ref":"#/components/responses/LegacyMessageNotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/LegacyMessageServerError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deleteBackupRule":{"post":{"operationId":"deleteBackupRule","summary":"Delete a backup rule","description":"Requires: bearer token; role editor or admin.\n","tags":["Users & Team"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["rule_id"],"properties":{"rule_id":{"type":"string"}}},"example":{"rule_id":"br_8Vt1xKd"}}}},"responses":{"200":{"$ref":"#/components/responses/AvailabilitySuccessOk"},"400":{"$ref":"#/components/responses/LegacyMessageBadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/LegacyMessageNotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/LegacyMessageServerError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deleteTeam":{"post":{"operationId":"deleteTeam","summary":"Delete a team","description":"Requires: bearer token; role admin.\nExpiries move to `moveExpiriesToTeamId` or become team-less; members move to `moveMembersToTeamId` or are removed from the team.\n","tags":["Users & Team"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["teamId"],"properties":{"teamId":{"type":"string"},"moveExpiriesToTeamId":{"type":"string","description":"Team (in your organization) to move the team's expiries to. Omit to leave them without a team."},"moveMembersToTeamId":{"type":"string","description":"Team to move the members to. Omit to just remove them."}}},"example":{"teamId":"tm_3Fh8qLx","moveExpiriesToTeamId":"tm_9Kd2wRp"}}}},"responses":{"200":{"description":"Team deleted.","content":{"application/json":{"schema":{"type":"object","required":["message","stats"],"properties":{"message":{"type":"string"},"stats":{"type":"object","properties":{"movedExpiries":{"type":"integer"},"deletedExpiries":{"type":"integer","description":"Expiries whose team was cleared (they are not deleted)."},"movedMembers":{"type":"integer"},"deletedMembers":{"type":"integer","description":"Memberships removed (users are not deleted)."}}}}},"example":{"message":"Team deleted successfully","stats":{"movedExpiries":12,"deletedExpiries":0,"movedMembers":0,"deletedMembers":4}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/exportMyData":{"get":{"operationId":"exportMyData","summary":"Export my personal data (GDPR)","description":"Requires: bearer token; any role.\nReturns a JSON download (`Content-Disposition: attachment`) of your profile, settings, onboarding state,\nnotification preferences and (redacted) organization record. Expiries, contacts and documents are not included yet.\n","tags":["Users & Team"],"responses":{"200":{"$ref":"#/components/responses/PersonalDataExportOk"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"exportMyData_post","summary":"Export my personal data (POST)","description":"Requires: bearer token; any role.\nSame as GET.\n","tags":["Users & Team"],"responses":{"200":{"$ref":"#/components/responses/PersonalDataExportOk"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getAccountDetails":{"get":{"operationId":"getAccountDetails","summary":"Get account and plan details","description":"Requires: bearer token; any role.\nReturns the caller's name plus the organization's plan, billing dates and notification credit balances.\n","tags":["Users & Team"],"responses":{"200":{"$ref":"#/components/responses/AccountDetailsOk"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getAccountDetails_post","summary":"Get account and plan details (POST)","description":"Requires: bearer token; any role.\nSame as GET.\n","tags":["Users & Team"],"responses":{"200":{"$ref":"#/components/responses/AccountDetailsOk"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getBackupRules":{"get":{"operationId":"getBackupRules","summary":"List backup rules","description":"Requires: bearer token; any role (editors and admins see every rule; viewers only their own).\nAlso returns the organization's users for pickers. Unbounded.\n","tags":["Users & Team"],"responses":{"200":{"$ref":"#/components/responses/BackupRulesOk"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/LegacyMessageServerError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getBackupRules_post","summary":"List backup rules (POST)","description":"Requires: bearer token; any role.\nSame as GET.\n","tags":["Users & Team"],"responses":{"200":{"$ref":"#/components/responses/BackupRulesOk"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/LegacyMessageServerError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getMe":{"get":{"operationId":"getMe","summary":"Get the caller's profile","description":"Requires: bearer token; any role (self only).\nReturns the user profile document (internal security fields removed). Useful as a connection test.\n","tags":["Users & Team"],"responses":{"200":{"description":"The caller's profile.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"},"example":{"id":"u_71bXq","email":"priya.shah@northwinddental.com","firstName":"Priya","lastName":"Shah","displayName":"Priya Shah","role":"admin","user_type":"organization","organization_id":"org_northwind","timezone":"America/Chicago","email_verified":true,"createdAt":"2026-09-27T14:05:00.000Z"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getMe_post","summary":"Get the caller's profile (POST)","description":"Requires: bearer token; any role (self only).\nSame as the GET form.\n","tags":["Users & Team"],"responses":{"200":{"description":"The caller's profile.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getMyAvailability":{"get":{"operationId":"getMyAvailability","summary":"List my leave","description":"Requires: bearer token; any role.\nYour leave records, newest start date first. Unbounded.\n","tags":["Users & Team"],"responses":{"200":{"$ref":"#/components/responses/AvailabilityListOk"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/LegacyMessageServerError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getMyAvailability_post","summary":"List my leave (POST)","description":"Requires: bearer token; any role.\nSame as GET.\n","tags":["Users & Team"],"responses":{"200":{"$ref":"#/components/responses/AvailabilityListOk"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/LegacyMessageServerError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getReassignmentLogs":{"get":{"operationId":"getReassignmentLogs","summary":"List expiry reassignments","description":"Requires: bearer token; any role.\nThe 200 most recent automatic reassignments made while users were on leave, newest first.\n`timestamp` is an ISO 8601 date-time string (UTC).\n","tags":["Users & Team"],"responses":{"200":{"$ref":"#/components/responses/ReassignmentLogsOk"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/LegacyMessageServerError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getReassignmentLogs_post","summary":"List expiry reassignments (POST)","description":"Requires: bearer token; any role.\nSame as GET.\n","tags":["Users & Team"],"responses":{"200":{"$ref":"#/components/responses/ReassignmentLogsOk"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/LegacyMessageServerError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getTeamAvailability":{"get":{"operationId":"getTeamAvailability","summary":"List team leave","description":"Requires: bearer token; any role.\nLeave for members of `team_id`, or (without it) of every team you belong to; only your own if you are in no team.\nIncludes `user_name` and `user_photo`. Unbounded.\n","tags":["Users & Team"],"parameters":[{"$ref":"#/components/parameters/AvailabilityTeamIdQuery"}],"responses":{"200":{"$ref":"#/components/responses/AvailabilityListOk"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/LegacyMessageServerError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getTeamAvailability_post","summary":"List team leave (POST)","description":"Requires: bearer token; any role.\nSame as GET; `team_id` is still read from the query string.\n","tags":["Users & Team"],"parameters":[{"$ref":"#/components/parameters/AvailabilityTeamIdQuery"}],"responses":{"200":{"$ref":"#/components/responses/AvailabilityListOk"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/LegacyMessageServerError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getTeamMembers":{"get":{"operationId":"getTeamMembers","summary":"List a team's members","description":"Requires: bearer token; any role.\nReturns all members of the team (unbounded). `role` is the member's organization role.\n","tags":["Users & Team"],"parameters":[{"$ref":"#/components/parameters/TeamIdQuery"}],"responses":{"200":{"$ref":"#/components/responses/TeamMembersOk"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getTeamMembers_post","summary":"List a team's members (POST)","description":"Requires: bearer token; any role.\nSame as GET; the team id is still read from the `id` query parameter, not the body.\n","tags":["Users & Team"],"parameters":[{"$ref":"#/components/parameters/TeamIdQuery"}],"responses":{"200":{"$ref":"#/components/responses/TeamMembersOk"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getTeams":{"get":{"operationId":"getTeams","summary":"List teams","description":"Requires: bearer token; any role.\nReturns every team in your organization with its member count. Returns all records; unbounded.\nUse POST with `{\"userTeamsOnly\": true}` to get only the teams you belong to.\n","tags":["Users & Team"],"responses":{"200":{"description":"Teams in the organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamList"},"example":{"teams":[{"team_id":"tm_3Fh8qLx","team_name":"Front Desk","created_at":"2026-09-27T14:05:00.000Z","member_count":4}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Your user profile was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LegacyMessageError"},"example":{"message":"User not found"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getTeams_post","summary":"List teams (optionally only mine)","description":"Requires: bearer token; any role.\nSame as GET. With `userTeamsOnly: true` only teams you are a member of are returned. Unbounded.\n","tags":["Users & Team"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"userTeamsOnly":{"type":"boolean","description":"Return only teams the caller is a member of."}}},"example":{"userTeamsOnly":true}}}},"responses":{"200":{"description":"Teams.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamList"},"example":{"teams":[{"team_id":"tm_3Fh8qLx","team_name":"Front Desk","created_at":"2026-09-27T14:05:00.000Z","member_count":4}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Your user profile was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LegacyMessageError"},"example":{"message":"User not found"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getUsers":{"get":{"operationId":"getUsers","summary":"List users in the caller's organization","description":"Requires: bearer token; any role.\nReturns all users of the organization (safe fields only); unbounded.\n","tags":["Users & Team"],"responses":{"200":{"description":"Organization users.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserList"},"example":{"users":[{"id":"u_71bXq","email":"priya.shah@northwinddental.com","displayName":"Priya Shah","firstName":"Priya","lastName":"Shah","role":"admin","user_type":"organization","organization_id":"org_northwind","status":"active","created_at":"2026-09-27T14:05:00.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getUsers_post","summary":"List users in the caller's organization (POST)","description":"Requires: bearer token; any role.\nSame as the GET form; the body is ignored.\n","tags":["Users & Team"],"responses":{"200":{"description":"Organization users.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserList"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/invitations/cancel/{invitationId}":{"delete":{"operationId":"invitationsCancel","summary":"Cancel a pending invitation","description":"Requires: bearer token; role admin.\nThe invitation id is the last path segment (do not add a query string). Sets the invitation status to `cancelled`.\n","tags":["Users & Team"],"parameters":[{"name":"invitationId","in":"path","required":true,"schema":{"type":"string"},"example":"inv_5Rt2kWq"}],"responses":{"200":{"description":"Cancelled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Invitation cancelled successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/invitations/pending":{"get":{"operationId":"invitationsPending","summary":"List pending invitations","description":"Requires: bearer token; any role.\nReturns all pending invitations for your organization, newest first. Unbounded.\n","tags":["Users & Team"],"responses":{"200":{"description":"Pending invitations.","content":{"application/json":{"schema":{"type":"object","required":["pending_invitations"],"properties":{"pending_invitations":{"type":"array","items":{"$ref":"#/components/schemas/Invitation"}}}},"example":{"pending_invitations":[{"id":"inv_5Rt2kWq","email":"maria@northwinddental.com","organization_id":"org_northwind","invited_by":"u_71bXq","inviter_name":"Dana Brooks","status":"pending","created_at":"2026-09-27T14:05:00.000Z","expires_at":"2026-10-04T14:05:00.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/invite":{"post":{"operationId":"invite","summary":"Invite users to the organization","description":"Requires: bearer token; role admin.\nUp to 50 emails per request. Emails already in the organization or with a pending invitation are skipped.\nPending invitations count toward the plan's user limit. Invitations expire after 7 days.\n","tags":["Users & Team"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["emails"],"properties":{"emails":{"type":"array","minItems":1,"maxItems":50,"items":{"type":"string","format":"email"}},"role":{"type":"string","enum":["admin","editor","viewer"],"default":"editor"}}},"example":{"emails":["maria@northwinddental.com"],"role":"viewer"}}}},"responses":{"201":{"description":"Invitations created and emailed.","content":{"application/json":{"schema":{"type":"object","required":["message","invitations"],"properties":{"message":{"type":"string"},"invitations":{"type":"array","items":{"$ref":"#/components/schemas/Invitation"}}}},"example":{"message":"1 invitation(s) sent successfully","invitations":[{"id":"inv_5Rt2kWq","email":"maria@northwinddental.com","organization_id":"org_northwind","invited_by":"u_71bXq","inviter_name":"Dana Brooks","status":"pending","role":"viewer","created_at":"2026-09-27T14:05:00.000Z","expires_at":"2026-10-04T14:05:00.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"400":{"description":"Invalid emails or role, not enough user seats, or every email was skipped.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Cannot invite 3 user(s). You have 1 slot(s) remaining out of 5 total."}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"409":{"$ref":"#/components/responses/IdempotencyInProgress"},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":30,"window":"15m","scope":"per IP, per endpoint, per server instance"}}},"/removeTeamMember":{"post":{"operationId":"removeTeamMember","summary":"Remove a user from a team","description":"Requires: bearer token; role editor or admin.\n","tags":["Users & Team"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamMembershipInput"},"example":{"teamId":"tm_3Fh8qLx","userId":"u_71bXq"}}}},"responses":{"200":{"description":"Member removed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"User removed from team successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/renameTeam":{"post":{"operationId":"renameTeam","summary":"Rename a team","description":"Requires: bearer token; role editor or admin.\nThe team id is passed as the `id` query parameter; the new name in the body.\n","tags":["Users & Team"],"parameters":[{"name":"id","in":"query","required":true,"description":"Team id.","schema":{"type":"string"},"example":"tm_3Fh8qLx"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1}}},"example":{"name":"Reception"}}}},"responses":{"200":{"description":"Renamed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Team renamed successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Missing id or name, or the name is already used.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LegacyMessageError"},"example":{"message":"A team with this name already exists"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Team not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LegacyMessageError"},"example":{"message":"Team not found"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/requestAccountDeletion":{"post":{"operationId":"requestAccountDeletion","summary":"Request deletion of my account (GDPR)","description":"Requires: bearer token; any role.\nFiles an erasure request that support completes within 30 days; nothing is deleted immediately.\nEach call files a new request. `reason` is truncated to 1000 characters.\n","tags":["Users & Team"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","maxLength":1000}}},"example":{"reason":"Closing the practice at the end of the year."}}}},"responses":{"201":{"description":"Request filed.","content":{"application/json":{"schema":{"type":"object","required":["status","message","request_id"],"properties":{"status":{"type":"string","const":"success"},"message":{"type":"string"},"request_id":{"type":"string","readOnly":true}}},"example":{"status":"success","message":"We've received your deletion request and will process it within 30 days, as required by GDPR.","request_id":"del_2Wm7vQa"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/resendInvitation":{"post":{"operationId":"resendInvitation","summary":"Resend an invitation email","description":"Requires: bearer token; role admin.\nOne resend per invitation every 60 seconds; an earlier retry is rejected with 429 (`code: RATE_LIMITED`, `retry_after_seconds`, `Retry-After` header).\n`invitationId` may also be passed as a query parameter.\n","tags":["Users & Team"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["invitationId"],"properties":{"invitationId":{"type":"string"}}},"example":{"invitationId":"inv_5Rt2kWq"}}}},"responses":{"200":{"description":"Invitation email resent.","content":{"application/json":{"schema":{"type":"object","required":["message","email","last_resent_at","cooldown_seconds"],"properties":{"message":{"type":"string"},"email":{"type":"string"},"last_resent_at":{"$ref":"#/components/schemas/Timestamp"},"cooldown_seconds":{"type":"integer"}}},"example":{"message":"Invitation email resent.","email":"maria@northwinddental.com","last_resent_at":"2026-09-27T14:05:00.000Z","cooldown_seconds":60}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"409":{"description":"The invitation is no longer pending (`code: INVITATION_NOT_PENDING`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusCodedError"},"example":{"error":"This invitation is accepted and can no longer be resent.","code":"INVITATION_NOT_PENDING"}}}},"410":{"description":"The invitation has expired (`code: INVITATION_EXPIRED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusCodedError"},"example":{"error":"This invitation has expired. Please cancel it and send a new one.","code":"INVITATION_EXPIRED"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"502":{"description":"The email provider failed to send the invitation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Email provider failed to send the invitation. Please try again in a moment.","code":"INTERNAL_ERROR","requestId":"3fa1c09b2d7e"}}}}},"x-rate-limit":{"limit":30,"window":"15m","scope":"per IP, per endpoint, per server instance"}}},"/setAvailability":{"post":{"operationId":"setAvailability","summary":"Add or update my leave","description":"Requires: bearer token; any role (own records only).\nCreates a leave period, or updates one when `availability_id` is given. Overlapping leave is rejected.\nWhile on leave, expiries are reassigned according to your backup rule. Errors use `{message}`.\n","tags":["Users & Team"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["start_date","end_date"],"properties":{"start_date":{"$ref":"#/components/schemas/IsoDate"},"end_date":{"$ref":"#/components/schemas/IsoDate"},"reason":{"type":"string"},"availability_id":{"type":"string","description":"Id of your existing leave record to update."}}},"example":{"start_date":"2026-10-15","end_date":"2026-10-22","reason":"Annual leave"}}}},"responses":{"200":{"description":"Saved.","content":{"application/json":{"schema":{"type":"object","required":["success","availability_id"],"properties":{"success":{"type":"boolean"},"availability_id":{"type":"string"}}},"example":{"success":true,"availability_id":"av_6Jc3nTy"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/LegacyMessageBadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/LegacyMessageForbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/LegacyMessageServerError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/setBackupRule":{"post":{"operationId":"setBackupRule","summary":"Create or update a user's backup rule","description":"Requires: bearer token; role editor or admin.\nOne rule per primary user (upsert). `general` uses one backup; `type_based` picks a backup per expiry type;\n`mixed` uses type backups with `fallback_user_id` / `general_backup_user_id` for other types.\n","tags":["Users & Team"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackupRuleInput"},"example":{"primary_user_id":"u_4Np8cZe","backup_type":"mixed","general_backup_user_id":"u_71bXq","fallback_user_id":"u_71bXq","type_backups":[{"expiry_type_id":"et_2Lp9","expiry_type_name":"Professional License","backup_user_id":"u_9Qw3rTs"}]}}}},"responses":{"200":{"description":"Saved.","content":{"application/json":{"schema":{"type":"object","required":["success","rule_id"],"properties":{"success":{"type":"boolean"},"rule_id":{"type":"string"}}},"example":{"success":true,"rule_id":"br_8Vt1xKd"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/LegacyMessageBadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/LegacyMessageServerError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/triggerAvailabilityScanner":{"post":{"operationId":"triggerAvailabilityScanner","summary":"Run the leave reassignment scan now","description":"Requires: bearer token; role admin.\nRuns the hourly leave scan for your organization immediately (normally it runs at 07:00 organization time),\nreassigning expiries of users currently on leave to their backups and notifying them.\n","tags":["Users & Team"],"responses":{"200":{"description":"Scan finished.","content":{"application/json":{"schema":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean"},"processed":{"type":"integer","description":"Organizations processed."},"reassigned":{"type":"integer","description":"Expiries reassigned."},"alerts":{"type":"integer","description":"Alerts sent where no backup was available."}}},"example":{"success":true,"processed":1,"reassigned":3,"alerts":0}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/LegacyMessageForbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/LegacyMessageServerError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateUser":{"post":{"operationId":"updateUser","summary":"Update the caller's profile","description":"Requires: bearer token; any role (self only).\nOnly the listed fields are written; anything else (role, organization_id, email...) is ignored.\n412 if the profile has not been provisioned yet.\n","tags":["Users & Team"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserUpdateInput"},"example":{"firstName":"Priya","lastName":"Shah-Patel","phone":"+1 312 555 0142","timezone":"America/Chicago"}}}},"responses":{"200":{"description":"Profile updated.","content":{"application/json":{"schema":{"type":"object","required":["message","user"],"properties":{"message":{"type":"string"},"user":{"$ref":"#/components/schemas/User"}}},"example":{"message":"User updated successfully","user":{"id":"u_71bXq","email":"priya.shah@northwinddental.com","firstName":"Priya","lastName":"Shah-Patel","phone":"+1 312 555 0142","timezone":"America/Chicago","role":"admin","organization_id":"org_northwind","updated_at":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"412":{"description":"The user profile does not exist yet (registration incomplete).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"User profile not yet provisioned - please complete registration first."}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateUserRole":{"post":{"operationId":"updateUserRole","summary":"Change a member's role","description":"Requires: bearer token; role admin.\nThe target user must be in the caller's organization. Also updates the user's team memberships (admin or member).\n","tags":["Users & Team"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userId","role"],"properties":{"userId":{"type":"string"},"role":{"type":"string","enum":["admin","editor","viewer"],"description":"Case-insensitive."}}},"example":{"userId":"u_9Pq2Lm","role":"editor"}}}},"responses":{"200":{"description":"Role updated.","content":{"application/json":{"schema":{"type":"object","required":["message","userId","role"],"properties":{"message":{"type":"string"},"userId":{"type":"string"},"role":{"type":"string","enum":["admin","editor","viewer"]}}},"example":{"message":"User role updated successfully","userId":"u_9Pq2Lm","role":"editor"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/user/delete":{"post":{"operationId":"userDelete","summary":"Delete a user from the organization","description":"Requires: bearer token (Firebase ID token only); role admin.\nOwned expiries are deleted or reassigned; the user is unassigned (or replaced) on assigned expiries. Contacts are kept.\nYou cannot delete yourself, the organization owner or the last active admin. Irreversible.\n","tags":["Users & Team"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userId","expiriesAction"],"properties":{"userId":{"type":"string"},"expiriesAction":{"type":"string","enum":["delete","reassign"],"description":"What to do with expiries the user owns."},"reassignToUserId":{"type":"string","description":"Required when `expiriesAction` is `reassign`."},"assignedExpiriesAction":{"type":"string","enum":["unassign","reassign"],"default":"unassign"},"reassignAssignedToUserId":{"type":"string","description":"Required when `assignedExpiriesAction` is `reassign`."}}},"example":{"userId":"u_4Np8cZe","expiriesAction":"reassign","reassignToUserId":"u_71bXq","assignedExpiriesAction":"unassign"}}}},"responses":{"200":{"description":"User deleted.","content":{"application/json":{"schema":{"type":"object","required":["message","stats"],"properties":{"message":{"type":"string"},"stats":{"type":"object","properties":{"expiriesProcessed":{"type":"integer"},"expiriesUnassigned":{"type":"integer"},"expiriesAssignedReassigned":{"type":"integer"},"teamMembershipsRemoved":{"type":"integer"}}}}},"example":{"message":"User deleted successfully","stats":{"expiriesProcessed":14,"expiriesUnassigned":3,"expiriesAssignedReassigned":0,"teamMembershipsRemoved":2}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/user/dependencies/{userId}":{"get":{"operationId":"userDependencies","summary":"Get what a user owns before deleting them","description":"Requires: bearer token (Firebase ID token only); role admin.\nCounts the user's owned and assigned expiries, contacts and team memberships.\n","tags":["Users & Team"],"parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"},"example":"u_4Np8cZe"}],"responses":{"200":{"description":"Dependency counts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserDependencies"},"example":{"expiriesCount":14,"assignedExpiriesCount":1,"totalExpiriesCount":15,"contactsCount":6,"teamsCount":1,"teams":[{"id":"tm_3Fh8qLx","name":"Front Desk"}],"assignedExpiries":[{"id":"exp_4Tq9sLm2","name":"Dental license renewal"}],"userData":{"email":"sam@northwinddental.com","displayName":"Sam Ortiz","role":"editor","status":"active"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/user/status":{"post":{"operationId":"userStatus","summary":"Activate or deactivate a user","description":"Requires: bearer token (Firebase ID token only; session JWTs are rejected); role admin.\nDeactivating disables sign-in and revokes the user's sessions. You cannot change your own status,\ndeactivate the organization owner, or deactivate the last active admin.\n","tags":["Users & Team"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userId","status"],"properties":{"userId":{"type":"string"},"status":{"type":"string","enum":["active","deactivated"]}}},"example":{"userId":"u_4Np8cZe","status":"deactivated"}}}},"responses":{"200":{"description":"Status changed.","content":{"application/json":{"schema":{"type":"object","required":["message","userId","status"],"properties":{"message":{"type":"string"},"userId":{"type":"string"},"status":{"type":"string","enum":["active","deactivated"]}}},"example":{"message":"User deactivated successfully","userId":"u_4Np8cZe","status":"deactivated"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/verify_invitation_token":{"post":{"operationId":"verify_invitation_token","summary":"Verify an invitation token","description":"Public (no token); requires the invitation token from the invitation email link.\nUsed by the registration page to prefill the invitee's email and organization.\n","tags":["Users & Team"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string","description":"The `token` query value from the invitation link."}}},"example":{"token":"inv_5Rt2kWq"}}}},"responses":{"200":{"description":"The invitation is valid.","content":{"application/json":{"schema":{"type":"object","required":["invitation","organization_name"],"properties":{"invitation":{"type":"object","properties":{"email":{"type":"string"},"organization_id":{"type":"string"},"invited_by":{"type":"string"}}},"organization_name":{"type":"string"}}},"example":{"invitation":{"email":"maria@northwinddental.com","organization_id":"org_northwind","invited_by":"u_71bXq"},"organization_name":"Northwind Dental"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Token missing, or the invitation was already used or cancelled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"This invitation has already been used or cancelled"}}}},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"410":{"description":"The invitation has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"This invitation has expired"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/archiveExpiry":{"post":{"operationId":"archiveExpiry","summary":"Archive an expiry","description":"Requires: bearer token; role editor or admin.\nSets `is_archive: true` and `state: onhold`, and pauses the record's workflow attachments and collection triggers.\n","tags":["Expiries"],"requestBody":{"$ref":"#/components/requestBodies/ExpiryIdBody"},"responses":{"200":{"description":"Archived.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Expiry archived successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/createExpiry":{"post":{"operationId":"createExpiry","summary":"Create an expiry","description":"Requires: bearer token; role editor or admin.\nCounts against the plan's expiry limit (403 with `current` / `limit` / `remaining` when reached). A share token is\ngenerated for every record; `is_public` defaults to false and `escalation_enabled` to false.\n","tags":["Expiries"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpiryInput"},"example":{"name":"Contoso Legal - Professional Liability Insurance","type":"Insurance","expiry_date":"2026-10-15","start_date":"2025-10-15","priority":"High","state":"todo","notes":"Broker renewal packet due 30 days before expiry.","team_id":"0","contacts":["c_8Hk2pQ"],"assigned_to":"u_71bXq","is_recurring":true,"recurrence_type":"year","recurrence_interval":1,"recurrence_mode":"renew_as_copy"}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpiryCreatedResponse"},"example":{"message":"Expiry created successfully","id":"exp_4Tq9sLm2"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"400":{"description":"Invalid input, a referenced record outside your organization (`code: VALIDATION_FAILED`, `details` lists the fields), or an invalid recurring configuration (`details` is an array of messages).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Invalid recurring configuration","details":["Recurrence interval must be at least 1"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Role is viewer, or the plan's expiry limit is reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanLimitError"},"example":{"error":"You have reached the maximum number of expiries for your plan.","current":100,"limit":100,"remaining":0}}}},"409":{"$ref":"#/components/responses/IdempotencyInProgress"},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deleteExpiry":{"post":{"operationId":"deleteExpiry_post","summary":"Delete an expiry (POST)","description":"Requires: bearer token; role editor or admin.\nSame as DELETE; the id is read from the `id` query parameter.\n","tags":["Expiries"],"parameters":[{"$ref":"#/components/parameters/ExpiryIdQuery"}],"responses":{"200":{"$ref":"#/components/responses/ExpiryDeletedOk"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"delete":{"operationId":"deleteExpiry","summary":"Delete an expiry","description":"Requires: bearer token; role editor or admin.\nPermanently deletes the record and its pending reminders, notifications, workflow attachments and collection triggers.\n","tags":["Expiries"],"parameters":[{"$ref":"#/components/parameters/ExpiryIdQuery"}],"responses":{"200":{"$ref":"#/components/responses/ExpiryDeletedOk"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/generateShareUrl":{"get":{"operationId":"generateShareUrl","summary":"Enable and get the public share link","description":"Requires: bearer token; role editor or admin.\nSets `is_public: true` and returns the share URL. The token is stable: re-sharing returns the same URL.\n","tags":["Expiries"],"parameters":[{"$ref":"#/components/parameters/ExpiryIdQuery"}],"responses":{"200":{"$ref":"#/components/responses/ExpiryShareUrlOk"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"generateShareUrl_post","summary":"Enable and get the public share link (POST)","description":"Requires: bearer token; role editor or admin.\nSame as GET; the id is read from the `id` query parameter.\n","tags":["Expiries"],"parameters":[{"$ref":"#/components/parameters/ExpiryIdQuery"}],"responses":{"200":{"$ref":"#/components/responses/ExpiryShareUrlOk"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getAllExpiries":{"post":{"operationId":"getAllExpiries","summary":"List all expiries","description":"Requires: bearer token; any role.\nReturns the organization's expiries as a bare array, capped by `limit` (default and max 5000). No cursor.\nPrefer listExpiryChanges for polling. `share_password` is never returned (see `has_share_password`).\n","tags":["Expiries"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"selectedTeam":{"description":"Team id or array of team ids (max 30 for an array). `'0'` or omitted = all teams.","oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"limit":{"type":"integer","minimum":1,"maximum":5000,"default":5000,"description":"Values above 5000 are clamped."},"orderBy":{"type":"string","enum":["expiry_date","created_at","updated_at","name","type","priority","state","start_date"],"default":"expiry_date","description":"Any other value falls back to `expiry_date`."},"orderDirection":{"type":"string","enum":["asc","desc"],"default":"asc"},"activeOnly":{"type":"boolean","default":false,"description":"Drop archived and done records."}}},"example":{"selectedTeam":"0","limit":500,"orderBy":"expiry_date","orderDirection":"asc","activeOnly":true}}}},"responses":{"200":{"description":"Expiries, ordered as requested.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ExpiryListItem"}},"example":[{"id":"exp_4Tq9sLm2","name":"Northwind Dental - State Dental License","type":"License","expiry_date":"2026-10-15","priority":"High","state":"todo","team_id":"0","contacts":["c_8Hk2pQ"],"is_done":false,"is_archive":false,"is_public":false,"has_share_password":false,"organization_id":"org_northwind","user_id":"u_71bXq","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}]}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":500,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getExpiry":{"get":{"operationId":"getExpiry","summary":"Get one expiry","description":"Requires: bearer token; any role in the expiry's organization.\nReturns the record with `contacts` and `escalation_contacts` resolved to contact objects and assignee names.\n","tags":["Expiries"],"parameters":[{"$ref":"#/components/parameters/ExpiryIdQuery"}],"responses":{"200":{"$ref":"#/components/responses/ExpiryDetailOk"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getExpiry_post","summary":"Get one expiry (POST)","description":"Requires: bearer token; any role in the expiry's organization.\nSame as GET; the id is still read from the `id` query parameter.\n","tags":["Expiries"],"parameters":[{"$ref":"#/components/parameters/ExpiryIdQuery"}],"responses":{"200":{"$ref":"#/components/responses/ExpiryDetailOk"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getExpiryMetrics":{"post":{"operationId":"getExpiryMetrics","summary":"Get dashboard metric counts","description":"Requires: bearer token; any role.\nCounts the organization's expiries per dashboard card. Computed over all records (no pagination).\n","tags":["Expiries"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"selectedTeam":{"type":"string","description":"Team id; `'0'` or omitted = all teams."},"futureDurationDays":{"description":"Window in days for the `future` count, or `all`.","oneOf":[{"type":"integer"},{"type":"string"}]}}},"example":{"selectedTeam":"0","futureDurationDays":90}}}},"responses":{"200":{"description":"Metric counts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpiryMetrics"},"example":{"total":142,"completed":38,"expired":4,"archived":11,"active":138,"future":27,"upcomingThisMonth":9,"today":1,"notNotifying":3}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":500,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getForecastData":{"get":{"operationId":"getForecastData","summary":"Get renewal forecast, workload and risk analytics","description":"Requires: bearer token; any role.\nComputed over all non-archived expiries of the organization. `months` (default 12) sets the forecast horizon. Workload keys and `assigned_to` are user ids or `Unassigned`.\n","tags":["Expiries"],"parameters":[{"name":"months","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":12},"example":12}],"responses":{"200":{"description":"Forecast analytics.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpiryForecast"},"example":{"success":true,"summary":{"totalActive":118,"totalOverdue":4,"expiringThisMonth":6,"expiringNextMonth":9,"highPriorityAtRisk":2,"avgProcessingDays":12,"medianProcessingDays":9,"completionRate":38,"busiest_month":"2026-10"},"renewalForecast":[{"month":"2026-10","count":9,"highPriority":2,"items":[{"id":"exp_4Tq9sLm2","name":"Business License","expiry_date":"2026-10-15","type":"License","priority":"High","assigned_to":"u_71bXq"}]}],"workload":[{"userId":"u_71bXq","name":"Priya Shah","total":14,"thisMonth":3,"nextMonth":5,"overdue":1,"items":[]}],"overdueAging":{"1-7 days":2,"8-30 days":1,"31-90 days":1,"90+ days":0,"total":4,"items":[{"id":"exp_7Yh3kP","name":"Autoclave Inspection","expiry_date":"2026-09-20","days_overdue":7,"priority":"Medium","assigned_to":"u_71bXq","type":"Inspection"}]},"typeAnalysis":[{"type":"License","total":22,"active":8,"expired":1,"upcoming":12,"avgDurationDays":365}],"riskItems":[{"id":"exp_4Tq9sLm2","name":"Business License","expiry_date":"2026-10-15","days_left":18,"priority":"High","type":"License","assigned_to":"u_71bXq","assignee_name":"Priya Shah"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getPaginatedExpiries":{"get":{"operationId":"getPaginatedExpiries_get","summary":"List expiries with filters and pagination (query string)","description":"Requires: bearer token; any role.\nSame as the POST form but reads the filters from the query string. Prefer listExpiryChanges for polling.\n","tags":["Expiries"],"parameters":[{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":10000,"default":10}},{"name":"metric","in":"query","schema":{"$ref":"#/components/schemas/ExpiryMetricFilter"}},{"name":"type","in":"query","schema":{"type":"string"},"description":"Expiry type name, or `All`."},{"name":"priority","in":"query","schema":{"type":"string"},"description":"Priority, or `All`."},{"name":"stateFilter","in":"query","schema":{"type":"string"}},{"name":"search","in":"query","schema":{"type":"string"}},{"name":"team_id","in":"query","schema":{"type":"string"}},{"name":"folder_id","in":"query","schema":{"type":"string"},"description":"Folder id, or `root` for unfiled."},{"name":"include_subfolders","in":"query","schema":{"type":"boolean"}},{"name":"assignedToMe","in":"query","schema":{"type":"boolean"}},{"name":"assignedUser","in":"query","schema":{"type":"string"}},{"name":"futureDurationDays","in":"query","schema":{"type":"string"},"description":"Days, or `all`."},{"name":"notifyNone","in":"query","schema":{"type":"boolean"}},{"name":"sortBy","in":"query","schema":{"type":"string","default":"expiry_date"}},{"name":"sortDirection","in":"query","schema":{"type":"string","enum":["asc","desc"],"default":"asc"}}],"responses":{"200":{"$ref":"#/components/responses/PaginatedExpiriesOk"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":500,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getPaginatedExpiries","summary":"List expiries with filters and pagination","description":"Requires: bearer token; any role.\nOffset pagination with `page` / `limit` (default 10, max 10000). With complex filters the server scans at most\n5000 records in memory (`pagination.optimized: false`). Prefer listExpiryChanges for polling.\n","tags":["Expiries"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedExpiriesQuery"},"example":{"page":1,"limit":25,"metric":"future","futureDurationDays":30,"type":"License","sortBy":"expiry_date","sortDirection":"asc"}}}},"responses":{"200":{"$ref":"#/components/responses/PaginatedExpiriesOk"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":500,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getTeamProductivity":{"get":{"operationId":"getTeamProductivity","summary":"Get on-time completion metrics per assignee","description":"Requires: bearer token; any role.\nCovers non-archived expiries whose expiry date is within the last `days` days (default 90) or later. Sorted by `onTimeRate` descending.\n","tags":["Expiries"],"parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":90},"example":90}],"responses":{"200":{"description":"Productivity per assignee.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"lookbackDays":{"type":"integer"},"productivity":{"type":"array","items":{"$ref":"#/components/schemas/AssigneeProductivity"}}}},"example":{"success":true,"lookbackDays":90,"productivity":[{"userId":"u_71bXq","name":"Priya Shah","total":20,"completed":12,"completedOnTime":11,"completedLate":1,"pending":7,"overdue":1,"daysBeforeExpiry":[14,3,21],"onTimeRate":92,"avgDaysBeforeExpiry":13}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/markExpiryDone":{"post":{"operationId":"markExpiryDone","summary":"Mark an expiry done","description":"Requires: bearer token; role editor or admin.\nPass the id as query `id` or body `id` / `expiryId`. Sets `is_done`, `state: completed`, fires on_done workflows and,\nfor recurring expiries, creates the next occurrence (returned in `next_expiry`). Every required checklist item id\nmust be in `checklistCompleted`.\n","tags":["Expiries"],"parameters":[{"name":"id","in":"query","required":false,"description":"Expiry id (alternative to body `id` / `expiryId`).","schema":{"type":"string"},"example":"exp_4Tq9sLm2"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkExpiryDoneInput"},"example":{"expiryId":"exp_4Tq9sLm2","closingNotes":"Renewed with the State Board; certificate filed.","checklistCompleted":["chk_submit_form","chk_pay_fee"]}}}},"responses":{"200":{"description":"Marked done.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkExpiryDoneResult"},"example":{"message":"Expiry marked as done","next_expiry":{"next_expiry_id":"exp_8Wd3nQ1v","next_expiry_date":"2027-10-15","mode":"renew_as_copy","occurrence_number":2}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Missing id, or required checklist items not completed.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/ChecklistRequiredError"}]},"example":{"error":"Required checklist items must be completed","missing_required":["chk_pay_fee"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/unarchiveExpiry":{"post":{"operationId":"unarchiveExpiry","summary":"Unarchive an expiry","description":"Requires: bearer token; role editor or admin.\nSets `is_archive: false`, `is_done: false` and `state: todo`. Counts against the plan's expiry limit.\n","tags":["Expiries"],"requestBody":{"$ref":"#/components/requestBodies/ExpiryIdBody"},"responses":{"200":{"description":"Unarchived.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Expiry unarchived successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Role is viewer, or the plan's expiry limit is reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanLimitError"},"example":{"error":"You have reached the maximum number of expiries for your plan.","current":100,"limit":100,"remaining":0}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateExpiry":{"put":{"operationId":"updateExpiry_put","summary":"Update an expiry (PUT)","description":"Requires: bearer token; role editor or admin.\nIdentical to POST. Partial update despite the method.\n","tags":["Expiries"],"parameters":[{"$ref":"#/components/parameters/ExpiryIdQuery"}],"requestBody":{"$ref":"#/components/requestBodies/ExpiryUpdate"},"responses":{"200":{"$ref":"#/components/responses/ExpiryUpdatedOk"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"updateExpiry","summary":"Update an expiry","description":"Requires: bearer token; role editor or admin.\nPartial update: only the fields sent are changed. Server-owned fields (`organization_id`, `user_id`, `share_token`,\n`created_at`, recurrence and escalation bookkeeping) are ignored.\n","tags":["Expiries"],"parameters":[{"$ref":"#/components/parameters/ExpiryIdQuery"}],"requestBody":{"$ref":"#/components/requestBodies/ExpiryUpdate"},"responses":{"200":{"$ref":"#/components/responses/ExpiryUpdatedOk"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateExpiryState":{"post":{"operationId":"updateExpiryState","summary":"Change an expiry's workflow state","description":"Requires: bearer token; role editor or admin.\n`completed` also sets `is_done: true`, fires on-done workflows and, for recurring records, creates or rolls\nforward the next occurrence (`next_expiry`).\n","tags":["Expiries"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expiryId","state"],"properties":{"expiryId":{"type":"string"},"state":{"type":"string","enum":["todo","inprogress","onhold","inreview","completed"]}}},"example":{"expiryId":"exp_4Tq9sLm2","state":"completed"}}}},"responses":{"200":{"description":"State updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpiryStateUpdateResponse"},"example":{"message":"Expiry state updated successfully","state":"completed","next_expiry":{"next_expiry_id":"exp_7Pn2xQa8","next_expiry_date":"2027-10-15","mode":"renew_as_copy","occurrence_number":2}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/viewExpiry":{"get":{"operationId":"viewExpiry_get","summary":"View a shared expiry (GET)","description":"Public (no token); requires the expiry share token.\nWorks only for links without a password (password-protected links need POST).\n","tags":["Expiries"],"parameters":[{"$ref":"#/components/parameters/ShareTokenQuery"}],"responses":{"200":{"$ref":"#/components/responses/SharedExpiryOk"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/SharePasswordRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"viewExpiry","summary":"View a shared expiry","description":"Public (no token); requires the expiry share token, plus `password` when the link is password-protected.\nReturns display fields only. 403 when sharing is off; 401 with `requiresPassword: true` when a password is needed or wrong.\n","tags":["Expiries"],"parameters":[{"$ref":"#/components/parameters/ShareTokenQuery"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"share_token":{"type":"string","description":"Alternative to the query parameter."},"password":{"type":"string","writeOnly":true}}},"example":{"password":"renew2026"}}}},"responses":{"200":{"$ref":"#/components/responses/SharedExpiryOk"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/SharePasswordRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/addExpiryType":{"post":{"operationId":"addExpiryType","summary":"Add an expiry type (legacy store)","description":"Requires: bearer token; role editor or admin.\nLegacy: writes to a store the web app no longer reads and is not plan-limited. Use addExpiryTypeNew.\n","tags":["Expiry Types"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}},"example":{"name":"Business License"}}}},"responses":{"201":{"description":"Created. `id` is the slugified name.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpiryTypeCreatedResponse"},"example":{"message":"Expiry type added successfully","id":"business_license","name":"Business License"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"deprecated":true,"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/addExpiryTypeNew":{"post":{"operationId":"addExpiryTypeNew","summary":"Add an expiry type","description":"Requires: bearer token; role editor or admin.\nCounts against the plan's categories limit (400 with `limitReached: true`). Setting `email_template_id` requires the\ncustom email templates plan feature (402).\n","tags":["Expiry Types"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Must be unique in the organization (exact match)."},"email_template_id":{"type":["string","null"],"description":"Email template used for reminders of this type; null = organization default."}}},"example":{"name":"Business License","email_template_id":null}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpiryTypeCreatedResponse"},"example":{"message":"Expiry type added successfully","id":"et_5Gm1rT","name":"Business License","email_template_id":null}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Missing name, duplicate name, unknown email template, or plan categories limit reached (`limitReached: true`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PlanLimitError"},{"type":"object","properties":{"limitReached":{"type":"boolean"}}}]},"example":{"error":"You've reached your plan's limit of 10 categories. Upgrade your plan to add more.","current":10,"limit":10,"remaining":0,"limitReached":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/EmailTemplatePlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deleteExpiryType":{"post":{"operationId":"deleteExpiryType","summary":"Delete an expiry type","description":"Requires: bearer token; role editor or admin.\nIf expiries use the type, `option` decides what happens to them: `move` (to type `newTypeNameOrId`), `rename`\n(keep the type under the new name `newTypeNameOrId`) or `delete_all` (permanently delete those expiries).\n","tags":["Expiry Types"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["typeId"],"properties":{"typeId":{"type":"string"},"option":{"type":"string","enum":["move","rename","delete_all"],"description":"Required when the type is in use."},"newTypeNameOrId":{"type":"string","description":"Target type id for `move`, new name for `rename`."}}},"example":{"typeId":"et_5Gm1rT","option":"move","newTypeNameOrId":"et_2Kd9wB"}}}},"responses":{"200":{"description":"Deleted (or renamed). Exactly one of the count fields is present.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpiryTypeDeleteResult"},"example":{"message":"Moved 12 expiries to new type and deleted old type","moved_expiries":12}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getAllExpiryTypes":{"post":{"operationId":"getAllExpiryTypes","summary":"List distinct expiry type names (legacy)","description":"Requires: bearer token; any role.\nLegacy: distinct `type` values across the organization's expiries plus a legacy custom-type store. Unbounded.\nUse getExpiryTypesWithStats.\n","tags":["Expiry Types"],"responses":{"200":{"description":"Type names; `id` equals `name`.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ExpiryTypeName"}},"example":[{"id":"License","name":"License"},{"id":"Insurance","name":"Insurance"}]}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"deprecated":true,"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getExpiryTypes":{"post":{"operationId":"getExpiryTypes","summary":"Count expiries per type for a metric card","description":"Requires: bearer token; any role.\nReturns a map of expiry type name to count, filtered by the same metric as getPaginatedExpiries.\nRecords without a type count as `Other`.\n","tags":["Expiry Types"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"metricFilter":{"$ref":"#/components/schemas/ExpiryMetricFilter"},"selectedTeam":{"type":"string"},"futureDurationDays":{"oneOf":[{"type":"integer"},{"type":"string"}]}}},"example":{"metricFilter":"active","selectedTeam":"0"}}}},"responses":{"200":{"description":"Type name -> count.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpiryTypeCounts"},"example":{"License":12,"Insurance":7,"Other":2}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getExpiryTypesWithStats":{"get":{"operationId":"getExpiryTypesWithStats_get","summary":"List expiry types with usage counts (GET)","description":"Requires: bearer token; any role.\nSame as POST.\n","tags":["Expiry Types"],"responses":{"200":{"$ref":"#/components/responses/ExpiryTypesWithStatsOk"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getExpiryTypesWithStats","summary":"List expiry types with usage counts","description":"Requires: bearer token; any role.\nReturns every expiry type of the organization (unbounded) with its expiry count and email template override.\n","tags":["Expiry Types"],"responses":{"200":{"$ref":"#/components/responses/ExpiryTypesWithStatsOk"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateExpiryType":{"post":{"operationId":"updateExpiryType","summary":"Rename an expiry type or set its email template","description":"Requires: bearer token; role editor or admin.\nSend `newName` and/or `email_template_id` (null clears it). A rename also updates `type` on every expiry using it.\n","tags":["Expiry Types"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["typeId"],"properties":{"typeId":{"type":"string"},"newName":{"type":"string"},"email_template_id":{"type":["string","null"],"description":"Omit to leave unchanged; null or empty string resets to the organization default."}}},"example":{"typeId":"et_5Gm1rT","newName":"State Business License"}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpiryTypeUpdateResult"},"example":{"message":"Expiry type updated successfully","updated_expiries":12,"email_template_id":null}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/EmailTemplatePlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/createExpiryReminders":{"post":{"operationId":"createExpiryReminders","summary":"Add reminders to an expiry","description":"Requires: bearer token; role editor or admin.\nAppends new reminder records (does not replace existing ones - use updateExpiryReminders for that); max 20 per call.\nNot idempotent: a retry creates duplicates. Email, SMS and WhatsApp default to enabled unless set to false.\n","tags":["Reminders & Notifications"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expiry_id","expiry_date","reminders"],"properties":{"expiry_id":{"type":"string"},"expiry_date":{"$ref":"#/components/schemas/IsoDate"},"reminders":{"type":"array","maxItems":20,"items":{"$ref":"#/components/schemas/ReminderInput"}},"org_timezone":{"type":"string","description":"IANA timezone for reminder times. Falls back to the first reminder's `timezone`, then UTC."}}},"example":{"expiry_id":"exp_4Tq9sLm2","expiry_date":"2026-10-15","org_timezone":"America/Chicago","reminders":[{"amount":30,"time_unit":"day","period":"before","time":"09:00","email":true,"sms":false,"whatsapp":false},{"amount":1,"time_unit":"week","period":"before","time":"09:00"}]}}}},"responses":{"201":{"description":"Reminders created.","content":{"application/json":{"schema":{"type":"object","required":["message","reminders","count"],"properties":{"message":{"type":"string"},"reminders":{"type":"array","items":{"$ref":"#/components/schemas/Reminder"}},"count":{"type":"integer"}}},"example":{"message":"Expiry reminders created successfully","count":1,"reminders":[{"id":"rem_5Jd8wQ","expiry_id":"exp_4Tq9sLm2","organization_id":"org_northwind","user_id":"u_71bXq","amount":30,"time_unit":"day","period":"before","time":"09:00","timezone":"America/Chicago","reminder_date":"2026-09-15T00:00:00.000Z","scheduled_at":"2026-09-15T14:00:00.000Z","email_enabled":true,"sms_enabled":false,"whatsapp_enabled":false,"status":"pending","sent_at":null,"error_message":null,"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deleteExpiryReminders":{"post":{"operationId":"deleteExpiryReminders","summary":"Delete all reminders of an expiry","description":"Requires: bearer token; role editor or admin.\n`expiry_id` may be sent in the query or the body. The expiry itself is not changed.\n","tags":["Reminders & Notifications"],"parameters":[{"name":"expiry_id","in":"query","required":false,"schema":{"type":"string"},"example":"exp_4Tq9sLm2"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"expiry_id":{"type":"string"}}},"example":{"expiry_id":"exp_4Tq9sLm2"}}}},"responses":{"200":{"description":"Reminders deleted.","content":{"application/json":{"schema":{"type":"object","required":["message","deleted"],"properties":{"message":{"type":"string"},"deleted":{"type":"integer"}}},"example":{"message":"Expiry reminders deleted successfully","deleted":3}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deleteNotification":{"post":{"operationId":"deleteNotification","summary":"Delete one notification","description":"Requires: bearer token (Firebase ID token or session JWT); any role. Only the notification's recipient can delete it. The id is read from `?id=` or the JSON body (`notification_id` or `id`).","tags":["Reminders & Notifications"],"parameters":[{"$ref":"#/components/parameters/NotificationIdQuery"}],"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true,"message":"Notification deleted"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getEmailSendDetail":{"get":{"operationId":"getEmailSendDetail","summary":"Get one sent email with its events","description":"Requires: bearer token; any role in the send's organization. Returns the send row (as in getOrgEmailActivity) plus every event for that message, oldest first.","tags":["Reminders & Notifications"],"parameters":[{"name":"send_id","in":"query","required":true,"description":"The `id` of a row from getOrgEmailActivity.","schema":{"type":"string"},"example":"resend_msg_01J8ZK_exp_4Tq9sLm2"}],"responses":{"200":{"description":"Send detail.","content":{"application/json":{"schema":{"type":"object","required":["send","events"],"properties":{"send":{"$ref":"#/components/schemas/EmailSend"},"events":{"type":"array","items":{"$ref":"#/components/schemas/EmailActivityEvent"}}}},"example":{"send":{"id":"resend_msg_01J8ZK_exp_4Tq9sLm2","organization_id":"org_northwind","expiry_id":"exp_4Tq9sLm2","expiry_name":"Northwind Dental - State Dental License","recipient_email":"billing@contosolegal.com","recipient_display":"Contoso Legal Billing","provider":"resend","provider_message_id":"msg_01J8ZK","subject":"Reminder: State Dental License expires in 18 days","is_digest":false,"status":"bounced","status_updated_at":"2026-09-26T09:00:09.000Z","failure_reason":"Mailbox does not exist","created_at":"2026-09-26T09:00:04.512Z"},"events":[{"id":"evt_B4k1Zs","event_type":"bounced","timestamp":"2026-09-26T09:00:09.000Z","contact_email":"billing@contosolegal.com","contact_display":"Contoso Legal Billing","message_id":"msg_01J8ZK","subject":"Reminder: State Dental License expires in 18 days","clicked_url":null,"bounce_type":"hard","failure_reason":"Mailbox does not exist","reply_text":null,"provider":"resend"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/getExpiryActivity":{"get":{"operationId":"getExpiryActivity","summary":"Get the email activity timeline of an expiry","description":"Requires: bearer token; any role in the expiry's organization. Returns up to 300 most recent email events (sent, delivered, opened, clicked, bounced, ...) for the expiry, oldest first. No pagination.","tags":["Reminders & Notifications"],"parameters":[{"name":"expiry_id","in":"query","required":true,"schema":{"type":"string"},"example":"exp_4Tq9sLm2"}],"responses":{"200":{"description":"Activity timeline.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpiryEmailActivity"},"example":{"expiry_id":"exp_4Tq9sLm2","stats":{"sent_count":2,"delivered_count":2,"opened_count":1,"last_event_at":"2026-09-27T14:05:00.000Z"},"events":[{"id":"evt_R2m8Tq","event_type":"sent","timestamp":"2026-09-26T09:00:04.000Z","contact_email":"billing@contosolegal.com","contact_display":"Contoso Legal Billing","message_id":"msg_01J8ZK","subject":"Reminder: State Dental License expires in 18 days","clicked_url":null,"bounce_type":null,"failure_reason":null,"reply_text":null,"provider":"resend"},{"id":"evt_W7n3Ya","event_type":"opened","timestamp":"2026-09-27T14:05:00.000Z","contact_email":"billing@contosolegal.com","contact_display":"Contoso Legal Billing","message_id":"msg_01J8ZK","subject":"Reminder: State Dental License expires in 18 days","clicked_url":null,"bounce_type":null,"failure_reason":null,"reply_text":null,"provider":"resend"}],"contact_count":1}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/getExpiryReminders":{"get":{"operationId":"getExpiryReminders","summary":"List an expiry's reminders","description":"Requires: bearer token; any role.\nReturns every reminder record of the expiry, sorted by `scheduled_at` ascending (not paginated).\n","tags":["Reminders & Notifications"],"parameters":[{"name":"expiry_id","in":"query","required":true,"schema":{"type":"string"},"example":"exp_4Tq9sLm2"}],"responses":{"200":{"description":"Reminders.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReminderList"},"example":{"reminders":[{"id":"rem_5Jd8wQ","expiry_id":"exp_4Tq9sLm2","organization_id":"org_northwind","amount":30,"time_unit":"day","period":"before","time":"09:00","timezone":"America/Chicago","reminder_date":"2026-09-15T00:00:00.000Z","scheduled_at":"2026-09-15T14:00:00.000Z","email_enabled":true,"sms_enabled":false,"whatsapp_enabled":false,"status":"sent","sent_at":"2026-09-15T14:00:07.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getExpiryReminders_post","summary":"List an expiry's reminders (POST)","description":"Requires: bearer token; any role.\nSame as the GET form; `expiry_id` may be sent in the query or the body.\n","tags":["Reminders & Notifications"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"expiry_id":{"type":"string"}}},"example":{"expiry_id":"exp_4Tq9sLm2"}}}},"responses":{"200":{"description":"Reminders.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReminderList"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getExpiryReminderStatus":{"post":{"operationId":"getExpiryReminderStatus","summary":"Get pending-reminder status for expiries","description":"Requires: bearer token; any role.\nFor up to 200 expiry ids (extra ids are ignored), reports whether a reminder is queued and when the next one fires.\n","tags":["Reminders & Notifications"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expiry_ids"],"properties":{"expiry_ids":{"type":"array","maxItems":200,"items":{"type":"string"}}}},"example":{"expiry_ids":["exp_4Tq9sLm2","exp_9Rk3vWn1"]}}}},"responses":{"200":{"description":"Status keyed by expiry id. Empty object when no ids were sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpiryReminderStatusResponse"},"example":{"status":{"exp_4Tq9sLm2":{"has_pending_reminder":true,"pending_reminder_count":3,"next_reminder_at":"2026-10-08T13:00:00.000Z"},"exp_9Rk3vWn1":{"has_pending_reminder":false,"pending_reminder_count":0,"next_reminder_at":null}}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getOrgEmailActivity":{"get":{"operationId":"getOrgEmailActivity","summary":"List emails sent across your organization","description":"Requires: bearer token; any role. One row per reminder email sent in the caller's organization, newest first. `limit` defaults to 25, clamped to 1-100. Pass `nextCursor` back as `cursor` for the next page; it is null on the last page.","tags":["Reminders & Notifications"],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"cursor","in":"query","required":false,"description":"`nextCursor` from the previous page (epoch milliseconds).","schema":{"type":"integer","format":"int64"}},{"name":"status","in":"query","required":false,"description":"`all`, `issues` (bounced, complained or failed), or one exact status.","schema":{"type":"string","default":"all","enum":["all","issues","sent","delayed","delivered","opened","clicked","bounced","complained","failed"]}}],"responses":{"200":{"description":"One page of sends.","content":{"application/json":{"schema":{"type":"object","required":["items","nextCursor"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/EmailSend"}},"nextCursor":{"type":["integer","null"],"format":"int64"}}},"example":{"items":[{"id":"resend_msg_01J8ZK_exp_4Tq9sLm2","organization_id":"org_northwind","expiry_id":"exp_4Tq9sLm2","expiry_name":"Northwind Dental - State Dental License","reminder_id":"rem_5Pz1","recipient_email":"billing@contosolegal.com","recipient_display":"Contoso Legal Billing","provider":"resend","provider_message_id":"msg_01J8ZK","subject":"Reminder: State Dental License expires in 18 days","is_digest":false,"status":"opened","status_updated_at":"2026-09-27T14:05:00.000Z","sent_at":"2026-09-26T09:00:04.000Z","opened_at":"2026-09-27T14:05:00.000Z","created_at":"2026-09-26T09:00:04.512Z"}],"nextCursor":1790413204512}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/getUpcomingNotifications":{"post":{"operationId":"getUpcomingNotifications","summary":"List reminders due in the next 30 days (organization rules)","description":"Requires: bearer token; any role.\nProjects the next reminder per active expiry from the organization's reminder settings (default 1, 7 and 30 days\nbefore) within 30 days, sorted by time. Unbounded. getUpcomingNotificationsV2 reads the actual reminder queue.\n","tags":["Reminders & Notifications"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"selectedTeam":{"type":"string","description":"Team id; `'0'` or omitted = all teams."}}},"example":{"selectedTeam":"0"}}}},"responses":{"200":{"description":"Upcoming notifications, soonest first.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/LegacyUpcomingNotification"}},"example":[{"id":"exp_4Tq9sLm2","expiryName":"Northwind Dental - State Dental License","expiryType":"License","expiryDate":"2026-10-15","nextNotification":"2026-10-08T09:00:00.000Z","reminderTime":"09:00","timezone":"America/Chicago","daysUntilExpiry":18,"reminderDaysBefore":7,"reminderPeriod":"before","recipients":[{"name":"Priya Shah","email":"priya@northwinddental.com","channels":["email"]}],"notificationChannels":["email"],"priority":"high"}]}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getUpcomingNotificationsV2":{"post":{"operationId":"getUpcomingNotificationsV2","summary":"Upcoming reminder sends in the next 30 days","description":"Requires: bearer token; any role.\nOne entry per active (not done, not archived) expiry with a reminder scheduled in the next 30 days and at least\none reachable recipient, sorted by `nextNotification`. Returns a bare array; unbounded.\n","tags":["Reminders & Notifications"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"selectedTeam":{"type":"string","description":"Team id to filter by; `'0'` or omitted for all teams."}}},"example":{"selectedTeam":"0"}}}},"responses":{"200":{"description":"Upcoming notifications.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UpcomingNotification"}},"example":[{"id":"exp_4Tq9sLm2","expiryName":"Northwind Dental - State Dental License","expiryType":"License","expiryDate":"2026-10-15","nextNotification":"2026-10-01T14:00:00.000Z","nextNotificationOrgTz":"2026-10-01T09:00:00.000Z","reminderTime":"09:00","timezone":"America/Chicago","daysUntilExpiry":18,"recipients":[{"name":"Priya Shah","email":"priya.shah@northwinddental.com","channels":["email"]}],"notificationChannels":["email"],"priority":"medium"}]}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/markAllNotificationsRead":{"post":{"operationId":"markAllNotificationsRead","summary":"Mark all your notifications as read","description":"Requires: bearer token (Firebase ID token or session JWT); any role. Marks every unread notification of the caller as read. No body.","tags":["Reminders & Notifications"],"responses":{"200":{"description":"All unread notifications marked as read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true,"message":"3 notifications marked as read"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/markCommentDeleted":{"post":{"operationId":"markCommentDeleted","summary":"Flag notifications of a deleted comment","description":"Requires: bearer token (Firebase ID token or session JWT); any role.\nSets `comment_deleted: true` on every notification that references `comment_id`, so the UI can grey them out.\n","tags":["Reminders & Notifications"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["comment_id"],"properties":{"comment_id":{"type":"string"}}},"example":{"comment_id":"cmt_9Lx2Rf"}}}},"responses":{"200":{"description":"Notifications flagged.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true,"message":"Notifications marked as comment deleted"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/markNotificationRead":{"post":{"operationId":"markNotificationRead","summary":"Mark one notification as read","description":"Requires: bearer token (Firebase ID token or session JWT); any role. Only the notification's recipient can mark it. The id is read from `?id=` or the JSON body (`notification_id` or `id`).","tags":["Reminders & Notifications"],"parameters":[{"$ref":"#/components/parameters/NotificationIdQuery"}],"responses":{"200":{"description":"Marked as read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true,"message":"Notification marked as read"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/notifications":{"get":{"operationId":"notifications_get","summary":"List your in-app notifications","description":"Requires: bearer token (Firebase ID token or session JWT); any role. Returns the caller's own notifications (assignments and @mentions), newest first. `limit` defaults to 50 and is clamped to 1-200; no further pages. `unread_count` counts all unread notifications, not just the returned page.","tags":["Reminders & Notifications"],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"unread_only","in":"query","required":false,"description":"`true` to return only unread notifications.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"The caller's notifications.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationList"},"example":{"notifications":[{"id":"ntf_3Jd8wQ","user_id":"u_71bXq","organization_id":"org_northwind","type":"mention","item_type":"expiry","item_id":"exp_4Tq9sLm2","item_name":"Northwind Dental - State Dental License","message":"Priya Shah mentioned you in a comment","mentioned_by":{"id":"u_2kPz9","name":"Priya Shah","email":"priya@northwinddental.com"},"comment_text":"@Sam can you upload the renewed certificate?","link":"/dashboard/expiry/exp_4Tq9sLm2","read":false,"created_at":"2026-09-27T14:05:00.000Z"}],"unread_count":3,"total":1}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"post":{"operationId":"notifications_post","summary":"Create an in-app notification (mention)","description":"Requires: bearer token (Firebase ID token or session JWT); any role.\nNotifies a user in the caller's organization (used for @mentions in comments). `mentioned_by` is set from the caller's profile;\n`link` must be a relative app path (anything else is stored as empty). Text fields are truncated (message 500, comment_text 5000 chars).\n","tags":["Reminders & Notifications"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationInput"},"example":{"user_id":"u_71bXq","type":"mention","item_type":"expiry","item_id":"exp_4Tq9sLm2","item_name":"Northwind Dental - State Dental License","message":"Priya Shah mentioned you in a comment","comment_text":"@Sam can you upload the renewed certificate?","link":"/dashboard/expiry/exp_4Tq9sLm2"}}}},"responses":{"201":{"description":"Notification created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateNotificationResult"},"example":{"success":true,"notification_id":"ntf_3Jd8wQ","message":"Notification created successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/registerDeviceToken":{"post":{"operationId":"registerDeviceToken","summary":"Register a mobile device for push notifications","description":"Requires: bearer token; any role.\nUpserts one registration per (user, device_id), so re-registering on app relaunch does not create duplicates.\n","tags":["Reminders & Notifications"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["fcm_token","platform","device_id"],"properties":{"fcm_token":{"type":"string","description":"Firebase Cloud Messaging registration token."},"platform":{"type":"string","enum":["ios","android"]},"device_id":{"type":"string","description":"Stable per-install device identifier."},"app_version":{"type":"string"}}},"example":{"fcm_token":"dQw4w9WgXcQ:APA91bH-example-token","platform":"ios","device_id":"6F1C2B9A-8D3E-4F7A-9B21-0C5D7E8F9A10","app_version":"2.4.1"}}}},"responses":{"200":{"description":"Device registered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/sendExpiryNotificationNow":{"post":{"operationId":"sendExpiryNotificationNow","summary":"Send an expiry reminder email now","description":"Requires: bearer token; any role (admin, editor or viewer).\nEmails the expiry reminder immediately to up to 50 of the expiry's assigned contacts, outside the reminder schedule. Uses one email credit per recipient; per-contact outcomes are in `results`.","tags":["Reminders & Notifications"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expiryId","contactIds"],"properties":{"expiryId":{"type":"string","description":"Expiry in your organization."},"contactIds":{"type":"array","minItems":1,"maxItems":50,"items":{"type":"string"},"description":"Contact ids; each must be assigned to the expiry (400 otherwise). Duplicates are removed."}}},"example":{"expiryId":"exp_4Tq9sLm2","contactIds":["c_8Hk2pQ","c_2Wn7rT"]}}}},"responses":{"200":{"description":"Per-recipient results.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendNowResult"},"example":{"attempted":2,"succeeded":1,"failed":0,"skipped":1,"results":[{"contactId":"c_8Hk2pQ","email":"priya@northwinddental.example","status":"sent"},{"contactId":"c_2Wn7rT","email":"","status":"skipped","reason":"No email or email opt-out"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"409":{"$ref":"#/components/responses/IdempotencyInProgress"},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":30,"window":"15m","scope":"per IP, per endpoint, per server instance"}}},"/sendTestEmail":{"post":{"operationId":"sendTestEmail","summary":"Send a test reminder email","description":"Requires: bearer token (Firebase ID token or session JWT); role editor or admin.\nSends a sample reminder built from `expiryData` and deducts 1 email credit from the caller's organization (400 when none are left).\n","tags":["Reminders & Notifications"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"},"expiryData":{"$ref":"#/components/schemas/TestReminderExpiryData"}}},"example":{"email":"sam.lee@northwinddental.com","expiryData":{"name":"State Dental License","type":"License","expiryDate":"2026-10-15","priority":"High","daysRemaining":18}}}}},"responses":{"200":{"description":"Test email sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestNotificationResult"},"example":{"message":"Test email sent successfully. 1 email credit has been deducted.","details":{"recipient":"sam.lee@northwinddental.com","expiry":"State Dental License","daysLeft":18}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/sendTestSMS":{"post":{"operationId":"sendTestSMS","summary":"Send a test reminder SMS","description":"Requires: bearer token (Firebase ID token or session JWT); role editor or admin.\nSends a sample reminder SMS and deducts SMS credits from the caller's organization. Use E.164 phone numbers.\n","tags":["Reminders & Notifications"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["phoneNumber"],"properties":{"phoneNumber":{"type":"string","description":"E.164 number."},"expiryData":{"$ref":"#/components/schemas/TestReminderExpiryData"}}},"example":{"phoneNumber":"+15551234567","expiryData":{"name":"State Dental License","expiryDate":"2026-10-15","daysRemaining":18}}}}},"responses":{"200":{"description":"Test SMS sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestNotificationResult"},"example":{"message":"Test SMS sent successfully. 1 message credit has been deducted.","details":{"recipient":"+15551234567","expiry":"State Dental License","daysLeft":18,"sent":true}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/sendTestWhatsApp":{"post":{"operationId":"sendTestWhatsApp","summary":"Send a test WhatsApp reminder","description":"Requires: bearer token (Firebase ID token or session JWT); role editor or admin.\nSends a sample WhatsApp reminder and deducts credits from the caller's organization. Use E.164 phone numbers.\n","tags":["Reminders & Notifications"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["phoneNumber"],"properties":{"phoneNumber":{"type":"string","description":"E.164 number."},"expiryData":{"$ref":"#/components/schemas/TestReminderExpiryData"}}},"example":{"phoneNumber":"+15551234567","expiryData":{"name":"State Dental License","expiryDate":"2026-10-15","daysRemaining":18,"priority":"High"}}}}},"responses":{"200":{"description":"Test WhatsApp message sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestNotificationResult"},"example":{"message":"Test WhatsApp sent successfully. 1 message credit has been deducted.","details":{"recipient":"+15551234567","expiry":"State Dental License","daysLeft":18,"sent":true}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/syncOrganizationTimezone":{"post":{"operationId":"syncOrganizationTimezone","summary":"Reschedule pending reminders to a new timezone","description":"Requires: bearer token; role admin.\nRecomputes `scheduled_at` for every pending reminder in your organization using `new_timezone` (keeps each\nreminder's local `time`). Does not change the organization's timezone setting itself.\n","tags":["Reminders & Notifications"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["organization_id","new_timezone"],"properties":{"organization_id":{"type":"string","description":"Must be your own organization id."},"new_timezone":{"type":"string","description":"Valid IANA timezone."}}},"example":{"organization_id":"org_northwind","new_timezone":"America/Denver"}}}},"responses":{"200":{"description":"Reminders rescheduled.","content":{"application/json":{"schema":{"type":"object","required":["message","updated"],"properties":{"message":{"type":"string"},"updated":{"type":"integer"},"new_timezone":{"type":"string","description":"Omitted when there were no pending reminders."}}},"example":{"message":"Timezone synced successfully","updated":42,"new_timezone":"America/Denver"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/unregisterDeviceToken":{"post":{"operationId":"unregisterDeviceToken","summary":"Unregister a mobile device from push notifications","description":"Requires: bearer token; any role. Removes the caller's registration for `device_id` (no error if it did not exist).\n","tags":["Reminders & Notifications"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["device_id"],"properties":{"device_id":{"type":"string"}}},"example":{"device_id":"6F1C2B9A-8D3E-4F7A-9B21-0C5D7E8F9A10"}}}},"responses":{"200":{"description":"Device unregistered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/updateExpiryReminders":{"post":{"operationId":"updateExpiryReminders","summary":"Replace an expiry's reminder schedule","description":"Requires: bearer token; role editor or admin.\nUpserts `reminders` (matched by `firestore_id`, else by amount+time_unit+period); max 20. Without\n`deleted_reminder_ids`, existing reminders not in the list are deleted; with it, only those ids are deleted.\nReminders moved into the future are reset to `pending`.\n","tags":["Reminders & Notifications"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expiry_id","expiry_date"],"properties":{"expiry_id":{"type":"string"},"expiry_date":{"$ref":"#/components/schemas/IsoDate"},"reminders":{"type":"array","maxItems":20,"items":{"$ref":"#/components/schemas/ReminderInput"}},"deleted_reminder_ids":{"type":"array","items":{"type":"string"}},"org_timezone":{"type":"string"}}},"example":{"expiry_id":"exp_4Tq9sLm2","expiry_date":"2026-10-15","org_timezone":"America/Chicago","reminders":[{"firestore_id":"rem_5Jd8wQ","amount":14,"time_unit":"day","period":"before","time":"08:30","email":true,"sms":false,"whatsapp":false}]}}}},"responses":{"200":{"description":"Schedule updated.","content":{"application/json":{"schema":{"type":"object","required":["message","updated","created","deleted","reminders"],"properties":{"message":{"type":"string"},"updated":{"type":"integer"},"created":{"type":"integer"},"deleted":{"type":"integer"},"reminders":{"type":"array","description":"The upserted reminders (summary fields only).","items":{"$ref":"#/components/schemas/Reminder"}}}},"example":{"message":"Expiry reminders updated successfully","updated":1,"created":0,"deleted":0,"reminders":[{"id":"rem_5Jd8wQ","amount":14,"time_unit":"day","period":"before","time":"08:30","timezone":"America/Chicago","scheduled_at":"2026-10-01T13:30:00.000Z","status":"pending","email_enabled":true,"sms_enabled":false,"whatsapp_enabled":false}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getRenewalHistory":{"post":{"operationId":"getRenewalHistory","summary":"Get an expiry's renewal history","description":"Requires: bearer token; any role in the expiry's organization.\nReturns the renewals recorded on this expiry (the original of a recurring series). Unbounded.\n","tags":["Recurrence"],"parameters":[{"$ref":"#/components/parameters/ExpiryIdQuery"}],"responses":{"200":{"description":"Renewal history entries, oldest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenewalHistoryResponse"},"example":{"data":[{"from_expiry_id":"exp_4Tq9sLm2","to_expiry_id":"exp_7Pn2xQa8","occurrence_number":2,"renewed_at":"2026-10-15T09:00:12.000Z","renewal_type":"automatic","renewal_mode":"renew_as_copy","previous_date":"2026-10-15","new_date":"2027-10-15","timestamp":"2026-10-15T09:00:12.000Z"}],"count":1}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateRecurrenceSettings":{"post":{"operationId":"updateRecurrenceSettings","summary":"Update an expiry's recurrence settings","description":"Requires: bearer token; role editor or admin.\nReplaces all recurrence fields. With `is_recurring: false` every recurrence field is cleared.\n","tags":["Recurrence"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecurrenceSettingsInput"},"example":{"expiryId":"exp_4Tq9sLm2","is_recurring":true,"recurrence_type":"year","recurrence_interval":1,"recurrence_count":null,"recurrence_end_date":null,"recurrence_mode":"renew_as_copy"}}}},"responses":{"200":{"description":"Updated; `settings` echoes what was stored.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecurrenceSettingsResponse"},"example":{"message":"Recurrence settings updated successfully","settings":{"is_recurring":true,"recurrence_type":"year","recurrence_interval":1,"recurrence_count":null,"recurrence_end_date":null,"recurrence_mode":"renew_as_copy","updated_at":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Missing `expiryId` or invalid recurrence configuration (`details` lists the problems).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Invalid recurring configuration","details":["Invalid recurrence type. Must be one of: day, week, month, year"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/bulkArchiveExpiries":{"post":{"operationId":"bulkArchiveExpiries","summary":"Archive many expiries","description":"Requires: bearer token; role editor or admin.\nSets `is_archive: true` and pauses workflow attachments. Missing or foreign ids are reported in `errors`.\nKeep batches at or below 500 ids.\n","tags":["Bulk Operations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkExpiryIdsInput"},"example":{"expiryIds":["exp_4Tq9sLm2","exp_7Hn2kPq9"]}}}},"responses":{"200":{"description":"Result per batch.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/BulkOperationResult"},{"type":"object","required":["archived_count","archived_items"],"properties":{"archived_count":{"type":"integer"},"archived_items":{"type":"array","items":{"$ref":"#/components/schemas/BulkAffectedExpiry"}}}}]},"example":{"success":true,"archived_count":2,"archived_items":[{"id":"exp_4Tq9sLm2","name":"Northwind Dental - State Dental License"},{"id":"exp_7Hn2kPq9","name":"Northwind Dental - Malpractice Insurance"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/bulkChangeExpiryType":{"post":{"operationId":"bulkChangeExpiryType","summary":"Change the type of many expiries","description":"Requires: bearer token; role editor or admin.\n`type` must be an existing expiry type name in your organization (400 otherwise). Items already of that type\ncount as skipped. Keep batches at or below 500 ids.\n","tags":["Bulk Operations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expiryIds","type"],"properties":{"expiryIds":{"type":"array","minItems":1,"items":{"type":"string"}},"type":{"type":"string","description":"Expiry type name."}}},"example":{"expiryIds":["exp_4Tq9sLm2","exp_7Hn2kPq9"],"type":"License"}}}},"responses":{"200":{"description":"Result per batch.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/BulkOperationResult"},{"type":"object","required":["updated_count","skipped_count","updated_items"],"properties":{"updated_count":{"type":"integer"},"skipped_count":{"type":"integer"},"updated_items":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/BulkAffectedExpiry"},{"type":"object","properties":{"previous_type":{"type":["string","null"]}}}]}}}}]},"example":{"success":true,"updated_count":1,"skipped_count":1,"updated_items":[{"id":"exp_7Hn2kPq9","name":"Northwind Dental - Malpractice Insurance","previous_type":"Insurance"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/bulkDeleteExpiries":{"post":{"operationId":"bulkDeleteExpiries","summary":"Delete many expiries","description":"Requires: bearer token; role editor or admin.\nAlso deletes the expiries' reminders and cancels workflow attachments. Ids that are missing or outside your\norganization are reported in `errors` and skipped. Keep batches at or below 500 ids (single Firestore batch).\n","tags":["Bulk Operations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkExpiryIdsInput"},"example":{"expiryIds":["exp_4Tq9sLm2","exp_7Hn2kPq9"]}}}},"responses":{"200":{"description":"Result per batch.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/BulkOperationResult"},{"type":"object","required":["deleted_count","deleted_items"],"properties":{"deleted_count":{"type":"integer"},"deleted_items":{"type":"array","items":{"$ref":"#/components/schemas/BulkAffectedExpiry"}}}}]},"example":{"success":true,"deleted_count":1,"deleted_items":[{"id":"exp_4Tq9sLm2","name":"Northwind Dental - State Dental License"}],"errors":[{"id":"exp_7Hn2kPq9","error":"Expiry not found"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/bulkImportExpiries":{"post":{"operationId":"bulkImportExpiries","summary":"Import expiries from spreadsheet rows","description":"Requires: bearer token; role editor or admin.\nAll-or-nothing, max 5000 rows. Rows with errors abort the whole import with `200` and `aborted: true`. An identical\npayload re-sent later replays the first result with `deduped: true`; 409 while it is still running.\n","tags":["Bulk Operations"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkImportExpiriesRequest"},"example":{"records":[{"name":"Northwind Dental - State Dental License","type":"License","expiry_date":"2026-10-15","priority":"High","notification_email":"priya@northwinddental.com"},{"name":"Northwind Dental - Radiation Equipment Registration","type":"Registration","expiry_date":"2027-01-31","value":"450","currency":"USD"}]}}}},"responses":{"200":{"description":"Import result. Check `aborted`; when true nothing was written and `errors` lists every bad row.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkImportExpiriesResult"},"example":{"message":"2 expiries imported successfully","successCount":2,"errors":[],"warnings":[],"notificationWarnings":[],"missingContactEmails":[],"assignedTeamId":"team_3Fh8"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"400":{"$ref":"#/components/responses/BulkImportExpiriesError"},"401":{"$ref":"#/components/responses/BulkImportExpiriesError"},"403":{"description":"Role is viewer, or the import would exceed the plan's expiry limit (`code: LIMIT_REACHED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkImportExpiriesFailure"},"example":{"error":"This import would exceed your plan limit: 120 record(s) to import, 40 remaining. Upgrade your plan or import fewer rows.","code":"LIMIT_REACHED","limitReached":true,"limit":100,"current":60,"remaining":40,"successCount":0,"errors":["This import would exceed your plan limit: 120 record(s) to import, 40 remaining. Upgrade your plan or import fewer rows."]}}}},"409":{"description":"The same payload is already being imported by an earlier request. Wait; do not resubmit.; or a request with the same Idempotency-Key is still being processed (code IDEMPOTENCY_IN_PROGRESS).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"More than 5000 rows (`code: TOO_MANY_ROWS`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkImportExpiriesFailure"},"example":{"error":"Too many rows (6200). Import at most 5000 rows per file - split larger files and import them separately.","code":"TOO_MANY_ROWS","maxRows":5000,"successCount":0,"errors":["Too many rows (6200). Import at most 5000 rows per file - split larger files and import them separately."]}}}},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/BulkImportExpiriesError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/bulkMarkExpiriesDone":{"post":{"operationId":"bulkMarkExpiriesDone","summary":"Mark many expiries done","description":"Requires: bearer token; role editor or admin.\nFires on_done workflows and creates next occurrences for recurring items. Items with incomplete required\nchecklist items are reported in `errors` (with `missing_required`). Keep batches at or below 500 ids.\n","tags":["Bulk Operations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expiryIds"],"properties":{"expiryIds":{"type":"array","minItems":1,"items":{"type":"string"}},"closingNotes":{"type":"string","description":"Stored as `closing_notes` on every item."},"checklistCompletedById":{"type":"object","description":"Expiry id -> array of completed checklist item ids.","additionalProperties":{"type":"array","items":{"type":"string"}}},"checklistCommentsById":{"type":"object","description":"Expiry id -> {checklist item id -> comment}.","additionalProperties":{"type":"object","additionalProperties":{"type":"string"}}}}},"example":{"expiryIds":["exp_4Tq9sLm2","exp_7Hn2kPq9"],"closingNotes":"Renewed for 2027.","checklistCompletedById":{"exp_4Tq9sLm2":["chk_submit_form","chk_pay_fee"]}}}}},"responses":{"200":{"description":"Result per batch.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/BulkOperationResult"},{"type":"object","required":["completed_count","completed_items"],"properties":{"completed_count":{"type":"integer"},"completed_items":{"type":"array","items":{"$ref":"#/components/schemas/BulkAffectedExpiry"}}}}]},"example":{"success":true,"completed_count":1,"completed_items":[{"id":"exp_4Tq9sLm2","name":"Northwind Dental - State Dental License"}],"errors":[{"id":"exp_7Hn2kPq9","error":"Required checklist items must be completed","missing_required":["chk_upload_certificate"]}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/bulkMoveExpiriesToTeam":{"post":{"operationId":"bulkMoveExpiriesToTeam","summary":"Move many expiries to a team","description":"Requires: bearer token; role editor or admin.\n`teamId` must be a team in your organization; `null` (or the string `'null'`) removes the team.\nMissing or foreign expiry ids are reported in `errors`. Keep batches at or below 500 ids.\n","tags":["Bulk Operations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expiryIds","teamId"],"properties":{"expiryIds":{"type":"array","minItems":1,"items":{"type":"string"}},"teamId":{"type":["string","null"]}}},"example":{"expiryIds":["exp_4Tq9sLm2","exp_7Hn2kPq9"],"teamId":"team_front_desk"}}}},"responses":{"200":{"description":"Result per batch.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/BulkOperationResult"},{"type":"object","required":["moved_count","moved_items"],"properties":{"moved_count":{"type":"integer"},"moved_items":{"type":"array","items":{"$ref":"#/components/schemas/BulkAffectedExpiry"}}}}]},"example":{"success":true,"moved_count":2,"moved_items":[{"id":"exp_4Tq9sLm2","name":"Northwind Dental - State Dental License"},{"id":"exp_7Hn2kPq9","name":"Northwind Dental - Malpractice Insurance"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/bulkReassignExpiries":{"post":{"operationId":"bulkReassignExpiries","summary":"Reassign many expiries to another member","description":"Requires: bearer token; role editor or admin.\n`toUserId` (and `fromUserId` when given) must be in your organization. With `fromUserId`, only items currently\nassigned to that user are changed; others count as skipped. Keep batches at or below 500 ids.\n","tags":["Bulk Operations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expiryIds","toUserId"],"properties":{"expiryIds":{"type":"array","minItems":1,"items":{"type":"string"}},"toUserId":{"type":"string"},"fromUserId":{"type":"string"}}},"example":{"expiryIds":["exp_4Tq9sLm2","exp_7Hn2kPq9"],"toUserId":"u_9Pq2Lm","fromUserId":"u_71bXq"}}}},"responses":{"200":{"description":"Result per batch.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/BulkOperationResult"},{"type":"object","required":["reassigned_count","skipped_count","reassigned_items"],"properties":{"reassigned_count":{"type":"integer"},"skipped_count":{"type":"integer"},"reassigned_items":{"type":"array","items":{"$ref":"#/components/schemas/BulkAffectedExpiry"}}}}]},"example":{"success":true,"reassigned_count":1,"skipped_count":1,"reassigned_items":[{"id":"exp_4Tq9sLm2","name":"Northwind Dental - State Dental License"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/bulkUpdateEscalationSettings":{"post":{"operationId":"bulkUpdateEscalationSettings","summary":"Enable or disable escalation on many expiries","description":"Requires: bearer token; role editor or admin.\n`enable: true` turns escalation on and fills `escalation_notification_days` from the org default only where empty;\n`enable: false` only turns it off (contacts and timing are kept). `no_contacts_count` (enable only) counts items\nwith no escalation contacts. Processed in chunks of 400 ids.\n","tags":["Bulk Operations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expiryIds","enable"],"properties":{"expiryIds":{"type":"array","minItems":1,"items":{"type":"string"}},"enable":{"type":"boolean"}}},"example":{"expiryIds":["exp_4Tq9sLm2","exp_7Hn2kPq9"],"enable":true}}}},"responses":{"200":{"description":"Result per batch.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/BulkOperationResult"},{"type":"object","required":["updated_count"],"properties":{"updated_count":{"type":"integer"},"no_contacts_count":{"type":"integer","description":"Present only when `enable` is true."}}}]},"example":{"success":true,"updated_count":2,"no_contacts_count":1}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/bulkUpdateNotificationSettings":{"post":{"operationId":"bulkUpdateNotificationSettings","summary":"Replace contacts and/or reminder schedules on many expiries","description":"Requires: bearer token; role editor or admin.\nWith `updateContacts`, each expiry's `contacts` is replaced by `contactIds` (ids outside your organization are\ndropped; max 500). With `updateReminders`, each expiry's reminders are deleted and recreated from `reminders`\n(max 20). Processed in chunks of 400 ids.\n","tags":["Bulk Operations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expiryIds"],"properties":{"expiryIds":{"type":"array","minItems":1,"items":{"type":"string"}},"updateContacts":{"type":"boolean","description":"At least one of updateContacts / updateReminders must be true."},"contactIds":{"type":"array","maxItems":500,"items":{"type":"string"}},"updateReminders":{"type":"boolean"},"reminders":{"type":"array","maxItems":20,"items":{"$ref":"#/components/schemas/ReminderInput"}},"orgTimezone":{"type":"string","description":"IANA timezone used when a reminder has no `timezone`. Defaults to UTC."}}},"example":{"expiryIds":["exp_4Tq9sLm2","exp_7Hn2kPq9"],"updateContacts":true,"contactIds":["c_8Hk2pQ","c_3Rt7wX"],"updateReminders":true,"reminders":[{"amount":30,"time_unit":"day","period":"before","time":"09:00","email":true,"sms":false,"whatsapp":false}],"orgTimezone":"America/Chicago"}}}},"responses":{"200":{"description":"Result per batch.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/BulkOperationResult"},{"type":"object","required":["updated_count"],"properties":{"updated_count":{"type":"integer"}}}]},"example":{"success":true,"updated_count":2}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/bulkDeleteContacts":{"post":{"operationId":"bulkDeleteContacts","summary":"Delete many contacts","description":"Requires: bearer token; role editor or admin.\nDeletes every id in `contactIds` in one batch (keep it under 500 ids). Missing or other-organization ids\nare reported in `errors`; the rest are deleted.\n","tags":["Contacts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["contactIds"],"properties":{"contactIds":{"type":"array","minItems":1,"items":{"type":"string"}}}},"example":{"contactIds":["c_8Hk2pQ","c_3Rm7vN"]}}}},"responses":{"200":{"description":"Contacts deleted. `errors` is present only when some ids failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkDeleteContactsResult"},"example":{"success":true,"deleted_count":1,"deleted_ids":["c_8Hk2pQ"],"errors":[{"id":"c_3Rm7vN","error":"Not found"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/bulkImportContacts":{"post":{"operationId":"bulkImportContacts","summary":"Import contacts from a spreadsheet","description":"Requires: bearer token; role editor or admin.\nUp to 1000 rows per request (`contacts`, or `records`). Each row needs first name, last name and a valid email;\nheader spellings such as `first_name`, `Email Address`, `phone`, `group` are accepted. Duplicate emails are\nskipped, rows beyond the plan's remaining contact slots are skipped (`skippedLimit`), new groups are created.\n","tags":["Contacts"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkImportContactsInput"},"example":{"consent":true,"contacts":[{"firstName":"Priya","lastName":"Shah","email":"priya.shah@northwinddental.example","smsPhone":"+15550123456","contactGroup":"Licensing"},{"first_name":"Marco","last_name":"Diaz","Email Address":"marco.diaz@contosolegal.example","enable_notification":"no"}]}}}},"responses":{"200":{"description":"Import finished (possibly partially). Row problems are listed in `errors`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkImportContactsResult"},"example":{"success":true,"created":2,"skippedDuplicates":0,"skippedLimit":0,"totalRows":2,"limit":250,"remaining":180,"errors":[]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"400":{"description":"No rows, more than 1000 rows, or the caller has no organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkImportContactsResult"},"example":{"error":"Too many rows. A single import is limited to 1000 contacts. Please split your file.","created":0,"errors":["Received 1200 rows; the limit is 1000."]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Role is not editor/admin (`{error}`), or the subscription does not allow adding contacts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkImportContactsResult"},"example":{"error":"Your subscription does not allow adding contacts right now.","created":0,"skippedDuplicates":0,"skippedLimit":2,"current":50,"limit":50,"remaining":0,"errors":[]}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/IdempotencyInProgress"},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/bulkUpdateContacts":{"post":{"operationId":"bulkUpdateContacts","summary":"Apply the same changes to many contacts","description":"Requires: bearer token; role editor or admin.\nApplies `updates` (only `contactGroup`, `jobTitle`, `contactType`, `sendNotifications`) to every id in\n`contactIds` in one batch (keep it under 500 ids). Missing or other-organization ids are reported in `errors`.\n","tags":["Contacts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkUpdateContactsInput"},"example":{"contactIds":["c_8Hk2pQ","c_3Rm7vN"],"updates":{"contactGroup":"Vendors","sendNotifications":true}}}}},"responses":{"200":{"description":"Contacts updated. `errors` is present only when some ids failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkUpdateContactsResult"},"example":{"success":true,"updated_count":2,"updated_ids":["c_8Hk2pQ","c_3Rm7vN"]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/bulkUpdateDefaultContacts":{"post":{"operationId":"bulkUpdateDefaultContacts","summary":"Set default-contact and opt-in flags on many contacts","description":"Requires: bearer token; role editor or admin.\nBody is a JSON array (max 1000 items) of `{id, is_default?, email_opt_in?, sms_opt_in?, whatsapp_opt_in?}`.\nOnly these boolean flags are written; when `is_default` is omitted it is set to `true`. Items without an id,\nor whose contact is missing or in another organization, are returned in `skipped`.\n","tags":["Contacts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","maxItems":1000,"items":{"$ref":"#/components/schemas/DefaultContactFlags"}},"example":[{"id":"c_8Hk2pQ","is_default":true},{"id":"c_3Rm7vN","is_default":false,"sms_opt_in":false}]}}},"responses":{"200":{"description":"Flags updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkUpdateDefaultContactsResult"},"example":{"message":"2 contacts updated successfully","updated":2,"skipped":[]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/createContact":{"post":{"operationId":"createContact","summary":"Create a contact","description":"Requires: bearer token; role editor or admin.\nEmails must be unique in the organization (case-insensitive, 409 `EMAIL_EXISTS`). Counts toward the\nplan's contact limit (403 with `current`/`limit`/`remaining` when reached). A new `contactGroup` name\ncreates the group. Send `lastName` (use `''` if unknown).\n","tags":["Contacts"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactInput"},"example":{"firstName":"Priya","lastName":"Shah","email":"priya.shah@northwinddental.example","smsPhone":"+15550123456","jobTitle":"Practice Manager","contactType":"Staff","contactGroup":"Licensing","timezone":"America/New_York","sendNotifications":true,"email_opt_in":true,"sms_opt_in":true}}}},"responses":{"201":{"description":"Contact created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactSavedResponse"},"example":{"message":"Contact added successfully","contact":{"id":"c_8Hk2pQ","user_id":"u_71bXq","organization_id":"org_northwind","firstName":"Priya","lastName":"Shah","email":"priya.shah@northwinddental.example","smsPhone":"+15550123456","whatsappPhone":null,"contactType":"Staff","jobTitle":"Practice Manager","timezone":"America/New_York","contactGroup":"Licensing","is_default":false,"email_opt_in":true,"sms_opt_in":true,"whatsapp_opt_in":false,"sendNotifications":true,"createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Role is not editor/admin (`{error}`), or the plan's contact limit is reached (`PlanLimitError`).","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/PlanLimitError"},{"$ref":"#/components/schemas/Error"}]},"example":{"error":"You have reached the maximum number of contacts for your plan.","current":50,"limit":50,"remaining":0}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"A contact with this email already exists in your organization.; or a request with the same Idempotency-Key is still being processed (code IDEMPOTENCY_IN_PROGRESS).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/createContactGroup":{"post":{"operationId":"createContactGroup","summary":"Create a contact group","description":"Requires: bearer token; role editor or admin.\nNames are unique per organization (case-insensitive, 409 `GROUP_EXISTS`). `color` defaults to a color\nderived from the name; `sortOrder` defaults to the end of the list.\n","tags":["Contacts"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactGroupInput"},"example":{"name":"Vendors","color":"#2e7d32"}}}},"responses":{"201":{"description":"Group created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactGroupSavedResponse"},"example":{"message":"Group created","group":{"id":"grp_7Qw2","organization_id":"org_northwind","name":"Vendors","color":"#2e7d32","sortOrder":3,"createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"A group with that name already exists.; or a request with the same Idempotency-Key is still being processed (code IDEMPOTENCY_IN_PROGRESS).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deleteContact":{"post":{"operationId":"deleteContact","summary":"Delete a contact","description":"Requires: bearer token; role editor or admin.\nPermanently deletes the contact and frees one slot of the plan's contact limit. The id may be sent as\nthe `id` query parameter or as `contactId` in the body.\n","tags":["Contacts"],"parameters":[{"name":"id","in":"query","required":false,"description":"Contact id (alternative to body `contactId`).","schema":{"type":"string"},"example":"c_8Hk2pQ"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"contactId":{"type":"string","description":"Contact id (used when `id` is not in the query)."}}},"example":{"contactId":"c_8Hk2pQ"}}}},"responses":{"200":{"description":"Contact deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Contact deleted successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"delete":{"operationId":"deleteContact_delete","summary":"Delete a contact (DELETE)","description":"Requires: bearer token; role editor or admin.\nSame as `POST /deleteContact` with the id in the `id` query parameter.\n","tags":["Contacts"],"parameters":[{"name":"id","in":"query","required":true,"description":"Contact id.","schema":{"type":"string"},"example":"c_8Hk2pQ"}],"responses":{"200":{"description":"Contact deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Contact deleted successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deleteContactGroup":{"post":{"operationId":"deleteContactGroup","summary":"Delete a contact group","description":"Requires: bearer token; role editor or admin.\nId from the `id` query parameter (or body `id`). Member contacts are kept and become ungrouped.\n","tags":["Contacts"],"parameters":[{"name":"id","in":"query","required":false,"description":"Group id (alternative to body `id`).","schema":{"type":"string"},"example":"grp_7Qw2"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"}}},"example":{}}}},"responses":{"200":{"description":"Group deleted.","content":{"application/json":{"schema":{"type":"object","required":["message","membersUpdated"],"properties":{"message":{"type":"string"},"membersUpdated":{"type":"integer","description":"Contacts that were moved to ungrouped."}}},"example":{"message":"Group deleted","membersUpdated":3}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getAllContactGroups":{"get":{"operationId":"getAllContactGroups","summary":"List contact groups","description":"Requires: bearer token; any role (admin, editor or viewer).\nReturns every group in the organization with its member count, sorted by `sortOrder` then name, plus\n`totalContacts` and `ungroupedCount`. On the first call for an organization, groups are created from the\ngroup names already on contacts. Unbounded.\n","tags":["Contacts"],"responses":{"200":{"description":"Groups with counts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactGroupList"},"example":{"groups":[{"id":"grp_5Nc1","organization_id":"org_northwind","name":"Licensing","color":"#1976d2","sortOrder":0,"count":4,"createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}],"totalContacts":9,"ungroupedCount":5}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getAllContactGroups_post","summary":"List contact groups (POST)","description":"Requires: bearer token; any role (admin, editor or viewer).\nSame as `GET /getAllContactGroups`; the body is ignored. Unbounded.\n","tags":["Contacts"],"responses":{"200":{"description":"Groups with counts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactGroupList"},"example":{"groups":[{"id":"grp_5Nc1","organization_id":"org_northwind","name":"Licensing","color":"#1976d2","sortOrder":0,"count":4,"createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}],"totalContacts":9,"ungroupedCount":5}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getAllContacts":{"get":{"operationId":"getAllContacts","summary":"List contacts","description":"Requires: bearer token; any role (admin, editor or viewer).\nReturns the organization's contacts as a bare array (camelCase fields). No pagination or ordering:\n`limit` defaults to 5000 and no maximum is enforced. Filters are equality-only.\n","tags":["Contacts"],"parameters":[{"name":"selectedTeam","in":"query","required":false,"description":"Only contacts with this `team_id`. `0` or omitted means all teams.","schema":{"type":"string"},"example":"0"},{"name":"contactGroup","in":"query","required":false,"description":"Only contacts in this group (group name). Ignored when `ungrouped` is true.","schema":{"type":"string"},"example":"Vendors"},{"name":"contactType","in":"query","required":false,"description":"Only contacts with this `contactType`.","schema":{"type":"string"},"example":"Client"},{"name":"ungrouped","in":"query","required":false,"description":"When `true`, only contacts with no group.","schema":{"type":"boolean"},"example":false},{"name":"limit","in":"query","required":false,"description":"Maximum contacts returned. Default 5000; not clamped.","schema":{"type":"integer","minimum":1,"default":5000},"example":500}],"responses":{"200":{"description":"Array of contacts (empty array when none match).","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}},"example":[{"id":"c_8Hk2pQ","user_id":"u_71bXq","organization_id":"org_northwind","firstName":"Priya","lastName":"Shah","email":"priya.shah@northwinddental.example","smsPhone":"+15550123456","whatsappPhone":null,"contactType":"Staff","jobTitle":"Practice Manager","timezone":"America/New_York","contactGroup":"Licensing","is_default":true,"email_opt_in":true,"sms_opt_in":true,"whatsapp_opt_in":false,"sendNotifications":true,"createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}]}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getAllContacts_post","summary":"List contacts (filters in body)","description":"Requires: bearer token; any role (admin, editor or viewer).\nSame as `GET /getAllContacts` with the filters in the JSON body (used by the contacts dashboard).\nReturns all matching contacts up to `limit` (default 5000, not clamped); no ordering.\n","tags":["Contacts"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactListFilters"},"example":{"contactGroup":"Licensing","limit":500}}}},"responses":{"200":{"description":"Array of contacts (empty array when none match).","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}},"example":[{"id":"c_8Hk2pQ","organization_id":"org_northwind","firstName":"Priya","lastName":"Shah","email":"priya.shah@northwinddental.example","contactGroup":"Licensing","is_default":true,"sendNotifications":true,"createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}]}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getContact":{"get":{"operationId":"getContact","summary":"Get a contact","description":"Requires: bearer token; any role (admin, editor or viewer).\nReturns the contact as a flat camelCase object (not wrapped). A contact in another organization returns 403.\n","tags":["Contacts"],"parameters":[{"name":"id","in":"query","required":true,"description":"Contact id.","schema":{"type":"string"},"example":"c_8Hk2pQ"}],"responses":{"200":{"description":"The contact.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"},"example":{"id":"c_8Hk2pQ","user_id":"u_71bXq","organization_id":"org_northwind","firstName":"Priya","lastName":"Shah","email":"priya.shah@northwinddental.example","smsPhone":"+15550123456","whatsappPhone":null,"contactType":"Staff","jobTitle":"Practice Manager","timezone":"America/New_York","contactGroup":"Licensing","is_default":true,"email_opt_in":true,"sms_opt_in":true,"whatsapp_opt_in":false,"sendNotifications":true,"createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getContact_post","summary":"Get a contact (POST)","description":"Requires: bearer token; any role (admin, editor or viewer).\nSame as `GET /getContact`; the id is still read from the `id` query parameter, not the body.\n","tags":["Contacts"],"parameters":[{"name":"id","in":"query","required":true,"description":"Contact id.","schema":{"type":"string"},"example":"c_8Hk2pQ"}],"responses":{"200":{"description":"The contact.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"},"example":{"id":"c_8Hk2pQ","organization_id":"org_northwind","firstName":"Priya","lastName":"Shah","email":"priya.shah@northwinddental.example","sendNotifications":true,"createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getContactExpiryCounts":{"post":{"operationId":"getContactExpiryCounts","summary":"Count expiries linked to contacts","description":"Requires: bearer token; any role (admin, editor or viewer).\nFor each contact id (first 100 used), counts the expiries whose `contacts` list contains it. `contactIds` may be\nan array or a comma-separated string; a GET with `?contactIds=a,b` also works. Empty input returns `{counts: {}}`.\n","tags":["Contacts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"contactIds":{"oneOf":[{"type":"array","maxItems":100,"items":{"type":"string"}},{"type":"string","description":"Comma-separated ids."}]}}},"example":{"contactIds":["c_8Hk2pQ","c_3Rm7vN"]}}}},"responses":{"200":{"description":"Map of contact id to linked expiry count.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactExpiryCounts"},"example":{"counts":{"c_8Hk2pQ":12,"c_3Rm7vN":0}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateContact":{"put":{"operationId":"updateContact_put","summary":"Update a contact (PUT)","description":"Requires: bearer token; role editor or admin.\nSame as `POST /updateContact`. Partial update; id in the `id` query parameter.\n","tags":["Contacts"],"parameters":[{"name":"id","in":"query","required":true,"description":"Contact id.","schema":{"type":"string"},"example":"c_8Hk2pQ"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactInput"},"example":{"email":"p.shah@northwinddental.example","sendNotifications":true}}}},"responses":{"200":{"description":"Contact updated; returns the stored contact.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactSavedResponse"},"example":{"message":"Contact updated successfully","contact":{"id":"c_8Hk2pQ","organization_id":"org_northwind","firstName":"Priya","lastName":"Shah","email":"p.shah@northwinddental.example","sendNotifications":true,"createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-28T09:12:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"updateContact","summary":"Update a contact","description":"Requires: bearer token; role editor or admin.\nPartial update: only the `ContactInput` fields present in the body change (other keys are ignored).\nThe id comes from the `id` query parameter. Moving a contact into a new group name creates the group.\n","tags":["Contacts"],"parameters":[{"name":"id","in":"query","required":true,"description":"Contact id.","schema":{"type":"string"},"example":"c_8Hk2pQ"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactInput"},"example":{"jobTitle":"Office Director","contactGroup":"Licensing","sms_opt_in":false}}}},"responses":{"200":{"description":"Contact updated; returns the stored contact.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactSavedResponse"},"example":{"message":"Contact updated successfully","contact":{"id":"c_8Hk2pQ","user_id":"u_71bXq","organization_id":"org_northwind","firstName":"Priya","lastName":"Shah","email":"priya.shah@northwinddental.example","smsPhone":"+15550123456","whatsappPhone":null,"contactType":"Staff","jobTitle":"Office Director","timezone":"America/New_York","contactGroup":"Licensing","is_default":true,"email_opt_in":true,"sms_opt_in":false,"whatsapp_opt_in":false,"sendNotifications":true,"createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-28T09:12:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateContactGroup":{"post":{"operationId":"updateContactGroup","summary":"Update or rename a contact group","description":"Requires: bearer token; role editor or admin.\nId from the `id` query parameter (or body `id`). Renaming checks uniqueness (409 `GROUP_EXISTS`) and\nmoves every member contact to the new name; `membersUpdated` reports how many.\n","tags":["Contacts"],"parameters":[{"name":"id","in":"query","required":false,"description":"Group id (alternative to body `id`).","schema":{"type":"string"},"example":"grp_7Qw2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ContactGroupInput"},{"type":"object","properties":{"id":{"type":"string","description":"Group id (used when `id` is not in the query)."}}}]},"example":{"name":"Suppliers","sortOrder":1}}}},"responses":{"200":{"description":"Group updated.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ContactGroupSavedResponse"},{"type":"object","properties":{"membersUpdated":{"type":"integer","description":"Contacts moved to the new name (0 when not renamed)."}}}]},"example":{"message":"Group updated","group":{"id":"grp_7Qw2","organization_id":"org_northwind","name":"Suppliers","color":"#2e7d32","sortOrder":1,"createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-28T09:12:00.000Z"},"membersUpdated":3}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"A group with that name already exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"A group with that name already exists","code":"GROUP_EXISTS"}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/createFolder":{"post":{"operationId":"createFolder","summary":"Create a folder","description":"Requires: bearer token; role editor or admin.\n`name` (max 255 characters) must be unique among non-deleted siblings (409). Omit `parent_folder_id`\nfor a root folder. A legacy nested body `{name: {name, parent_folder_id}}` is also accepted.\n","tags":["Folders"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":255},"parent_folder_id":{"type":["string","null"],"description":"Parent folder id in your organization; null or omitted for root."}}},"example":{"name":"State Board","parent_folder_id":"fld_2Kp9"}}}},"responses":{"201":{"description":"Folder created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FolderSavedResponse"},"example":{"success":true,"message":"Folder created successfully","folder":{"id":"fld_8Tz4","name":"State Board","parent_folder_id":"fld_2Kp9","folder_path":"/Licenses/State Board","user_id":"u_71bXq","organization_id":"org_northwind","is_deleted":false,"expiry_count":0,"created_at":"2026-09-27T14:06:00.000Z","updated_at":"2026-09-27T14:06:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"A folder with this name already exists in this location.; or a request with the same Idempotency-Key is still being processed (code IDEMPOTENCY_IN_PROGRESS).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deleteFolder":{"post":{"operationId":"deleteFolder","summary":"Delete a folder","description":"Requires: bearer token; role editor or admin.\nSoft-deletes the folder (`folderId` in the body, or `id` query parameter). A folder with subfolders or\nexpiries returns 400 unless `force` is true; with `force`, its expiries are un-filed and subfolders deleted.\n","tags":["Folders"],"parameters":[{"name":"id","in":"query","required":false,"description":"Folder id (alternative to body `folderId`).","schema":{"type":"string"},"example":"fld_8Tz4"},{"name":"force","in":"query","required":false,"description":"Delete even when not empty.","schema":{"type":"boolean","default":false}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"folderId":{"type":"string"},"force":{"oneOf":[{"type":"boolean"},{"type":"string","enum":["true","false"]}],"default":false}}},"example":{"folderId":"fld_8Tz4","force":true}}}},"responses":{"200":{"description":"Folder deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FolderDeletedResponse"},"example":{"success":true,"message":"Folder deleted successfully","deleted_subfolders":0}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Missing folder id, or the folder is not empty and `force` was not set.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FolderNotEmptyError"},"example":{"error":"Folder is not empty","message":"Folder contains 1 subfolder(s) and 6 expiry(ies)","children_count":1,"expiries_count":6,"can_force_delete":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getAllFolders":{"get":{"operationId":"getAllFolders","summary":"List folders as a tree","description":"Requires: bearer token; any role (admin, editor or viewer).\nReturns root folders with nested `children` and each folder's `expiry_count` (non-archived expiries).\nDeleted folders are excluded. Returns all folders; unbounded.\n","tags":["Folders"],"responses":{"200":{"description":"Folder tree.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FolderTree"},"example":{"success":true,"folders":[{"id":"fld_2Kp9","name":"Licenses","parent_folder_id":null,"folder_path":"/Licenses","user_id":"u_71bXq","organization_id":"org_northwind","is_deleted":false,"expiry_count":6,"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z","children":[{"id":"fld_8Tz4","name":"State Board","parent_folder_id":"fld_2Kp9","folder_path":"/Licenses/State Board","user_id":"u_71bXq","organization_id":"org_northwind","is_deleted":false,"expiry_count":2,"created_at":"2026-09-27T14:06:00.000Z","updated_at":"2026-09-27T14:06:00.000Z","children":[]}]}],"total_count":2,"root_count":1}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getAllFolders_post","summary":"List folders as a tree (POST)","description":"Requires: bearer token; any role (admin, editor or viewer).\nSame as `GET /getAllFolders`; the body is ignored. Unbounded.\n","tags":["Folders"],"responses":{"200":{"description":"Folder tree.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FolderTree"},"example":{"success":true,"folders":[{"id":"fld_2Kp9","name":"Licenses","parent_folder_id":null,"folder_path":"/Licenses","organization_id":"org_northwind","is_deleted":false,"expiry_count":6,"children":[],"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}],"total_count":1,"root_count":1}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/moveToFolder":{"post":{"operationId":"moveToFolder","summary":"Move expiries into a folder","description":"Requires: bearer token; role editor or admin.\nMoves `expiry_ids` (max 1000) or a single `expiryId` into `folder_id` / `folderId` (null or omitted = no folder).\nIds that are invalid, missing or in another organization are counted in `failed_count`. Moves expiries only, not folders.\n","tags":["Folders"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MoveToFolderInput"},"example":{"expiry_ids":["exp_4Tq9sLm2","exp_9Pd3kWx1"],"folder_id":"fld_2Kp9"}}}},"responses":{"200":{"description":"Move finished.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MoveToFolderResult"},"example":{"success":true,"message":"2 expiries moved successfully","updated_count":2,"failed_count":0,"folder_id":"fld_2Kp9"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateFolder":{"post":{"operationId":"updateFolder","summary":"Rename or move a folder","description":"Requires: bearer token; role editor or admin.\nId from the `id` query parameter (or body `id`). Send `name` to rename and/or `parent_folder_id` to move\n(null = root). Moves that would create a cycle return 400; descendants' `folder_path` is recomputed.\n","tags":["Folders"],"parameters":[{"name":"id","in":"query","required":false,"description":"Folder id (alternative to body `id`).","schema":{"type":"string"},"example":"fld_8Tz4"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Folder id (used when `id` is not in the query)."},"name":{"type":"string","maxLength":255},"parent_folder_id":{"type":["string","null"]}}},"example":{"name":"State Dental Board","parent_folder_id":"fld_2Kp9"}}}},"responses":{"200":{"description":"Folder updated. `folder` is the stored folder merged with the changes (no `children` or `expiry_count`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FolderSavedResponse"},"example":{"success":true,"message":"Folder updated successfully","folder":{"id":"fld_8Tz4","name":"State Dental Board","parent_folder_id":"fld_2Kp9","folder_path":"/Licenses/State Dental Board","user_id":"u_71bXq","organization_id":"org_northwind","is_deleted":false,"created_at":"2026-09-27T14:06:00.000Z","updated_at":"2026-09-28T09:12:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"A folder with this name already exists in this location.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Folder with this name already exists in this location"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/archiveTemplate":{"post":{"operationId":"archiveTemplate","summary":"Archive a custom template","description":"Requires: bearer token; role editor or admin.\nArchived templates are hidden from pickers and free their name for reuse.","tags":["Templates"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["templateId"],"properties":{"templateId":{"type":"string"},"organizationId":{"type":"string","description":"Optional legacy field; must equal your organization id (403 otherwise). `userId` is ignored."}}},"example":{"templateId":"tpl_Lic42a"}}}},"responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Template archived successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getIndustryTemplate":{"get":{"operationId":"getIndustryTemplate","summary":"Get an industry template","description":"Requires: bearer token; any role (admin, editor or viewer).\nUnexpected errors are returned as 400 (legacy).","tags":["Templates"],"parameters":[{"name":"id","in":"query","required":true,"description":"Industry template id.","schema":{"type":"string"},"example":"ind_dental_license"}],"responses":{"200":{"description":"The industry template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndustryTemplate"},"example":{"id":"ind_dental_license","industry_name":"Healthcare","template_name":"Dental License","template_data":{"License Number":"","Issuing Board":"State Board of Dentistry","CE Hours Required":""},"created_at":"2026-01-10T09:00:00.000Z","updated_at":"2026-06-02T11:30:00.000Z"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getIndustryTemplate_post","summary":"Get an industry template (POST)","description":"Requires: bearer token; any role (admin, editor or viewer).\nSame as the GET form; `id` must still be sent in the query string.","tags":["Templates"],"parameters":[{"name":"id","in":"query","required":true,"description":"Industry template id.","schema":{"type":"string"},"example":"ind_dental_license"}],"responses":{"200":{"description":"The industry template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndustryTemplate"},"example":{"id":"ind_dental_license","industry_name":"Healthcare","template_name":"Dental License","template_data":{"License Number":"","Issuing Board":"State Board of Dentistry","CE Hours Required":""},"created_at":"2026-01-10T09:00:00.000Z","updated_at":"2026-06-02T11:30:00.000Z"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getIndustryTemplates":{"get":{"operationId":"getIndustryTemplates","summary":"List industry templates","description":"Requires: bearer token; any role (admin, editor or viewer).\nReturns the global industry template catalog (shared by all organizations). Unbounded.","tags":["Templates"],"responses":{"200":{"description":"All industry templates.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/IndustryTemplate"}},"example":[{"id":"ind_dental_license","industry_name":"Healthcare","template_name":"Dental License","template_data":{"License Number":"","Issuing Board":"State Board of Dentistry","CE Hours Required":""},"created_at":"2026-01-10T09:00:00.000Z","updated_at":"2026-06-02T11:30:00.000Z"}]}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getIndustryTemplates_post","summary":"List industry templates (POST)","description":"Requires: bearer token; any role (admin, editor or viewer).\nReturns the global industry template catalog (shared by all organizations). Unbounded.","tags":["Templates"],"responses":{"200":{"description":"All industry templates.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/IndustryTemplate"}},"example":[{"id":"ind_dental_license","industry_name":"Healthcare","template_name":"Dental License","template_data":{"License Number":"","Issuing Board":"State Board of Dentistry","CE Hours Required":""},"created_at":"2026-01-10T09:00:00.000Z","updated_at":"2026-06-02T11:30:00.000Z"}]}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/template":{"get":{"operationId":"template_get","summary":"Get a custom template","description":"Requires: bearer token; any role (admin, editor or viewer).\nOnly GET, POST, PUT and DELETE are accepted on /template.","tags":["Templates"],"parameters":[{"name":"templateId","in":"query","required":true,"description":"Custom template id.","schema":{"type":"string"},"example":"tpl_Lic42a"},{"name":"organizationId","in":"query","required":false,"description":"Optional legacy parameter. If sent it must equal your organization id (403 otherwise); `userId` is ignored.","schema":{"type":"string"},"example":"org_northwind"}],"responses":{"200":{"description":"The template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Template"},"example":{"id":"tpl_Lic42a","name":"Professional License","description":"Fields tracked for every state professional license.","icon":"DocumentText","color":"#3b82f6","fields":[{"name":"license_number","label":"License Number","type":"text","required":true},{"name":"ce_hours","label":"CE Hours","type":"number","required":false,"options":{"min":0}},{"name":"board","label":"Board","type":"select","required":false,"options":{"options":["Dental","Medical","Nursing"]}}],"created_by":"u_71bXq","usage_count":3,"archived":false,"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"put":{"operationId":"template_put","summary":"Update a custom template","description":"Requires: bearer token; role editor or admin.\nPartial update of name, description, fields, icon and color. A new name must be unique among active templates.","tags":["Templates"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/TemplateInput"},{"type":"object","required":["templateId"],"properties":{"templateId":{"type":"string"},"organizationId":{"type":"string","description":"Optional legacy field; must equal your organization id (403 otherwise). `userId` is ignored."}}}]},"example":{"templateId":"tpl_Lic42a","description":"Fields tracked for every professional license, all states."}}}},"responses":{"200":{"description":"Updated. The template plus a `message`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Template"},{"type":"object","properties":{"message":{"type":"string"}}}]},"example":{"id":"tpl_Lic42a","name":"Professional License","description":"Fields tracked for every professional license, all states.","icon":"DocumentText","color":"#3b82f6","fields":[{"name":"license_number","label":"License Number","type":"text","required":true},{"name":"ce_hours","label":"CE Hours","type":"number","required":false,"options":{"min":0}},{"name":"board","label":"Board","type":"select","required":false,"options":{"options":["Dental","Medical","Nursing"]}}],"created_by":"u_71bXq","usage_count":3,"archived":false,"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z","message":"Template updated successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Missing templateId or invalid structure.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"},"errors":{"type":"array","items":{"type":"string"},"description":"Present for structure errors."},"limitReached":{"type":"boolean","description":"Present (true) when the plan cap is reached."},"current":{"type":"integer"},"limit":{"type":"integer"},"remaining":{"type":"integer"}}},"example":{"error":"Template ID is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"409":{"description":"An active template with this name already exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"A template with this name already exists.","message":"You already have a template named \"Professional License\". Please choose a different name.","code":"DUPLICATE_TEMPLATE_NAME"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"post":{"operationId":"template_post","summary":"Create a custom template","description":"Requires: bearer token; role editor or admin.\nName must be unique (case-insensitive) among active templates. Subject to the plan's `custom_field_templates` cap: when reached, returns 400 with `limitReached: true`.","tags":["Templates"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/TemplateInput"},{"type":"object","properties":{"organizationId":{"type":"string","description":"Optional legacy field; must equal your organization id (403 otherwise). `userId` is ignored."}}}],"required":["name","fields"]},"example":{"name":"Professional License","description":"Fields tracked for every state professional license.","icon":"DocumentText","color":"#3b82f6","fields":[{"name":"license_number","label":"License Number","type":"text","required":true},{"name":"ce_hours","label":"CE Hours","type":"number","required":false,"options":{"min":0}},{"name":"board","label":"Board","type":"select","required":false,"options":{"options":["Dental","Medical","Nursing"]}}]}}}},"responses":{"201":{"description":"Created. The template plus a `message`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Template"},{"type":"object","properties":{"message":{"type":"string"}}}]},"example":{"id":"tpl_Lic42a","name":"Professional License","description":"Fields tracked for every state professional license.","icon":"DocumentText","color":"#3b82f6","fields":[{"name":"license_number","label":"License Number","type":"text","required":true},{"name":"ce_hours","label":"CE Hours","type":"number","required":false,"options":{"min":0}},{"name":"board","label":"Board","type":"select","required":false,"options":{"options":["Dental","Medical","Nursing"]}}],"created_by":"u_71bXq","usage_count":0,"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z","message":"Template created successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Invalid template structure, or the plan cap is reached.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"},"errors":{"type":"array","items":{"type":"string"},"description":"Present for structure errors."},"limitReached":{"type":"boolean","description":"Present (true) when the plan cap is reached."},"current":{"type":"integer"},"limit":{"type":"integer"},"remaining":{"type":"integer"}}},"example":{"error":"Invalid template structure","message":"Field 2: invalid field type","errors":["Field 2: invalid field type"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"409":{"description":"An active template with this name already exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"A template with this name already exists.","message":"You already have a template named \"Professional License\". Please choose a different name.","code":"DUPLICATE_TEMPLATE_NAME"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"delete":{"operationId":"template_delete","summary":"Delete a custom template","description":"Requires: bearer token; role editor or admin.\nPermanently deletes the template; expiries keep their own copy of the fields. `templateId` goes in the JSON body. Succeeds even if the id does not exist.","tags":["Templates"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["templateId"],"properties":{"templateId":{"type":"string"},"organizationId":{"type":"string","description":"Optional legacy field; must equal your organization id (403 otherwise). `userId` is ignored."}}},"example":{"templateId":"tpl_Lic42a"}}}},"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Template deleted successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/templates":{"get":{"operationId":"templates","summary":"List custom templates","description":"Requires: bearer token; any role (admin, editor or viewer).\nReturns all of the organization's custom expiry templates. Unbounded.","tags":["Templates"],"parameters":[{"name":"organizationId","in":"query","required":false,"description":"Optional legacy parameter. If sent it must equal your organization id (403 otherwise); `userId` is ignored.","schema":{"type":"string"},"example":"org_northwind"}],"responses":{"200":{"description":"Templates, newest first (including archived ones; filter on `archived`).","content":{"application/json":{"schema":{"type":"object","properties":{"templates":{"type":"array","items":{"$ref":"#/components/schemas/Template"}}}},"example":{"templates":[{"id":"tpl_Lic42a","name":"Professional License","description":"Fields tracked for every state professional license.","icon":"DocumentText","color":"#3b82f6","fields":[{"name":"license_number","label":"License Number","type":"text","required":true},{"name":"ce_hours","label":"CE Hours","type":"number","required":false,"options":{"min":0}},{"name":"board","label":"Board","type":"select","required":false,"options":{"options":["Dental","Medical","Nursing"]}}],"created_by":"u_71bXq","usage_count":3,"archived":false,"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"templates_post","summary":"List custom templates (POST)","description":"Requires: bearer token; any role (admin, editor or viewer).\nSame as the GET form (`organizationId` is read from the query string only).","tags":["Templates"],"parameters":[{"name":"organizationId","in":"query","required":false,"description":"Optional legacy parameter. If sent it must equal your organization id (403 otherwise); `userId` is ignored.","schema":{"type":"string"},"example":"org_northwind"}],"responses":{"200":{"description":"Templates, newest first (including archived ones; filter on `archived`).","content":{"application/json":{"schema":{"type":"object","properties":{"templates":{"type":"array","items":{"$ref":"#/components/schemas/Template"}}}},"example":{"templates":[{"id":"tpl_Lic42a","name":"Professional License","description":"Fields tracked for every state professional license.","icon":"DocumentText","color":"#3b82f6","fields":[{"name":"license_number","label":"License Number","type":"text","required":true},{"name":"ce_hours","label":"CE Hours","type":"number","required":false,"options":{"min":0}},{"name":"board","label":"Board","type":"select","required":false,"options":{"options":["Dental","Medical","Nursing"]}}],"created_by":"u_71bXq","usage_count":3,"archived":false,"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/unarchiveTemplate":{"post":{"operationId":"unarchiveTemplate","summary":"Unarchive a custom template","description":"Requires: bearer token; role editor or admin.\nRestores an archived template.","tags":["Templates"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["templateId"],"properties":{"templateId":{"type":"string"},"organizationId":{"type":"string","description":"Optional legacy field; must equal your organization id (403 otherwise). `userId` is ignored."}}},"example":{"templateId":"tpl_Lic42a"}}}},"responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Template unarchived successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/validateTemplateData":{"post":{"operationId":"validateTemplateData","summary":"Validate values against a custom template","description":"Requires: bearer token; any role (admin, editor or viewer).\nChecks required fields and per-type rules. Invalid data returns 400 with `valid: false` and per-field `errors`.","tags":["Templates"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["templateId","data"],"properties":{"templateId":{"type":"string"},"data":{"type":"object","additionalProperties":true,"description":"Field name -> value."},"organizationId":{"type":"string","description":"Optional legacy field; must equal your organization id (403 otherwise). `userId` is ignored."}}},"example":{"templateId":"tpl_Lic42a","data":{"license_number":"DL-20931","ce_hours":"24","board":"Dental"}}}}},"responses":{"200":{"description":"The data is valid.","content":{"application/json":{"schema":{"type":"object","properties":{"valid":{"type":"boolean","const":true},"message":{"type":"string"}}},"example":{"valid":true,"message":"Data is valid"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Missing input, or the data is invalid (`valid: false`).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Present for missing input."},"valid":{"type":"boolean","const":false},"errors":{"type":"object","additionalProperties":{"type":"string"},"description":"Field name -> message."}}},"example":{"valid":false,"errors":{"license_number":"This field is required","ce_hours":"Value must be a valid number"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/emailTemplate":{"get":{"operationId":"emailTemplate_get","summary":"Get an email template","description":"Requires: bearer token; any role (admin, editor or viewer).\nOnly GET, POST, PUT and DELETE are accepted on /emailTemplate.","tags":["Email Templates"],"parameters":[{"name":"templateId","in":"query","required":true,"description":"Email template id.","schema":{"type":"string"},"example":"et_Qw81zd"}],"responses":{"200":{"description":"The template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplate"},"example":{"id":"et_Qw81zd","name":"Friendly renewal reminder","subject":"Reminder: {{expiry.name}} {{expiry.status}}","body_html":"<p>Hi {{contact.firstName}},</p><p>{{expiry.name}} {{expiry.status}}. <a href=\"{{expiry.url}}\">View details</a>.</p>","type":"expiry_reminder","track_opens":true,"track_clicks":false,"created_by":"u_71bXq","updated_by":"u_71bXq","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"put":{"operationId":"emailTemplate_put","summary":"Update an email template","description":"Requires: bearer token; role editor or admin.\nFull replace of name, subject and body_html (all required); `type`, `track_opens` and `track_clicks` keep their values when omitted. Requires the custom email templates plan feature (402).","tags":["Email Templates"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/EmailTemplateInput"},{"type":"object","properties":{"templateId":{"type":"string"}}}],"required":["templateId","name","subject","body_html"]},"example":{"templateId":"et_Qw81zd","name":"Friendly renewal reminder","subject":"Action needed: {{expiry.name}} {{expiry.status}}","body_html":"<p>Hi {{contact.firstName}},</p><p>{{expiry.name}} {{expiry.status}}. <a href=\"{{expiry.url}}\">View details</a>.</p>","type":"expiry_reminder","track_opens":true,"track_clicks":false}}}},"responses":{"200":{"description":"The updated template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplate"},"example":{"id":"et_Qw81zd","name":"Friendly renewal reminder","subject":"Action needed: {{expiry.name}} {{expiry.status}}","body_html":"<p>Hi {{contact.firstName}},</p><p>{{expiry.name}} {{expiry.status}}. <a href=\"{{expiry.url}}\">View details</a>.</p>","type":"expiry_reminder","track_opens":true,"track_clicks":false,"created_by":"u_71bXq","updated_by":"u_71bXq","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T15:00:00.000Z"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"The organization plan does not include custom email templates.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanUpgradeRequiredError"},"example":{"error":"Custom email templates are available on the Pro Essentials plan or higher.","code":"PLAN_UPGRADE_REQUIRED","required_feature":"custom_email_templates","required_plans":["ProEssentials","BusinessMax","PowerUser"],"current_plan":"Starter"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"post":{"operationId":"emailTemplate_post","summary":"Create an email template","description":"Requires: bearer token; role editor or admin.\nRequires a plan with custom email templates (402 otherwise). When the plan's `email_templates` cap is reached the handler returns 429 with `limitReached: true`, `current`, `limit`, `remaining`.","tags":["Email Templates"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/EmailTemplateInput"}],"required":["name","subject","body_html"]},"example":{"name":"Friendly renewal reminder","subject":"Reminder: {{expiry.name}} {{expiry.status}}","body_html":"<p>Hi {{contact.firstName}},</p><p>{{expiry.name}} {{expiry.status}}. <a href=\"{{expiry.url}}\">View details</a>.</p>","type":"expiry_reminder","track_opens":true,"track_clicks":false}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplate"},"example":{"id":"et_Qw81zd","name":"Friendly renewal reminder","subject":"Reminder: {{expiry.name}} {{expiry.status}}","body_html":"<p>Hi {{contact.firstName}},</p><p>{{expiry.name}} {{expiry.status}}. <a href=\"{{expiry.url}}\">View details</a>.</p>","type":"expiry_reminder","track_opens":true,"track_clicks":false,"created_by":"u_71bXq","updated_by":"u_71bXq","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"The organization plan does not include custom email templates.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanUpgradeRequiredError"},"example":{"error":"Custom email templates are available on the Pro Essentials plan or higher.","code":"PLAN_UPGRADE_REQUIRED","required_feature":"custom_email_templates","required_plans":["ProEssentials","BusinessMax","PowerUser"],"current_plan":"Starter"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"delete":{"operationId":"emailTemplate_delete","summary":"Delete an email template","description":"Requires: bearer token; role editor or admin.\nDeletes the template, clears it as the organization default and unlinks it from any expiry types (they fall back to the default). Pass `templateId` in the query string or JSON body.","tags":["Email Templates"],"parameters":[{"name":"templateId","in":"query","required":false,"description":"Email template id.","schema":{"type":"string"},"example":"et_Qw81zd"}],"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"deleted":{"type":"boolean"},"unlinked_expiry_type_count":{"type":"integer"}}},"example":{"id":"et_Qw81zd","deleted":true,"unlinked_expiry_type_count":2}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/emailTemplateDefault":{"get":{"operationId":"emailTemplateDefault","summary":"Get the built-in default email template","description":"Public (no token); returns static content.\nUse it as the starting point for a new template in an editor.","tags":["Email Templates"],"parameters":[{"name":"type","in":"query","required":false,"description":"Template type. Only `expiry_reminder` has a default.","schema":{"type":"string","enum":["expiry_reminder"],"default":"expiry_reminder"},"example":"expiry_reminder"}],"responses":{"200":{"description":"The built-in default template (name, type, subject, body_html).","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string"},"subject":{"type":"string"},"body_html":{"type":"string"}}},"example":{"name":"Default Expiry Reminder","type":"expiry_reminder","subject":"Reminder: {{expiry.name}} {{expiry.status}}","body_html":"<!DOCTYPE html><html lang=\"en\">...</html>"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"emailTemplateDefault_post","summary":"Get the built-in default email template (POST)","description":"Public (no token); returns static content.\nSame as the GET form; `type` is read from the query string.","tags":["Email Templates"],"parameters":[{"name":"type","in":"query","required":false,"description":"Template type. Only `expiry_reminder` has a default.","schema":{"type":"string","enum":["expiry_reminder"],"default":"expiry_reminder"},"example":"expiry_reminder"}],"responses":{"200":{"description":"The built-in default template (name, type, subject, body_html).","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string"},"subject":{"type":"string"},"body_html":{"type":"string"}}},"example":{"name":"Default Expiry Reminder","type":"expiry_reminder","subject":"Reminder: {{expiry.name}} {{expiry.status}}","body_html":"<!DOCTYPE html><html lang=\"en\">...</html>"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/emailTemplatePreview":{"post":{"operationId":"emailTemplatePreview","summary":"Render an email template preview","description":"Requires: bearer token; any role (admin, editor or viewer).\nRenders an unsaved subject and body against sample data, or against `context` when supplied (max 64 KB). Nothing is saved or sent.","tags":["Email Templates"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"subject":{"type":"string"},"body_html":{"type":"string","description":"At most ~900 KB."},"context":{"type":"object","additionalProperties":true,"description":"Render context (same shape as `sample` from emailTemplateVariables)."}}},"example":{"subject":"Reminder: {{expiry.name}} {{expiry.status}}","body_html":"<p>{{expiry.name}} {{expiry.status}}.</p>"}}}},"responses":{"200":{"description":"The rendered email.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplateRendered"},"example":{"subject":"Reminder: Acme Insurance Policy expires in 14 days","html":"<p>Acme Insurance Policy expires in 14 days.</p>","text":"Acme Insurance Policy expires in 14 days."}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"413":{"description":"The template or context is too large to preview.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Template is too large to preview.","code":"PAYLOAD_TOO_LARGE"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/emailTemplates":{"get":{"operationId":"emailTemplates","summary":"List email templates","description":"Requires: bearer token; any role (admin, editor or viewer).\nReturns all of the organization's email templates (unbounded), the selected default per type, plan entitlement and per-template expiry type usage.","tags":["Email Templates"],"responses":{"200":{"description":"Templates plus selection, entitlement and usage.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplateList"},"example":{"templates":[{"id":"et_Qw81zd","name":"Friendly renewal reminder","subject":"Reminder: {{expiry.name}} {{expiry.status}}","body_html":"<p>Hi {{contact.firstName}},</p><p>{{expiry.name}} {{expiry.status}}. <a href=\"{{expiry.url}}\">View details</a>.</p>","type":"expiry_reminder","track_opens":true,"track_clicks":false,"created_by":"u_71bXq","updated_by":"u_71bXq","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}],"selected":{"expiry_reminder":"et_Qw81zd"},"entitlement":{"allowed":true,"planName":"ProEssentials","expiresAt":1790518000000},"usage":{"et_Qw81zd":2}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"emailTemplates_post","summary":"List email templates (POST)","description":"Requires: bearer token; any role (admin, editor or viewer).\nReturns all of the organization's email templates (unbounded), the selected default per type, plan entitlement and per-template expiry type usage.","tags":["Email Templates"],"responses":{"200":{"description":"Templates plus selection, entitlement and usage.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplateList"},"example":{"templates":[{"id":"et_Qw81zd","name":"Friendly renewal reminder","subject":"Reminder: {{expiry.name}} {{expiry.status}}","body_html":"<p>Hi {{contact.firstName}},</p><p>{{expiry.name}} {{expiry.status}}. <a href=\"{{expiry.url}}\">View details</a>.</p>","type":"expiry_reminder","track_opens":true,"track_clicks":false,"created_by":"u_71bXq","updated_by":"u_71bXq","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}],"selected":{"expiry_reminder":"et_Qw81zd"},"entitlement":{"allowed":true,"planName":"ProEssentials","expiresAt":1790518000000},"usage":{"et_Qw81zd":2}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/emailTemplateSelect":{"post":{"operationId":"emailTemplateSelect","summary":"Select the default email template","description":"Requires: bearer token; role editor or admin.\nSets the organization default template for a type. `templateId: null` reverts to the built-in default (allowed on any plan); selecting a custom template requires the plan feature (402).","tags":["Email Templates"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"templateId":{"type":["string","null"],"description":"Template id, or null / empty to deselect."},"type":{"type":"string","enum":["expiry_reminder"],"description":"Defaults to `expiry_reminder`."}}},"example":{"templateId":"et_Qw81zd","type":"expiry_reminder"}}}},"responses":{"200":{"description":"The selection after the change.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplateSelection"},"example":{"selected":{"expiry_reminder":"et_Qw81zd"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"The organization plan does not include custom email templates.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanUpgradeRequiredError"},"example":{"error":"Custom email templates are available on the Pro Essentials plan or higher.","code":"PLAN_UPGRADE_REQUIRED","required_feature":"custom_email_templates","required_plans":["ProEssentials","BusinessMax","PowerUser"],"current_plan":"Starter"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/emailTemplateVariables":{"get":{"operationId":"emailTemplateVariables","summary":"List email template variables","description":"Optional bearer token; any role. Without a valid token the static variable set is returned.\nWith a token, adds one `custom_<templateId>` category per custom expiry template (`custom.<field>` keys, up to 40 fields each). Never returns an error.","tags":["Email Templates"],"responses":{"200":{"description":"Variable catalog.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplateVariables"},"example":{"categories":[{"key":"expiry","label":"Expiry","icon":"calendar","description":"Fields about the expiring item the email is about","variables":[{"key":"expiry.name","label":"Name","description":"Display name of the expiry","sample":"Acme Insurance Policy"},{"key":"expiry.daysLeft","label":"Days Left","description":"Days until expiry (negative if expired)","sample":14}]}],"sample":{"expiry":{"name":"Acme Insurance Policy","daysLeft":14}},"allKeys":["expiry.name","expiry.daysLeft"]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{},{"bearerAuth":[]}],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"emailTemplateVariables_post","summary":"List email template variables (POST)","description":"Optional bearer token; any role. Without a valid token the static variable set is returned.\nWith a token, adds one `custom_<templateId>` category per custom expiry template (`custom.<field>` keys, up to 40 fields each). Never returns an error.","tags":["Email Templates"],"responses":{"200":{"description":"Variable catalog.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailTemplateVariables"},"example":{"categories":[{"key":"expiry","label":"Expiry","icon":"calendar","description":"Fields about the expiring item the email is about","variables":[{"key":"expiry.name","label":"Name","description":"Display name of the expiry","sample":"Acme Insurance Policy"},{"key":"expiry.daysLeft","label":"Days Left","description":"Days until expiry (negative if expired)","sample":14}]}],"sample":{"expiry":{"name":"Acme Insurance Policy","daysLeft":14}},"allKeys":["expiry.name","expiry.daysLeft"]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{},{"bearerAuth":[]}],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/archiveCollectionRequest":{"post":{"operationId":"archiveCollectionRequest","summary":"Archive a request","description":"Requires: bearer token; role editor or admin.\nSets `archived: true` (a visibility flag only; the link keeps working). The handler does not check the HTTP method.","tags":["Document Collection"],"parameters":[{"name":"id","in":"query","required":false,"description":"Request id (or `id` in the body).","schema":{"type":"string"},"example":"req_9WkT"}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Request archived"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, or the record belongs to another organization (`{\"error\": \"Access denied\"}`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Access denied"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/archiveCollectionTemplate":{"post":{"operationId":"archiveCollectionTemplate","summary":"Archive a collection template","description":"Requires: bearer token; role editor or admin.\nArchived templates cannot be used to send new requests; existing request links keep working. The handler does not check the HTTP method.","tags":["Document Collection"],"parameters":[{"name":"id","in":"query","required":false,"description":"Template id (or `id` in the body).","schema":{"type":"string"},"example":"tpl_W9onboard"}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Template archived"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, or the record belongs to another organization (`{\"error\": \"Access denied\"}`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Access denied"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/cancelCollectionRequest":{"post":{"operationId":"cancelCollectionRequest","summary":"Cancel a request","description":"Requires: bearer token; role editor or admin.\nThe link stops working (410 for the recipient) and pending reminders are skipped. Completed requests cannot be cancelled. The handler does not check the HTTP method.","tags":["Document Collection"],"parameters":[{"name":"id","in":"query","required":false,"description":"Request id (or `id` in the body).","schema":{"type":"string"},"example":"req_9WkT"}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Request cancelled"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, or the record belongs to another organization (`{\"error\": \"Access denied\"}`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Access denied"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/createCollectionRequestDraft":{"post":{"operationId":"createCollectionRequestDraft","summary":"Create a one-off request draft from a template","description":"Requires: bearer token; role editor or admin.\nCopies a template into a `request_draft` template you can edit for a single send (does not count against the template limit; reference files are not copied). Sending it with createCollectionRequests marks it `request_sent`. The handler does not check the HTTP method.","tags":["Document Collection"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["templateId"],"properties":{"templateId":{"type":"string"}}},"example":{"templateId":"tpl_W9onboard"}}}},"responses":{"201":{"description":"Draft created.","content":{"application/json":{"schema":{"type":"object","required":["message","template"],"properties":{"message":{"type":"string"},"template":{"$ref":"#/components/schemas/CollectionTemplate"}}},"example":{"message":"Request draft created","template":{"id":"tpl_D7draft","organization_id":"org_northwind","name":"New client onboarding - Northwind Dental","description":"Documents we need before the first engagement.","pages":[{"id":"p_company","title":"Company details","order":0,"description_html":"<p>Tell us about your practice.</p>","sections":[{"id":"s_basics","title":"Basics","order":0,"description_html":"","fields":[{"id":"fld_company_name","type":"short_text","label":"Legal company name","required":true,"help_text":""},{"id":"fld_entity","type":"dropdown","label":"Entity type","required":true,"help_text":"","options":["LLC","Corporation","Sole proprietor"]},{"id":"fld_insurance","type":"file_upload","label":"Liability insurance certificate","required":true,"help_text":"PDF or image, current policy year.","allowed_types":["pdf","jpg","png"],"max_size_bytes":26214400,"reference_file":{"storage_path":"organizations/org_northwind/collection-templates/tpl_W9onboard/fields/fld_insurance/reference-1b2c-sample.pdf","original_filename":"sample-certificate.pdf","content_type":"application/pdf","size_bytes":120331}}]}]}],"intro_email":{"body_html":"<p>Hi, please upload the documents below by Friday.</p>"},"schema_version":2,"status":"request_draft","created_by":"u_71bXq","created_by_name":"Priya Shah","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, or the record belongs to another organization (`{\"error\": \"Access denied\"}`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Access denied"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/createCollectionRequests":{"post":{"operationId":"createCollectionRequests","summary":"Send a collection request","description":"Requires: bearer token; role editor or admin.\nCreates one request per recipient (contacts plus members of `groupId`, deduplicated; max 200 per send), emails each a unique link and schedules reminders (the org default sequence unless `reminders` is given). Contacts without an email are skipped. Counts against the `collection_requests` plan limit. The handler does not check the HTTP method.","tags":["Document Collection"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["templateId"],"description":"Give `contactIds`, `groupId` or both.","properties":{"templateId":{"type":"string","description":"An `active` template or an unsent `request_draft`."},"contactIds":{"type":"array","items":{"type":"string"}},"groupId":{"type":"string","description":"Contact group id; all contacts in the group are added."},"expiryId":{"type":["string","null"],"description":"Optional expiry to link the requests to."},"password":{"type":["string","null"],"minLength":4,"writeOnly":true,"description":"Optional password the recipient must enter to open the link."},"expiresAt":{"type":["string","null"],"format":"date-time","description":"When the link stops working. No expiry when omitted."},"name":{"type":["string","null"],"maxLength":200,"description":"Label for this send."},"dueDate":{"type":["string","null"],"format":"date-time","description":"Informational \"please submit by\" date shown to the recipient."},"reminders":{"type":"array","maxItems":20,"items":{"$ref":"#/components/schemas/CollectionReminderInput"},"description":"Override the reminder schedule (counted from the send time). `[]` means no reminders."}}},"example":{"templateId":"tpl_W9onboard","contactIds":["c_8Hk2pQ"],"expiryId":"exp_4Tq9sLm2","expiresAt":"2026-10-31T23:59:59.000Z","name":"Q4 insurance refresh","dueDate":"2026-10-15","reminders":[{"amount":3,"time_unit":"day","time":"09:00"}]}}}},"responses":{"201":{"description":"Requests created.","content":{"application/json":{"schema":{"type":"object","required":["message","created","emails_sent","request_ids"],"properties":{"message":{"type":"string"},"created":{"type":"integer"},"emails_sent":{"type":"integer","description":"Emails accepted by the provider; failures are only reflected in this count."},"request_ids":{"type":"array","items":{"type":"string"}},"linked_expiry":{"type":["object","null"],"properties":{"id":{"type":"string"},"name":{"type":"string"}}}}},"example":{"message":"1 request(s) created, 1 email(s) sent","created":1,"emails_sent":1,"request_ids":["req_9WkT"],"linked_expiry":{"id":"exp_4Tq9sLm2","name":"Northwind Dental - State Dental License"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, the record belongs to another organization (`Access denied`), or the plan limit is reached (body adds `current`, `limit`, `remaining`).","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/PlanLimitError"}]},"example":{"error":"You have reached the maximum number of collection templates for your plan.","current":5,"limit":5,"remaining":0}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/IdempotencyInProgress"},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/createCollectionTemplate":{"post":{"operationId":"createCollectionTemplate","summary":"Create a collection template","description":"Requires: bearer token; role editor or admin.\nCounts against the `collection_templates` plan limit. Limits: 20 pages, 20 sections per page, 50 fields in total, 20KB of HTML per rich-text block. Pass an optional client-generated `id` to upload field reference files before the first save. The handler does not check the HTTP method.","tags":["Document Collection"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"id":{"type":"string","pattern":"^[a-zA-Z0-9_-]+$","description":"Optional client-chosen id for the new template (create only; 409 if it exists)."}}},{"$ref":"#/components/schemas/CollectionTemplateInput"}],"required":["name","pages"]},"example":{"id":"tpl_W9onboard","name":"New client onboarding - Northwind Dental","description":"Documents we need before the first engagement.","pages":[{"id":"p_company","title":"Company details","description_html":"<p>Tell us about your practice.</p>","sections":[{"id":"s_basics","title":"Basics","fields":[{"id":"fld_company_name","type":"short_text","label":"Legal company name","required":true},{"id":"fld_insurance","type":"file_upload","label":"Liability insurance certificate","required":true,"allowed_types":["pdf","jpg","png"],"max_size_bytes":26214400}]}]}],"intro_email":{"body_html":"<p>Hi, please upload the documents below by Friday.</p>"}}}}},"responses":{"201":{"description":"Template created.","content":{"application/json":{"schema":{"type":"object","required":["message","template"],"properties":{"message":{"type":"string"},"template":{"$ref":"#/components/schemas/CollectionTemplate"}}},"example":{"message":"Template created","template":{"id":"tpl_W9onboard","organization_id":"org_northwind","name":"New client onboarding - Northwind Dental","description":"Documents we need before the first engagement.","pages":[{"id":"p_company","title":"Company details","order":0,"description_html":"<p>Tell us about your practice.</p>","sections":[{"id":"s_basics","title":"Basics","order":0,"description_html":"","fields":[{"id":"fld_company_name","type":"short_text","label":"Legal company name","required":true,"help_text":""},{"id":"fld_entity","type":"dropdown","label":"Entity type","required":true,"help_text":"","options":["LLC","Corporation","Sole proprietor"]},{"id":"fld_insurance","type":"file_upload","label":"Liability insurance certificate","required":true,"help_text":"PDF or image, current policy year.","allowed_types":["pdf","jpg","png"],"max_size_bytes":26214400,"reference_file":{"storage_path":"organizations/org_northwind/collection-templates/tpl_W9onboard/fields/fld_insurance/reference-1b2c-sample.pdf","original_filename":"sample-certificate.pdf","content_type":"application/pdf","size_bytes":120331}}]}]}],"intro_email":{"body_html":"<p>Hi, please upload the documents below by Friday.</p>"},"schema_version":2,"status":"active","created_by":"u_71bXq","created_by_name":"Priya Shah","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, the record belongs to another organization (`Access denied`), or the plan limit is reached (body adds `current`, `limit`, `remaining`).","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/PlanLimitError"}]},"example":{"error":"You have reached the maximum number of collection templates for your plan.","current":5,"limit":5,"remaining":0}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"A template with the given `id` already exists.; or a request with the same Idempotency-Key is still being processed (code IDEMPOTENCY_IN_PROGRESS).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/createCollectionTriggerRule":{"post":{"operationId":"createCollectionTriggerRule","summary":"Create an auto-send trigger rule on an expiry","description":"Requires: bearer token; role editor or admin.\nAutomatically sends a template to contacts relative to an expiry (before/on/after its date, on a date, when marked done, on renewal, or manual only). `manual` rules start `paused`, all others `pending`. The plan limit is checked when the rule fires. The handler does not check the HTTP method.","tags":["Document Collection"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionTriggerRuleInput"},"example":{"expiryId":"exp_4Tq9sLm2","templateId":"tpl_W9onboard","triggerType":"before_expiry","triggerDays":30,"triggerTime":"09:00","contactIds":["c_8Hk2pQ"],"expiresInDays":21,"dueInDays":14,"carryToRenewal":true}}}},"responses":{"201":{"description":"Rule created.","content":{"application/json":{"schema":{"type":"object","required":["message","trigger"],"properties":{"message":{"type":"string"},"trigger":{"$ref":"#/components/schemas/CollectionTriggerRule"}}},"example":{"message":"Trigger created","trigger":{"id":"trg_2Vb8","organization_id":"org_northwind","expiry_id":"exp_4Tq9sLm2","expiry_name":"Northwind Dental - State Dental License","template_id":"tpl_W9onboard","template_name":"New client onboarding - Northwind Dental","trigger_type":"before_expiry","trigger_days":30,"trigger_time":"09:00","trigger_date":"2026-09-15T13:00:00.000Z","contact_ids":["c_8Hk2pQ"],"group_id":null,"group_name":null,"expires_in_days":21,"due_in_days":14,"status":"pending","created_request_ids":[],"run_count":0,"last_triggered_at":null,"triggered_by":null,"error_message":null,"carry_to_renewal":true,"created_by":"u_71bXq","created_by_name":"Priya Shah","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deleteCollectionRequest":{"post":{"operationId":"deleteCollectionRequest","summary":"Delete a request permanently","description":"Requires: bearer token; role editor or admin.\nDeletes the request, its submission, uploaded files and reminders. Cannot be undone. The handler does not check the HTTP method.","tags":["Document Collection"],"parameters":[{"name":"id","in":"query","required":false,"description":"Request id (or `id` in the body).","schema":{"type":"string"},"example":"req_9WkT"}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Request deleted"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, or the record belongs to another organization (`{\"error\": \"Access denied\"}`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Access denied"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deleteCollectionRequestDraft":{"post":{"operationId":"deleteCollectionRequestDraft","summary":"Delete an unsent request draft","description":"Requires: bearer token; role editor or admin.\nPermanently deletes a template whose status is `request_draft` (400 for any other template). The handler does not check the HTTP method.","tags":["Document Collection"],"parameters":[{"name":"id","in":"query","required":false,"description":"Draft template id (or `id` in the body).","schema":{"type":"string"},"example":"tpl_D7draft"}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Draft deleted"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, or the record belongs to another organization (`{\"error\": \"Access denied\"}`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Access denied"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deleteCollectionSubmission":{"post":{"operationId":"deleteCollectionSubmission","summary":"Delete a submission","description":"Requires: bearer token; role editor or admin.\nDeletes the submission and its current files. The request itself is not changed. The handler does not check the HTTP method.","tags":["Document Collection"],"parameters":[{"name":"id","in":"query","required":false,"description":"Submission id (or `id` in the body).","schema":{"type":"string"},"example":"sub_Qm3r"}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Submission deleted"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, or the record belongs to another organization (`{\"error\": \"Access denied\"}`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Access denied"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deleteCollectionTriggerRule":{"post":{"operationId":"deleteCollectionTriggerRule","summary":"Delete a trigger rule","description":"Requires: bearer token; role editor or admin.\nSoft delete: the rule is marked deleted and `cancelled` and no longer listed; requests it already created are kept. The handler does not check the HTTP method.","tags":["Document Collection"],"parameters":[{"name":"id","in":"query","required":false,"description":"Rule id (or `id` in the body).","schema":{"type":"string"},"example":"trg_2Vb8"}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Trigger deleted"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/downloadCollectionSubmission":{"get":{"operationId":"downloadCollectionSubmission","summary":"Download a submission as a ZIP","description":"Requires: bearer token; any role (admin, editor or viewer) in the organization.\nStreams a ZIP containing `responses.pdf`, `responses.csv` and every uploaded file. The id is the submission id (`submission.id` from getCollectionRequest), read from the query string only. The handler does not check the HTTP method.","tags":["Document Collection"],"parameters":[{"name":"id","in":"query","required":true,"description":"Submission id.","schema":{"type":"string"},"example":"sub_Qm3r"}],"responses":{"200":{"description":"ZIP archive.","headers":{"Content-Disposition":{"description":"`attachment; filename=\"submission-<id>.zip\"`","schema":{"type":"string"}},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}},"content":{"application/zip":{"schema":{"type":"string","format":"binary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, or the record belongs to another organization (`{\"error\": \"Access denied\"}`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Access denied"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The archive failed mid-stream (the response is cut off; no JSON body)."}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"downloadCollectionSubmission_post","summary":"Download a submission as a ZIP (POST)","description":"Requires: bearer token; any role (admin, editor or viewer) in the organization.\nStreams a ZIP containing `responses.pdf`, `responses.csv` and every uploaded file. The id is the submission id (`submission.id` from getCollectionRequest), read from the query string only. The handler does not check the HTTP method.","tags":["Document Collection"],"parameters":[{"name":"id","in":"query","required":true,"description":"Submission id.","schema":{"type":"string"},"example":"sub_Qm3r"}],"responses":{"200":{"description":"ZIP archive.","headers":{"Content-Disposition":{"description":"`attachment; filename=\"submission-<id>.zip\"`","schema":{"type":"string"}},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}},"content":{"application/zip":{"schema":{"type":"string","format":"binary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, or the record belongs to another organization (`{\"error\": \"Access denied\"}`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Access denied"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The archive failed mid-stream (the response is cut off; no JSON body)."}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/duplicateCollectionTemplate":{"post":{"operationId":"duplicateCollectionTemplate","summary":"Duplicate a collection template","description":"Requires: bearer token; role editor or admin.\nCreates \"<name> (Copy)\" in the same organization. Field reference files are not copied. Counts against the `collection_templates` plan limit. The handler does not check the HTTP method.","tags":["Document Collection"],"parameters":[{"name":"id","in":"query","required":false,"description":"Source template id (or `id` in the body).","schema":{"type":"string"},"example":"tpl_W9onboard"}],"responses":{"201":{"description":"Template duplicated.","content":{"application/json":{"schema":{"type":"object","required":["message","template"],"properties":{"message":{"type":"string"},"template":{"$ref":"#/components/schemas/CollectionTemplate"}}},"example":{"message":"Template duplicated","template":{"id":"tpl_Y3copy","organization_id":"org_northwind","name":"New client onboarding - Northwind Dental (Copy)","description":"Documents we need before the first engagement.","pages":[{"id":"p_company","title":"Company details","order":0,"description_html":"<p>Tell us about your practice.</p>","sections":[{"id":"s_basics","title":"Basics","order":0,"description_html":"","fields":[{"id":"fld_company_name","type":"short_text","label":"Legal company name","required":true,"help_text":""},{"id":"fld_entity","type":"dropdown","label":"Entity type","required":true,"help_text":"","options":["LLC","Corporation","Sole proprietor"]},{"id":"fld_insurance","type":"file_upload","label":"Liability insurance certificate","required":true,"help_text":"PDF or image, current policy year.","allowed_types":["pdf","jpg","png"],"max_size_bytes":26214400,"reference_file":{"storage_path":"organizations/org_northwind/collection-templates/tpl_W9onboard/fields/fld_insurance/reference-1b2c-sample.pdf","original_filename":"sample-certificate.pdf","content_type":"application/pdf","size_bytes":120331}}]}]}],"intro_email":{"body_html":"<p>Hi, please upload the documents below by Friday.</p>"},"schema_version":2,"status":"active","created_by":"u_71bXq","created_by_name":"Priya Shah","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, the record belongs to another organization (`Access denied`), or the plan limit is reached (body adds `current`, `limit`, `remaining`).","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/PlanLimitError"}]},"example":{"error":"You have reached the maximum number of collection templates for your plan.","current":5,"limit":5,"remaining":0}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/exportCollectionRequests":{"get":{"operationId":"exportCollectionRequests","summary":"Export collection requests as CSV","description":"Requires: bearer token; any role (admin, editor or viewer) in the organization.\nReturns a CSV file (`Content-Disposition: attachment`). Pass `ids` for an exact set (max 500), otherwise `status` / `templateId` filters apply (unbounded). When every row uses one template, one column per template field is added with the submitted answers. The handler does not check the HTTP method. Parameters are read from the query string only.","tags":["Document Collection"],"parameters":[{"name":"ids","in":"query","required":false,"description":"Comma-separated request ids (max 500). Ids outside your organization are skipped.","schema":{"type":"string"},"example":"req_9WkT,req_3PzL"},{"name":"templateId","in":"query","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"CSV file. Columns: Request name, Recipient name, Recipient email, Template (multi-template exports only), Status, Due date, Sent at, Completed at, Review status, then one column per field for single-template exports.","headers":{"Content-Disposition":{"description":"`attachment; filename=\"<template name or document-collection-requests>.csv\"`","schema":{"type":"string"}},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}},"content":{"text/csv":{"schema":{"type":"string"},"example":"Request name,Recipient name,Recipient email,Template,Status,Due date,Sent at,Completed at,Review status\r\nQ4 insurance refresh,Dana Whitfield,dana@northwinddental.com,New client onboarding - Northwind Dental,completed,\"10/15/2026, 12:00:00 AM\",\"9/27/2026, 2:05:00 PM\",\"9/28/2026, 9:12:00 AM\",Awaiting review\r\n"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"exportCollectionRequests_post","summary":"Export collection requests as CSV (POST)","description":"Requires: bearer token; any role (admin, editor or viewer) in the organization.\nReturns a CSV file (`Content-Disposition: attachment`). Pass `ids` for an exact set (max 500), otherwise `status` / `templateId` filters apply (unbounded). When every row uses one template, one column per template field is added with the submitted answers. The handler does not check the HTTP method. Parameters are read from the query string only.","tags":["Document Collection"],"parameters":[{"name":"ids","in":"query","required":false,"description":"Comma-separated request ids (max 500). Ids outside your organization are skipped.","schema":{"type":"string"},"example":"req_9WkT,req_3PzL"},{"name":"templateId","in":"query","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"CSV file. Columns: Request name, Recipient name, Recipient email, Template (multi-template exports only), Status, Due date, Sent at, Completed at, Review status, then one column per field for single-template exports.","headers":{"Content-Disposition":{"description":"`attachment; filename=\"<template name or document-collection-requests>.csv\"`","schema":{"type":"string"}},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}},"content":{"text/csv":{"schema":{"type":"string"},"example":"Request name,Recipient name,Recipient email,Template,Status,Due date,Sent at,Completed at,Review status\r\nQ4 insurance refresh,Dana Whitfield,dana@northwinddental.com,New client onboarding - Northwind Dental,completed,\"10/15/2026, 12:00:00 AM\",\"9/27/2026, 2:05:00 PM\",\"9/28/2026, 9:12:00 AM\",Awaiting review\r\n"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getAllCollectionRequests":{"get":{"operationId":"getAllCollectionRequests","summary":"List collection requests","description":"Requires: bearer token; any role (admin, editor or viewer) in the organization.\nReturns every matching request in the organization, newest sent first; unbounded (no pagination). Archived requests are included (filter on `archived`). Open requests past `expires_at` are returned (and saved) as `expired`. The handler does not check the HTTP method. On GET filters are read from the query string, on other methods from the JSON body.","tags":["Document Collection"],"parameters":[{"name":"templateId","in":"query","required":false,"schema":{"type":"string"},"example":"tpl_W9onboard"},{"name":"contactId","in":"query","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["pending","viewed","completed","cancelled","expired"]}},{"name":"expiryId","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Requests.","content":{"application/json":{"schema":{"type":"object","required":["requests"],"properties":{"requests":{"type":"array","items":{"$ref":"#/components/schemas/CollectionRequest"}}}},"example":{"requests":[{"id":"req_9WkT","organization_id":"org_northwind","name":"Q4 insurance refresh","due_date":"2026-10-15T00:00:00.000Z","template_id":"tpl_W9onboard","template_name":"New client onboarding - Northwind Dental","contact_id":"c_8Hk2pQ","contact_name":"Dana Whitfield","contact_email":"dana@northwinddental.com","expiry_id":"exp_4Tq9sLm2","group_id":null,"has_password":false,"expires_at":"2026-10-31T23:59:59.000Z","status":"viewed","archived":false,"paused":false,"sent_at":"2026-09-27T14:05:00.000Z","viewed_at":"2026-09-27T15:10:00.000Z","completed_at":null,"cancelled_at":null,"resend_count":0,"last_resent_at":null,"draft_filled_count":3,"draft_total_fields":5,"draft_updated_at":"2026-09-27T15:20:00.000Z","next_reminder_at":"2026-09-28T13:00:00.000Z","reminders_sent_count":0,"email_stats":{"sent_count":1,"delivered_count":1,"opened_count":1,"last_event_at":"2026-09-27T15:09:30.000Z"},"reply_received":false,"reply_at":null,"reply_snippet":null,"latest_review_status":null,"review_approved_count":null,"review_total_fields":null,"completed_field_count":null,"created_by":"u_71bXq","created_by_name":"Priya Shah","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T15:20:00.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getAllCollectionRequests_post","summary":"List collection requests (POST)","description":"Requires: bearer token; any role (admin, editor or viewer) in the organization.\nReturns every matching request in the organization, newest sent first; unbounded (no pagination). Archived requests are included (filter on `archived`). Open requests past `expires_at` are returned (and saved) as `expired`. The handler does not check the HTTP method. On GET filters are read from the query string, on other methods from the JSON body.","tags":["Document Collection"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"templateId":{"type":"string"},"contactId":{"type":"string"},"status":{"type":"string"},"expiryId":{"type":"string"}}},"example":{"expiryId":"exp_4Tq9sLm2"}}}},"responses":{"200":{"description":"Requests.","content":{"application/json":{"schema":{"type":"object","required":["requests"],"properties":{"requests":{"type":"array","items":{"$ref":"#/components/schemas/CollectionRequest"}}}},"example":{"requests":[{"id":"req_9WkT","organization_id":"org_northwind","name":"Q4 insurance refresh","due_date":"2026-10-15T00:00:00.000Z","template_id":"tpl_W9onboard","template_name":"New client onboarding - Northwind Dental","contact_id":"c_8Hk2pQ","contact_name":"Dana Whitfield","contact_email":"dana@northwinddental.com","expiry_id":"exp_4Tq9sLm2","group_id":null,"has_password":false,"expires_at":"2026-10-31T23:59:59.000Z","status":"viewed","archived":false,"paused":false,"sent_at":"2026-09-27T14:05:00.000Z","viewed_at":"2026-09-27T15:10:00.000Z","completed_at":null,"cancelled_at":null,"resend_count":0,"last_resent_at":null,"draft_filled_count":3,"draft_total_fields":5,"draft_updated_at":"2026-09-27T15:20:00.000Z","next_reminder_at":"2026-09-28T13:00:00.000Z","reminders_sent_count":0,"email_stats":{"sent_count":1,"delivered_count":1,"opened_count":1,"last_event_at":"2026-09-27T15:09:30.000Z"},"reply_received":false,"reply_at":null,"reply_snippet":null,"latest_review_status":null,"review_approved_count":null,"review_total_fields":null,"completed_field_count":null,"created_by":"u_71bXq","created_by_name":"Priya Shah","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T15:20:00.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getAllCollectionTemplates":{"get":{"operationId":"getAllCollectionTemplates","summary":"List collection templates","description":"Requires: bearer token; any role (admin, editor or viewer) in the organization.\nReturns all of the organization's templates with the given status, newest first; unbounded (no pagination). The handler does not check the HTTP method. On GET the filter is read from the query string, on other methods from the JSON body.","tags":["Document Collection"],"parameters":[{"name":"status","in":"query","required":false,"description":"`active` (default), `archived` or `all`. `all` applies no status filter, so it also returns one-off request drafts (`request_draft`, `request_sent`).","schema":{"type":"string","default":"active","examples":["active"]}}],"responses":{"200":{"description":"Templates.","content":{"application/json":{"schema":{"type":"object","required":["templates"],"properties":{"templates":{"type":"array","items":{"$ref":"#/components/schemas/CollectionTemplate"}}}},"example":{"templates":[{"id":"tpl_W9onboard","organization_id":"org_northwind","name":"New client onboarding - Northwind Dental","description":"Documents we need before the first engagement.","pages":[{"id":"p_company","title":"Company details","order":0,"description_html":"<p>Tell us about your practice.</p>","sections":[{"id":"s_basics","title":"Basics","order":0,"description_html":"","fields":[{"id":"fld_company_name","type":"short_text","label":"Legal company name","required":true,"help_text":""},{"id":"fld_entity","type":"dropdown","label":"Entity type","required":true,"help_text":"","options":["LLC","Corporation","Sole proprietor"]},{"id":"fld_insurance","type":"file_upload","label":"Liability insurance certificate","required":true,"help_text":"PDF or image, current policy year.","allowed_types":["pdf","jpg","png"],"max_size_bytes":26214400,"reference_file":{"storage_path":"organizations/org_northwind/collection-templates/tpl_W9onboard/fields/fld_insurance/reference-1b2c-sample.pdf","original_filename":"sample-certificate.pdf","content_type":"application/pdf","size_bytes":120331}}]}]}],"intro_email":{"body_html":"<p>Hi, please upload the documents below by Friday.</p>"},"schema_version":2,"status":"active","created_by":"u_71bXq","created_by_name":"Priya Shah","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getAllCollectionTemplates_post","summary":"List collection templates (POST)","description":"Requires: bearer token; any role (admin, editor or viewer) in the organization.\nReturns all of the organization's templates with the given status, newest first; unbounded (no pagination). The handler does not check the HTTP method. On GET the filter is read from the query string, on other methods from the JSON body.","tags":["Document Collection"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","default":"active"}}},"example":{"status":"archived"}}}},"responses":{"200":{"description":"Templates.","content":{"application/json":{"schema":{"type":"object","required":["templates"],"properties":{"templates":{"type":"array","items":{"$ref":"#/components/schemas/CollectionTemplate"}}}},"example":{"templates":[{"id":"tpl_W9onboard","organization_id":"org_northwind","name":"New client onboarding - Northwind Dental","description":"Documents we need before the first engagement.","pages":[{"id":"p_company","title":"Company details","order":0,"description_html":"<p>Tell us about your practice.</p>","sections":[{"id":"s_basics","title":"Basics","order":0,"description_html":"","fields":[{"id":"fld_company_name","type":"short_text","label":"Legal company name","required":true,"help_text":""},{"id":"fld_entity","type":"dropdown","label":"Entity type","required":true,"help_text":"","options":["LLC","Corporation","Sole proprietor"]},{"id":"fld_insurance","type":"file_upload","label":"Liability insurance certificate","required":true,"help_text":"PDF or image, current policy year.","allowed_types":["pdf","jpg","png"],"max_size_bytes":26214400,"reference_file":{"storage_path":"organizations/org_northwind/collection-templates/tpl_W9onboard/fields/fld_insurance/reference-1b2c-sample.pdf","original_filename":"sample-certificate.pdf","content_type":"application/pdf","size_bytes":120331}}]}]}],"intro_email":{"body_html":"<p>Hi, please upload the documents below by Friday.</p>"},"schema_version":2,"status":"active","created_by":"u_71bXq","created_by_name":"Priya Shah","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getCollectionRequest":{"get":{"operationId":"getCollectionRequest","summary":"Get a collection request with its submission","description":"Requires: bearer token; any role (admin, editor or viewer) in the organization.\nReturns the request plus its submission (or null), a template snapshot, its reminders and up to 100 email events (newest first). The handler does not check the HTTP method. The id is read from the query string only.","tags":["Document Collection"],"parameters":[{"name":"id","in":"query","required":true,"description":"Request id.","schema":{"type":"string"},"example":"req_9WkT"}],"responses":{"200":{"description":"Request details.","content":{"application/json":{"schema":{"type":"object","required":["request","submission","template","reminders","email_events"],"properties":{"request":{"$ref":"#/components/schemas/CollectionRequest"},"submission":{"oneOf":[{"$ref":"#/components/schemas/CollectionSubmissionDetail"},{"type":"null"}]},"template":{"type":["object","null"],"description":"The template (or request draft) the request was sent from; null if it was deleted.","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"pages":{"type":"array","items":{"$ref":"#/components/schemas/CollectionTemplatePage"}}}},"reminders":{"type":"array","items":{"$ref":"#/components/schemas/CollectionRequestReminder"}},"email_events":{"type":"array","items":{"$ref":"#/components/schemas/CollectionEmailEvent"}}}},"example":{"request":{"id":"req_9WkT","organization_id":"org_northwind","name":"Q4 insurance refresh","due_date":"2026-10-15T00:00:00.000Z","template_id":"tpl_W9onboard","template_name":"New client onboarding - Northwind Dental","contact_id":"c_8Hk2pQ","contact_name":"Dana Whitfield","contact_email":"dana@northwinddental.com","expiry_id":"exp_4Tq9sLm2","group_id":null,"has_password":false,"expires_at":"2026-10-31T23:59:59.000Z","status":"completed","archived":false,"paused":false,"sent_at":"2026-09-27T14:05:00.000Z","viewed_at":"2026-09-27T15:10:00.000Z","completed_at":"2026-09-28T09:12:00.000Z","cancelled_at":null,"resend_count":0,"last_resent_at":null,"draft_filled_count":3,"draft_total_fields":5,"draft_updated_at":"2026-09-27T15:20:00.000Z","next_reminder_at":"2026-09-28T13:00:00.000Z","reminders_sent_count":0,"email_stats":{"sent_count":1,"delivered_count":1,"opened_count":1,"last_event_at":"2026-09-27T15:09:30.000Z"},"reply_received":false,"reply_at":null,"reply_snippet":null,"latest_review_status":"pending","review_approved_count":0,"review_total_fields":3,"completed_field_count":3,"created_by":"u_71bXq","created_by_name":"Priya Shah","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T15:20:00.000Z"},"submission":{"id":"sub_Qm3r","request_id":"req_9WkT","organization_id":"org_northwind","template_id":"tpl_W9onboard","responses":{"fld_company_name":"Northwind Dental LLC","fld_entity":"LLC"},"files":[{"field_id":"fld_insurance","original_filename":"liability-certificate-2026.pdf","storage_path":"organizations/org_northwind/collection-submissions/req_9WkT/fld_insurance-6c1e-liability-certificate-2026.pdf","content_type":"application/pdf","size_bytes":482113}],"submitted_at":"2026-09-28T09:12:00.000Z","review_status":"pending","field_reviews":{},"review_history":[],"revision":1},"template":{"id":"tpl_W9onboard","name":"New client onboarding - Northwind Dental","description":"Documents we need before the first engagement.","pages":[{"id":"p_company","title":"Company details","order":0,"description_html":"<p>Tell us about your practice.</p>","sections":[{"id":"s_basics","title":"Basics","order":0,"description_html":"","fields":[{"id":"fld_company_name","type":"short_text","label":"Legal company name","required":true,"help_text":""},{"id":"fld_entity","type":"dropdown","label":"Entity type","required":true,"help_text":"","options":["LLC","Corporation","Sole proprietor"]},{"id":"fld_insurance","type":"file_upload","label":"Liability insurance certificate","required":true,"help_text":"PDF or image, current policy year.","allowed_types":["pdf","jpg","png"],"max_size_bytes":26214400,"reference_file":{"storage_path":"organizations/org_northwind/collection-templates/tpl_W9onboard/fields/fld_insurance/reference-1b2c-sample.pdf","original_filename":"sample-certificate.pdf","content_type":"application/pdf","size_bytes":120331}}]}]}]},"reminders":[{"id":"rem_5Gh1","request_id":"req_9WkT","organization_id":"org_northwind","user_id":"u_71bXq","amount":3,"time_unit":"day","period":"after","time":"09:00","timezone":"America/New_York","reminder_date":"2026-09-30T14:05:00.000Z","scheduled_at":"2026-09-30T13:00:00.000Z","status":"skipped","sent_at":null,"error_message":"Request completed before this reminder was due","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-28T09:12:01.000Z"}],"email_events":[{"id":"resend_msg_81f_sent","request_id":"req_9WkT","reminder_id":null,"organization_id":"org_northwind","recipient_email":"dana@northwinddental.com","provider":"resend","provider_message_id":"msg_81f","event_type":"sent","timestamp":"2026-09-27T14:05:00.000Z","metadata":{"subject":"Northwind Dental requested documents from you","source":"send_call"}}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, or the record belongs to another organization (`{\"error\": \"Access denied\"}`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Access denied"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getCollectionRequest_post","summary":"Get a collection request with its submission (POST)","description":"Requires: bearer token; any role (admin, editor or viewer) in the organization.\nReturns the request plus its submission (or null), a template snapshot, its reminders and up to 100 email events (newest first). The handler does not check the HTTP method. The id is read from the query string only.","tags":["Document Collection"],"parameters":[{"name":"id","in":"query","required":true,"description":"Request id.","schema":{"type":"string"},"example":"req_9WkT"}],"responses":{"200":{"description":"Request details.","content":{"application/json":{"schema":{"type":"object","required":["request","submission","template","reminders","email_events"],"properties":{"request":{"$ref":"#/components/schemas/CollectionRequest"},"submission":{"oneOf":[{"$ref":"#/components/schemas/CollectionSubmissionDetail"},{"type":"null"}]},"template":{"type":["object","null"],"description":"The template (or request draft) the request was sent from; null if it was deleted.","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"pages":{"type":"array","items":{"$ref":"#/components/schemas/CollectionTemplatePage"}}}},"reminders":{"type":"array","items":{"$ref":"#/components/schemas/CollectionRequestReminder"}},"email_events":{"type":"array","items":{"$ref":"#/components/schemas/CollectionEmailEvent"}}}},"example":{"request":{"id":"req_9WkT","organization_id":"org_northwind","name":"Q4 insurance refresh","due_date":"2026-10-15T00:00:00.000Z","template_id":"tpl_W9onboard","template_name":"New client onboarding - Northwind Dental","contact_id":"c_8Hk2pQ","contact_name":"Dana Whitfield","contact_email":"dana@northwinddental.com","expiry_id":"exp_4Tq9sLm2","group_id":null,"has_password":false,"expires_at":"2026-10-31T23:59:59.000Z","status":"completed","archived":false,"paused":false,"sent_at":"2026-09-27T14:05:00.000Z","viewed_at":"2026-09-27T15:10:00.000Z","completed_at":"2026-09-28T09:12:00.000Z","cancelled_at":null,"resend_count":0,"last_resent_at":null,"draft_filled_count":3,"draft_total_fields":5,"draft_updated_at":"2026-09-27T15:20:00.000Z","next_reminder_at":"2026-09-28T13:00:00.000Z","reminders_sent_count":0,"email_stats":{"sent_count":1,"delivered_count":1,"opened_count":1,"last_event_at":"2026-09-27T15:09:30.000Z"},"reply_received":false,"reply_at":null,"reply_snippet":null,"latest_review_status":"pending","review_approved_count":0,"review_total_fields":3,"completed_field_count":3,"created_by":"u_71bXq","created_by_name":"Priya Shah","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T15:20:00.000Z"},"submission":{"id":"sub_Qm3r","request_id":"req_9WkT","organization_id":"org_northwind","template_id":"tpl_W9onboard","responses":{"fld_company_name":"Northwind Dental LLC","fld_entity":"LLC"},"files":[{"field_id":"fld_insurance","original_filename":"liability-certificate-2026.pdf","storage_path":"organizations/org_northwind/collection-submissions/req_9WkT/fld_insurance-6c1e-liability-certificate-2026.pdf","content_type":"application/pdf","size_bytes":482113}],"submitted_at":"2026-09-28T09:12:00.000Z","review_status":"pending","field_reviews":{},"review_history":[],"revision":1},"template":{"id":"tpl_W9onboard","name":"New client onboarding - Northwind Dental","description":"Documents we need before the first engagement.","pages":[{"id":"p_company","title":"Company details","order":0,"description_html":"<p>Tell us about your practice.</p>","sections":[{"id":"s_basics","title":"Basics","order":0,"description_html":"","fields":[{"id":"fld_company_name","type":"short_text","label":"Legal company name","required":true,"help_text":""},{"id":"fld_entity","type":"dropdown","label":"Entity type","required":true,"help_text":"","options":["LLC","Corporation","Sole proprietor"]},{"id":"fld_insurance","type":"file_upload","label":"Liability insurance certificate","required":true,"help_text":"PDF or image, current policy year.","allowed_types":["pdf","jpg","png"],"max_size_bytes":26214400,"reference_file":{"storage_path":"organizations/org_northwind/collection-templates/tpl_W9onboard/fields/fld_insurance/reference-1b2c-sample.pdf","original_filename":"sample-certificate.pdf","content_type":"application/pdf","size_bytes":120331}}]}]}]},"reminders":[{"id":"rem_5Gh1","request_id":"req_9WkT","organization_id":"org_northwind","user_id":"u_71bXq","amount":3,"time_unit":"day","period":"after","time":"09:00","timezone":"America/New_York","reminder_date":"2026-09-30T14:05:00.000Z","scheduled_at":"2026-09-30T13:00:00.000Z","status":"skipped","sent_at":null,"error_message":"Request completed before this reminder was due","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-28T09:12:01.000Z"}],"email_events":[{"id":"resend_msg_81f_sent","request_id":"req_9WkT","reminder_id":null,"organization_id":"org_northwind","recipient_email":"dana@northwinddental.com","provider":"resend","provider_message_id":"msg_81f","event_type":"sent","timestamp":"2026-09-27T14:05:00.000Z","metadata":{"subject":"Northwind Dental requested documents from you","source":"send_call"}}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, or the record belongs to another organization (`{\"error\": \"Access denied\"}`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Access denied"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getCollectionRequestReminders":{"get":{"operationId":"getCollectionRequestReminders","summary":"List a request's reminders","description":"Requires: bearer token; any role (admin, editor or viewer) in the organization.\nAll reminders for the request, soonest first. The handler does not check the HTTP method. `requestId` is read from the query string only.","tags":["Document Collection"],"parameters":[{"name":"requestId","in":"query","required":true,"schema":{"type":"string"},"example":"req_9WkT"}],"responses":{"200":{"description":"Reminders.","content":{"application/json":{"schema":{"type":"object","required":["reminders"],"properties":{"reminders":{"type":"array","items":{"$ref":"#/components/schemas/CollectionRequestReminder"}}}},"example":{"reminders":[{"id":"rem_5Gh1","request_id":"req_9WkT","organization_id":"org_northwind","user_id":"u_71bXq","amount":3,"time_unit":"day","period":"after","time":"09:00","timezone":"America/New_York","reminder_date":"2026-09-30T14:05:00.000Z","scheduled_at":"2026-09-30T13:00:00.000Z","status":"pending","sent_at":null,"error_message":null,"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, or the record belongs to another organization (`{\"error\": \"Access denied\"}`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Access denied"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getCollectionRequestReminders_post","summary":"List a request's reminders (POST)","description":"Requires: bearer token; any role (admin, editor or viewer) in the organization.\nAll reminders for the request, soonest first. The handler does not check the HTTP method. `requestId` is read from the query string only.","tags":["Document Collection"],"parameters":[{"name":"requestId","in":"query","required":true,"schema":{"type":"string"},"example":"req_9WkT"}],"responses":{"200":{"description":"Reminders.","content":{"application/json":{"schema":{"type":"object","required":["reminders"],"properties":{"reminders":{"type":"array","items":{"$ref":"#/components/schemas/CollectionRequestReminder"}}}},"example":{"reminders":[{"id":"rem_5Gh1","request_id":"req_9WkT","organization_id":"org_northwind","user_id":"u_71bXq","amount":3,"time_unit":"day","period":"after","time":"09:00","timezone":"America/New_York","reminder_date":"2026-09-30T14:05:00.000Z","scheduled_at":"2026-09-30T13:00:00.000Z","status":"pending","sent_at":null,"error_message":null,"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, or the record belongs to another organization (`{\"error\": \"Access denied\"}`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Access denied"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getCollectionSubmissionFile":{"get":{"operationId":"getCollectionSubmissionFile","summary":"Download one submitted file","description":"Requires: bearer token; any role (admin, editor or viewer) in the organization.\nStreams the file uploaded for `fieldId`, with the `Content-Type` recorded at upload. Pass `historyIndex` for a file replaced by a resubmission (`field_history[fieldId][historyIndex]`). Query string only. The handler does not check the HTTP method.","tags":["Document Collection"],"parameters":[{"name":"submissionId","in":"query","required":true,"schema":{"type":"string"},"example":"sub_Qm3r"},{"name":"fieldId","in":"query","required":true,"schema":{"type":"string"},"example":"fld_insurance"},{"name":"mode","in":"query","required":false,"description":"`download` sends `Content-Disposition: attachment`, anything else `inline`.","schema":{"type":"string","enum":["preview","download"],"default":"preview"}},{"name":"historyIndex","in":"query","required":false,"schema":{"type":"integer","minimum":0}}],"responses":{"200":{"description":"The file bytes. The actual `Content-Type` is the uploaded file type (for example `application/pdf` or `image/png`).","headers":{"Content-Disposition":{"description":"`inline` or `attachment`, with the original file name.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}},"content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, or the record belongs to another organization (`{\"error\": \"Access denied\"}`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Access denied"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Storage read failed mid-stream (no JSON body)."}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getCollectionSubmissionFile_post","summary":"Download one submitted file (POST)","description":"Requires: bearer token; any role (admin, editor or viewer) in the organization.\nStreams the file uploaded for `fieldId`, with the `Content-Type` recorded at upload. Pass `historyIndex` for a file replaced by a resubmission (`field_history[fieldId][historyIndex]`). Query string only. The handler does not check the HTTP method.","tags":["Document Collection"],"parameters":[{"name":"submissionId","in":"query","required":true,"schema":{"type":"string"},"example":"sub_Qm3r"},{"name":"fieldId","in":"query","required":true,"schema":{"type":"string"},"example":"fld_insurance"},{"name":"mode","in":"query","required":false,"description":"`download` sends `Content-Disposition: attachment`, anything else `inline`.","schema":{"type":"string","enum":["preview","download"],"default":"preview"}},{"name":"historyIndex","in":"query","required":false,"schema":{"type":"integer","minimum":0}}],"responses":{"200":{"description":"The file bytes. The actual `Content-Type` is the uploaded file type (for example `application/pdf` or `image/png`).","headers":{"Content-Disposition":{"description":"`inline` or `attachment`, with the original file name.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}},"content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, or the record belongs to another organization (`{\"error\": \"Access denied\"}`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Access denied"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Storage read failed mid-stream (no JSON body)."}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getCollectionTemplate":{"get":{"operationId":"getCollectionTemplate","summary":"Get a collection template","description":"Requires: bearer token; any role (admin, editor or viewer) in the organization.\nThe handler does not check the HTTP method. The id is read from the query string only.","tags":["Document Collection"],"parameters":[{"name":"id","in":"query","required":true,"description":"Template id.","schema":{"type":"string"},"example":"tpl_W9onboard"}],"responses":{"200":{"description":"The template.","content":{"application/json":{"schema":{"type":"object","required":["template"],"properties":{"template":{"$ref":"#/components/schemas/CollectionTemplate"}}},"example":{"template":{"id":"tpl_W9onboard","organization_id":"org_northwind","name":"New client onboarding - Northwind Dental","description":"Documents we need before the first engagement.","pages":[{"id":"p_company","title":"Company details","order":0,"description_html":"<p>Tell us about your practice.</p>","sections":[{"id":"s_basics","title":"Basics","order":0,"description_html":"","fields":[{"id":"fld_company_name","type":"short_text","label":"Legal company name","required":true,"help_text":""},{"id":"fld_entity","type":"dropdown","label":"Entity type","required":true,"help_text":"","options":["LLC","Corporation","Sole proprietor"]},{"id":"fld_insurance","type":"file_upload","label":"Liability insurance certificate","required":true,"help_text":"PDF or image, current policy year.","allowed_types":["pdf","jpg","png"],"max_size_bytes":26214400,"reference_file":{"storage_path":"organizations/org_northwind/collection-templates/tpl_W9onboard/fields/fld_insurance/reference-1b2c-sample.pdf","original_filename":"sample-certificate.pdf","content_type":"application/pdf","size_bytes":120331}}]}]}],"intro_email":{"body_html":"<p>Hi, please upload the documents below by Friday.</p>"},"schema_version":2,"status":"active","created_by":"u_71bXq","created_by_name":"Priya Shah","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, or the record belongs to another organization (`{\"error\": \"Access denied\"}`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Access denied"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getCollectionTemplate_post","summary":"Get a collection template (POST)","description":"Requires: bearer token; any role (admin, editor or viewer) in the organization.\nThe handler does not check the HTTP method. The id is read from the query string only.","tags":["Document Collection"],"parameters":[{"name":"id","in":"query","required":true,"description":"Template id.","schema":{"type":"string"},"example":"tpl_W9onboard"}],"responses":{"200":{"description":"The template.","content":{"application/json":{"schema":{"type":"object","required":["template"],"properties":{"template":{"$ref":"#/components/schemas/CollectionTemplate"}}},"example":{"template":{"id":"tpl_W9onboard","organization_id":"org_northwind","name":"New client onboarding - Northwind Dental","description":"Documents we need before the first engagement.","pages":[{"id":"p_company","title":"Company details","order":0,"description_html":"<p>Tell us about your practice.</p>","sections":[{"id":"s_basics","title":"Basics","order":0,"description_html":"","fields":[{"id":"fld_company_name","type":"short_text","label":"Legal company name","required":true,"help_text":""},{"id":"fld_entity","type":"dropdown","label":"Entity type","required":true,"help_text":"","options":["LLC","Corporation","Sole proprietor"]},{"id":"fld_insurance","type":"file_upload","label":"Liability insurance certificate","required":true,"help_text":"PDF or image, current policy year.","allowed_types":["pdf","jpg","png"],"max_size_bytes":26214400,"reference_file":{"storage_path":"organizations/org_northwind/collection-templates/tpl_W9onboard/fields/fld_insurance/reference-1b2c-sample.pdf","original_filename":"sample-certificate.pdf","content_type":"application/pdf","size_bytes":120331}}]}]}],"intro_email":{"body_html":"<p>Hi, please upload the documents below by Friday.</p>"},"schema_version":2,"status":"active","created_by":"u_71bXq","created_by_name":"Priya Shah","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, or the record belongs to another organization (`{\"error\": \"Access denied\"}`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Access denied"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getCollectionTemplateReferenceUploadUrl":{"post":{"operationId":"getCollectionTemplateReferenceUploadUrl","summary":"Get an upload URL for a field reference file","description":"Requires: bearer token; role editor or admin.\nReturns a V4 signed PUT URL valid for 15 minutes, bound to `contentType`. Send every header in `uploadHeaders` with the PUT. Max 2MB. Then put `{storagePath, originalFilename}` in the field's `reference_file` when saving the template. The handler does not check the HTTP method.","tags":["Document Collection"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["templateId","fieldId","filename","contentType","sizeBytes"],"properties":{"templateId":{"type":"string","pattern":"^[a-zA-Z0-9_-]+$","description":"Existing template id, or the client id you will pass to createCollectionTemplate."},"fieldId":{"type":"string","pattern":"^[a-zA-Z0-9_-]+$"},"filename":{"type":"string"},"contentType":{"type":"string","description":"One of the MIME types listed in `CollectionTemplateField.allowed_types`."},"sizeBytes":{"type":"integer","maximum":2097152}}},"example":{"templateId":"tpl_W9onboard","fieldId":"fld_insurance","filename":"sample-certificate.pdf","contentType":"application/pdf","sizeBytes":120331}}}},"responses":{"200":{"description":"Signed upload URL.","content":{"application/json":{"schema":{"type":"object","required":["uploadUrl","storagePath","uploadHeaders"],"properties":{"uploadUrl":{"type":"string","format":"uri"},"storagePath":{"type":"string"},"uploadHeaders":{"type":"object","additionalProperties":{"type":"string"},"description":"Send every one of these headers with the PUT. Always has `Content-Type`; also has the signed `x-goog-content-length-range` size cap (the size cap will be enforced - always send every header returned here)."}}},"example":{"uploadUrl":"https://storage.googleapis.com/stylingsphere.appspot.com/organizations/org_northwind/collection-templates/tpl_W9onboard/fields/fld_insurance/reference-1b2c-sample-certificate.pdf?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Expires=900&X-Goog-Signature=...","storagePath":"organizations/org_northwind/collection-templates/tpl_W9onboard/fields/fld_insurance/reference-1b2c-sample-certificate.pdf","uploadHeaders":{"Content-Type":"application/pdf"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getCollectionTriggerRules":{"get":{"operationId":"getCollectionTriggerRules","summary":"List trigger rules for an expiry","description":"Requires: bearer token; any role (admin, editor or viewer) in the organization.\nNon-deleted rules on the expiry, newest first; unbounded. The handler does not check the HTTP method. `expiryId` is read from the query string only.","tags":["Document Collection"],"parameters":[{"name":"expiryId","in":"query","required":true,"schema":{"type":"string"},"example":"exp_4Tq9sLm2"}],"responses":{"200":{"description":"Rules.","content":{"application/json":{"schema":{"type":"object","required":["triggers"],"properties":{"triggers":{"type":"array","items":{"$ref":"#/components/schemas/CollectionTriggerRule"}}}},"example":{"triggers":[{"id":"trg_2Vb8","organization_id":"org_northwind","expiry_id":"exp_4Tq9sLm2","expiry_name":"Northwind Dental - State Dental License","template_id":"tpl_W9onboard","template_name":"New client onboarding - Northwind Dental","trigger_type":"before_expiry","trigger_days":30,"trigger_time":"09:00","trigger_date":"2026-09-15T13:00:00.000Z","contact_ids":["c_8Hk2pQ"],"group_id":null,"group_name":null,"expires_in_days":21,"due_in_days":14,"status":"pending","created_request_ids":[],"run_count":0,"last_triggered_at":null,"triggered_by":null,"error_message":null,"carry_to_renewal":true,"created_by":"u_71bXq","created_by_name":"Priya Shah","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getCollectionTriggerRules_post","summary":"List trigger rules for an expiry (POST)","description":"Requires: bearer token; any role (admin, editor or viewer) in the organization.\nNon-deleted rules on the expiry, newest first; unbounded. The handler does not check the HTTP method. `expiryId` is read from the query string only.","tags":["Document Collection"],"parameters":[{"name":"expiryId","in":"query","required":true,"schema":{"type":"string"},"example":"exp_4Tq9sLm2"}],"responses":{"200":{"description":"Rules.","content":{"application/json":{"schema":{"type":"object","required":["triggers"],"properties":{"triggers":{"type":"array","items":{"$ref":"#/components/schemas/CollectionTriggerRule"}}}},"example":{"triggers":[{"id":"trg_2Vb8","organization_id":"org_northwind","expiry_id":"exp_4Tq9sLm2","expiry_name":"Northwind Dental - State Dental License","template_id":"tpl_W9onboard","template_name":"New client onboarding - Northwind Dental","trigger_type":"before_expiry","trigger_days":30,"trigger_time":"09:00","trigger_date":"2026-09-15T13:00:00.000Z","contact_ids":["c_8Hk2pQ"],"group_id":null,"group_name":null,"expires_in_days":21,"due_in_days":14,"status":"pending","created_request_ids":[],"run_count":0,"last_triggered_at":null,"triggered_by":null,"error_message":null,"carry_to_renewal":true,"created_by":"u_71bXq","created_by_name":"Priya Shah","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getScheduledCollectionTriggers":{"get":{"operationId":"getScheduledCollectionTriggers","summary":"List trigger rules across all expiries","description":"Requires: bearer token; any role (admin, editor or viewer) in the organization.\nNon-deleted rules with the given status plus per-status counts. `pending` and `all` are sorted by `trigger_date` (soonest first), others by last fired. Unbounded. The handler does not check the HTTP method. `status` is read from the query string only.","tags":["Document Collection"],"parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["pending","triggered","error","paused","cancelled","all"],"default":"pending"}}],"responses":{"200":{"description":"Rules.","content":{"application/json":{"schema":{"type":"object","required":["triggers","total","counts"],"properties":{"triggers":{"type":"array","items":{"$ref":"#/components/schemas/CollectionTriggerRule"}},"total":{"type":"integer","description":"Length of `triggers`."},"counts":{"type":"object","properties":{"pending":{"type":"integer"},"triggered":{"type":"integer"},"error":{"type":"integer"},"paused":{"type":"integer"},"cancelled":{"type":"integer"}}}}},"example":{"triggers":[{"id":"trg_2Vb8","organization_id":"org_northwind","expiry_id":"exp_4Tq9sLm2","expiry_name":"Northwind Dental - State Dental License","template_id":"tpl_W9onboard","template_name":"New client onboarding - Northwind Dental","trigger_type":"before_expiry","trigger_days":30,"trigger_time":"09:00","trigger_date":"2026-09-15T13:00:00.000Z","contact_ids":["c_8Hk2pQ"],"group_id":null,"group_name":null,"expires_in_days":21,"due_in_days":14,"status":"pending","created_request_ids":[],"run_count":0,"last_triggered_at":null,"triggered_by":null,"error_message":null,"carry_to_renewal":true,"created_by":"u_71bXq","created_by_name":"Priya Shah","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}],"total":1,"counts":{"pending":1,"triggered":3,"error":0,"paused":1,"cancelled":0}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getScheduledCollectionTriggers_post","summary":"List trigger rules across all expiries (POST)","description":"Requires: bearer token; any role (admin, editor or viewer) in the organization.\nNon-deleted rules with the given status plus per-status counts. `pending` and `all` are sorted by `trigger_date` (soonest first), others by last fired. Unbounded. The handler does not check the HTTP method. `status` is read from the query string only.","tags":["Document Collection"],"parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["pending","triggered","error","paused","cancelled","all"],"default":"pending"}}],"responses":{"200":{"description":"Rules.","content":{"application/json":{"schema":{"type":"object","required":["triggers","total","counts"],"properties":{"triggers":{"type":"array","items":{"$ref":"#/components/schemas/CollectionTriggerRule"}},"total":{"type":"integer","description":"Length of `triggers`."},"counts":{"type":"object","properties":{"pending":{"type":"integer"},"triggered":{"type":"integer"},"error":{"type":"integer"},"paused":{"type":"integer"},"cancelled":{"type":"integer"}}}}},"example":{"triggers":[{"id":"trg_2Vb8","organization_id":"org_northwind","expiry_id":"exp_4Tq9sLm2","expiry_name":"Northwind Dental - State Dental License","template_id":"tpl_W9onboard","template_name":"New client onboarding - Northwind Dental","trigger_type":"before_expiry","trigger_days":30,"trigger_time":"09:00","trigger_date":"2026-09-15T13:00:00.000Z","contact_ids":["c_8Hk2pQ"],"group_id":null,"group_name":null,"expires_in_days":21,"due_in_days":14,"status":"pending","created_request_ids":[],"run_count":0,"last_triggered_at":null,"triggered_by":null,"error_message":null,"carry_to_renewal":true,"created_by":"u_71bXq","created_by_name":"Priya Shah","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}],"total":1,"counts":{"pending":1,"triggered":3,"error":0,"paused":1,"cancelled":0}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/pauseCollectionRequest":{"post":{"operationId":"pauseCollectionRequest","summary":"Pause a request","description":"Requires: bearer token; role editor or admin.\nThe link returns 403 until resumed and reminders are skipped. Only `pending` or `viewed` requests can be paused. The handler does not check the HTTP method.","tags":["Document Collection"],"parameters":[{"name":"id","in":"query","required":false,"description":"Request id (or `id` in the body).","schema":{"type":"string"},"example":"req_9WkT"}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Request paused"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, or the record belongs to another organization (`{\"error\": \"Access denied\"}`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Access denied"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/resendCollectionRequest":{"post":{"operationId":"resendCollectionRequest","summary":"Resend a request (rotates the link)","description":"Requires: bearer token; role editor or admin.\nIssues a new link token (the old link stops working) and emails it. With `skipEmail: true` no email is sent and the new link is only returned (the web app's \"Copy link\"). Not allowed for paused, completed or cancelled requests. The handler does not check the HTTP method.","tags":["Document Collection"],"parameters":[{"name":"id","in":"query","required":false,"description":"Request id (or `id` in the body).","schema":{"type":"string"},"example":"req_9WkT"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"skipEmail":{"type":"boolean","default":false}}},"example":{"skipEmail":false}}}},"responses":{"200":{"description":"Link rotated.","content":{"application/json":{"schema":{"type":"object","required":["message","link"],"properties":{"message":{"type":"string"},"email_sent":{"type":"boolean","description":"Absent when `skipEmail` is true."},"link":{"type":"string","format":"uri","description":"The new recipient link. Treat it as a secret."}}},"example":{"message":"Reminder email sent","email_sent":true,"link":"https://app.expiryedge.com/collect/3f9a1c7e5b2d4f6a8c0e1b3d5f7a9c2e4b6d8f0a1c3e5b7d9f2a4c6e8b0d1f3a"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, or the record belongs to another organization (`{\"error\": \"Access denied\"}`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Access denied"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/resumeCollectionRequest":{"post":{"operationId":"resumeCollectionRequest","summary":"Resume a paused request","description":"Requires: bearer token; role editor or admin.\nSame link, status and saved progress as before the pause. The handler does not check the HTTP method.","tags":["Document Collection"],"parameters":[{"name":"id","in":"query","required":false,"description":"Request id (or `id` in the body).","schema":{"type":"string"},"example":"req_9WkT"}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Request resumed"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, or the record belongs to another organization (`{\"error\": \"Access denied\"}`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Access denied"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/reviewCollectionSubmission":{"post":{"operationId":"reviewCollectionSubmission","summary":"Approve a submission or request changes","description":"Requires: bearer token; role editor or admin.\n`approve` records the review. `request_changes` needs at least one rejected field; it reopens the request with a new link, re-seeds reminders and emails the recipient the comments. Entries for unknown fields are ignored. The handler does not check the HTTP method.","tags":["Document Collection"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["submissionId","decision","fieldReviews"],"properties":{"submissionId":{"type":"string"},"decision":{"type":"string","enum":["approve","request_changes"]},"fieldReviews":{"type":"array","items":{"type":"object","required":["fieldId","status"],"properties":{"fieldId":{"type":"string"},"status":{"type":"string","enum":["approved","rejected"]},"comment":{"type":"string","maxLength":1000}}}},"expectedRevision":{"type":"integer","description":"Optimistic concurrency: the submission `revision` you reviewed. 409 if it changed."}}},"example":{"submissionId":"sub_Qm3r","decision":"request_changes","expectedRevision":1,"fieldReviews":[{"fieldId":"fld_company_name","status":"approved"},{"fieldId":"fld_insurance","status":"rejected","comment":"This certificate expired in June. Please upload the current one."}]}}}},"responses":{"200":{"description":"Review saved.","content":{"application/json":{"schema":{"type":"object","required":["message"],"properties":{"message":{"type":"string"},"email_sent":{"type":"boolean","description":"Only for `request_changes`."}}},"example":{"message":"Changes requested - recipient notified","email_sent":true}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, or the record belongs to another organization (`{\"error\": \"Access denied\"}`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Access denied"}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"The submission was changed since `expectedRevision`.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"current_revision":{"type":"integer"}}},"example":{"error":"This submission was updated by someone else. Please reload and try again.","current_revision":2}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/triggerCollectionRuleNow":{"post":{"operationId":"triggerCollectionRuleNow","summary":"Fire a trigger rule now","description":"Requires: bearer token; role editor or admin.\nSends the rule's template to its recipients immediately (same checks and plan limit as createCollectionRequests) and marks it `triggered`. Rules that already fired return 400 until edited. Note the camelCase response fields. The handler does not check the HTTP method.","tags":["Document Collection"],"parameters":[{"name":"id","in":"query","required":false,"description":"Rule id (or `id` in the body).","schema":{"type":"string"},"example":"trg_2Vb8"}],"responses":{"200":{"description":"Sent.","content":{"application/json":{"schema":{"type":"object","required":["message","created","emailsSent","requestIds"],"properties":{"message":{"type":"string"},"created":{"type":"integer"},"emailsSent":{"type":"integer"},"requestIds":{"type":"array","items":{"type":"string"}},"linkedExpiry":{"type":["object","null"],"properties":{"id":{"type":"string"},"name":{"type":"string"}}}}},"example":{"message":"Sent to 1 recipient(s)","created":1,"emailsSent":1,"requestIds":["req_9WkT"],"linkedExpiry":{"id":"exp_4Tq9sLm2","name":"Northwind Dental - State Dental License"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Rule deleted or already fired, or the send failed (for example plan limit, no valid recipients, archived template). The rule is then marked `error` with the same message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"This trigger has already fired. Edit it to reset and fire it again."}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/unarchiveCollectionRequest":{"post":{"operationId":"unarchiveCollectionRequest","summary":"Unarchive a request","description":"Requires: bearer token; role editor or admin.\nSets `archived: false`. The handler does not check the HTTP method.","tags":["Document Collection"],"parameters":[{"name":"id","in":"query","required":false,"description":"Request id (or `id` in the body).","schema":{"type":"string"},"example":"req_9WkT"}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Request restored"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, or the record belongs to another organization (`{\"error\": \"Access denied\"}`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Access denied"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/unarchiveCollectionTemplate":{"post":{"operationId":"unarchiveCollectionTemplate","summary":"Restore an archived collection template","description":"Requires: bearer token; role editor or admin.\nCounts against the `collection_templates` plan limit. The handler does not check the HTTP method.","tags":["Document Collection"],"parameters":[{"name":"id","in":"query","required":false,"description":"Template id (or `id` in the body).","schema":{"type":"string"},"example":"tpl_W9onboard"}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Template restored"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, the record belongs to another organization (`Access denied`), or the plan limit is reached (body adds `current`, `limit`, `remaining`).","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/PlanLimitError"}]},"example":{"error":"You have reached the maximum number of collection templates for your plan.","current":5,"limit":5,"remaining":0}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateCollectionRequest":{"post":{"operationId":"updateCollectionRequest","summary":"Rename a request or change its due date","description":"Requires: bearer token; role editor or admin.\nOmit a field to leave it unchanged; send null or an empty string to clear it. Does not touch status, link or reminders. The handler does not check the HTTP method.","tags":["Document Collection"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"string"},"name":{"type":["string","null"],"maxLength":200},"dueDate":{"type":["string","null"],"format":"date-time"}}},"example":{"id":"req_9WkT","name":"Q4 insurance refresh","dueDate":"2026-10-15"}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Request updated"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, or the record belongs to another organization (`{\"error\": \"Access denied\"}`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Access denied"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateCollectionRequestReminders":{"post":{"operationId":"updateCollectionRequestReminders","summary":"Replace a request's reminder schedule","description":"Requires: bearer token; role editor or admin.\nReminders are matched to existing ones by `firestore_id` (or amount/unit), updated, created or deleted; `[]` removes all. Max 20, counted from the request's send time. The handler does not check the HTTP method.","tags":["Document Collection"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["requestId","reminders"],"properties":{"requestId":{"type":"string"},"reminders":{"type":"array","maxItems":20,"items":{"$ref":"#/components/schemas/CollectionReminderInput"}}}},"example":{"requestId":"req_9WkT","reminders":[{"firestore_id":"rem_5Gh1","amount":3,"time_unit":"day","time":"09:00"},{"amount":1,"time_unit":"week","time":"10:30"}]}}}},"responses":{"200":{"description":"Schedule updated.","content":{"application/json":{"schema":{"type":"object","required":["message","updated","created","deleted","reminders"],"properties":{"message":{"type":"string"},"updated":{"type":"integer"},"created":{"type":"integer"},"deleted":{"type":"integer"},"reminders":{"type":"array","items":{"$ref":"#/components/schemas/CollectionRequestReminder"}}}},"example":{"message":"Reminder schedule updated","updated":1,"created":1,"deleted":0,"reminders":[{"id":"rem_5Gh1","request_id":"req_9WkT","organization_id":"org_northwind","user_id":"u_71bXq","amount":3,"time_unit":"day","period":"after","time":"09:00","timezone":"America/New_York","reminder_date":"2026-09-30T14:05:00.000Z","scheduled_at":"2026-09-30T13:00:00.000Z","status":"pending","sent_at":null,"error_message":null,"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, or the record belongs to another organization (`{\"error\": \"Access denied\"}`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Access denied"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateCollectionTemplate":{"post":{"operationId":"updateCollectionTemplate","summary":"Update a collection template","description":"Requires: bearer token; role editor or admin.\nPartial update: send any of `name`, `description`, `pages`, `intro_email`. `pages` replaces the whole tree; reference files dropped from it are deleted. The id may be in the query string (as the web app sends it) or the body. The handler does not check the HTTP method.","tags":["Document Collection"],"parameters":[{"name":"id","in":"query","required":false,"description":"Template id (or `id` in the body).","schema":{"type":"string"},"example":"tpl_W9onboard"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"id":{"type":"string"}}},{"$ref":"#/components/schemas/CollectionTemplateInput"}]},"example":{"name":"Client onboarding 2026","description":"Updated for the 2026 policy year."}}}},"responses":{"200":{"description":"Template updated.","content":{"application/json":{"schema":{"type":"object","required":["message","template"],"properties":{"message":{"type":"string"},"template":{"$ref":"#/components/schemas/CollectionTemplate"}}},"example":{"message":"Template updated","template":{"id":"tpl_W9onboard","organization_id":"org_northwind","name":"Client onboarding 2026","description":"Documents we need before the first engagement.","pages":[{"id":"p_company","title":"Company details","order":0,"description_html":"<p>Tell us about your practice.</p>","sections":[{"id":"s_basics","title":"Basics","order":0,"description_html":"","fields":[{"id":"fld_company_name","type":"short_text","label":"Legal company name","required":true,"help_text":""},{"id":"fld_entity","type":"dropdown","label":"Entity type","required":true,"help_text":"","options":["LLC","Corporation","Sole proprietor"]},{"id":"fld_insurance","type":"file_upload","label":"Liability insurance certificate","required":true,"help_text":"PDF or image, current policy year.","allowed_types":["pdf","jpg","png"],"max_size_bytes":26214400,"reference_file":{"storage_path":"organizations/org_northwind/collection-templates/tpl_W9onboard/fields/fld_insurance/reference-1b2c-sample.pdf","original_filename":"sample-certificate.pdf","content_type":"application/pdf","size_bytes":120331}}]}]}],"intro_email":{"body_html":"<p>Hi, please upload the documents below by Friday.</p>"},"schema_version":2,"status":"active","created_by":"u_71bXq","created_by_name":"Priya Shah","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this, or the record belongs to another organization (`{\"error\": \"Access denied\"}`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Access denied"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateCollectionTriggerRule":{"post":{"operationId":"updateCollectionTriggerRule","summary":"Update a trigger rule","description":"Requires: bearer token; role editor or admin.\nSend any subset of the fields. Changing timing recomputes `trigger_date` and re-arms the rule (`pending`, or `paused` for manual) unless `status` is also sent. `status` accepts only `pending`, `paused` or `cancelled` (others are ignored). Rules in another organization return 404. The handler does not check the HTTP method.","tags":["Document Collection"],"parameters":[{"name":"id","in":"query","required":false,"description":"Rule id (or `id` in the body).","schema":{"type":"string"},"example":"trg_2Vb8"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionTriggerRuleUpdate"},"example":{"triggerDays":45,"status":"pending"}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Trigger updated"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/composeCollectionUpload":{"post":{"operationId":"composeCollectionUpload","summary":"Combine uploaded chunks into one file","description":"Public (no token); requires the collection request link token, plus the request password when one is set.\nSecond step of a chunked upload. `storagePath`, `uploadId` and `totalChunks` must be the values getCollectionUploadUrl returned. Fails with 400 if a chunk is missing or the combined file exceeds the field limit, 409 if already combined. The handler does not check the HTTP method.","tags":["Document Collection (Public)"],"parameters":[{"name":"X-Collection-Password","in":"header","required":false,"description":"Request password, when the sender set one. Preferred over the `password` body field / query parameter.","schema":{"type":"string"},"example":"harbor-42"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token","fieldId","uploadId","storagePath","totalChunks"],"properties":{"token":{"type":"string","pattern":"^[a-f0-9]{64}$","description":"The token from the request link."},"password":{"type":"string","writeOnly":true,"description":"Request password, when set (or use the `X-Collection-Password` header)."},"fieldId":{"type":"string","pattern":"^[a-zA-Z0-9_-]+$"},"uploadId":{"type":"string","format":"uuid"},"storagePath":{"type":"string"},"totalChunks":{"type":"integer","minimum":1,"maximum":32},"contentType":{"type":"string","description":"Final content type; must match the field's `allowed_types`."}}},"example":{"token":"3f9a1c7e5b2d4f6a8c0e1b3d5f7a9c2e4b6d8f0a1c3e5b7d9f2a4c6e8b0d1f3a","fieldId":"fld_insurance","uploadId":"0f8e2b3c-5d6a-4e7f-9a1b-2c3d4e5f6a7b","storagePath":"organizations/org_northwind/collection-submissions/req_9WkT/fld_insurance-0f8e2b3c-5d6a-4e7f-9a1b-2c3d4e5f6a7b-site-photos.pdf","totalChunks":3,"contentType":"application/pdf"}}}},"responses":{"200":{"description":"Combined.","content":{"application/json":{"schema":{"type":"object","required":["storagePath"],"properties":{"storagePath":{"type":"string"}}},"example":{"storagePath":"organizations/org_northwind/collection-submissions/req_9WkT/fld_insurance-0f8e2b3c-5d6a-4e7f-9a1b-2c3d4e5f6a7b-site-photos.pdf"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Malformed token (`Invalid link`) or invalid input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"Invalid link"}}}},"401":{"description":"The request has a password and it is missing (`{\"requiresPassword\": true}`) or wrong.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"That password didn't work.","requiresPassword":true}}}},"403":{"description":"The sender paused this request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"This request has been temporarily paused by the sender. Please check back later."}}}},"404":{"description":"Unknown token (link rotated, deleted or mistyped), or the template no longer exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"This link is invalid. Please check the URL or ask the sender for a new one."}}}},"409":{"description":"This upload was already combined.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"This upload has already been completed."}}}},"410":{"description":"The request was cancelled, already completed (`alreadySubmitted: true`) or has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"This link has already been used to submit documents.","alreadySubmitted":true}}}},"429":{"description":"Password locked out after 5 wrong attempts (15 minutes, per request). Also returned when the per-IP rate limit is exceeded (see Retry-After).","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CollectionPublicError"},{"$ref":"#/components/schemas/RateLimitError"}]}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"Something went wrong. Please try again."}}}}},"security":[],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getCollectionRequestPublic":{"get":{"operationId":"getCollectionRequestPublic","summary":"Open a request link (recipient view)","description":"Public (no token); requires the collection request link token, plus the request password when one is set.\nReturns what the fill page needs and marks a `pending` request `viewed`. `review` is set when the sender requested changes (only the rejected fields need answers). The handler does not check the HTTP method; `token` may also be sent in a JSON body.","tags":["Document Collection (Public)"],"parameters":[{"name":"token","in":"query","required":true,"description":"The 64-character hex token from the request link (`/collect/{token}`).","schema":{"type":"string","pattern":"^[a-f0-9]{64}$"},"example":"3f9a1c7e5b2d4f6a8c0e1b3d5f7a9c2e4b6d8f0a1c3e5b7d9f2a4c6e8b0d1f3a"},{"name":"X-Collection-Password","in":"header","required":false,"description":"Request password, when the sender set one. Preferred over the `password` body field / query parameter.","schema":{"type":"string"},"example":"harbor-42"},{"name":"password","in":"query","required":false,"deprecated":true,"description":"Legacy: request password in the query string (ends up in logs). Use the `X-Collection-Password` header instead.","schema":{"type":"string"}}],"responses":{"200":{"description":"Recipient view.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicView"},"example":{"request":{"status":"viewed","expires_at":"2026-10-31T23:59:59.000Z","recipient_name":"Dana Whitfield","expiry_linked":true},"review":null,"template":{"name":"New client onboarding - Northwind Dental","description":"Documents we need before the first engagement.","pages":[{"id":"p_company","title":"Company details","order":0,"description_html":"<p>Tell us about your practice.</p>","sections":[{"id":"s_basics","title":"Basics","order":0,"description_html":"","fields":[{"id":"fld_company_name","type":"short_text","label":"Legal company name","required":true,"help_text":""},{"id":"fld_entity","type":"dropdown","label":"Entity type","required":true,"help_text":"","options":["LLC","Corporation","Sole proprietor"]},{"id":"fld_insurance","type":"file_upload","label":"Liability insurance certificate","required":true,"help_text":"PDF or image, current policy year.","allowed_types":["pdf","jpg","png"],"max_size_bytes":26214400,"reference_file":{"original_filename":"sample-certificate.pdf","content_type":"application/pdf","size_bytes":120331}}]}]}]},"organization":{"name":"Contoso Legal","logo_url":null},"draft":{"responses":{"fld_company_name":"Northwind Dental LLC"},"files":{},"last_page_id":"p_company"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Malformed token (`Invalid link`) or invalid input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"Invalid link"}}}},"401":{"description":"The request has a password and it is missing (`{\"requiresPassword\": true}`) or wrong.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"That password didn't work.","requiresPassword":true}}}},"403":{"description":"The sender paused this request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"This request has been temporarily paused by the sender. Please check back later."}}}},"404":{"description":"Unknown token (link rotated, deleted or mistyped), or the template no longer exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"This link is invalid. Please check the URL or ask the sender for a new one."}}}},"410":{"description":"The request was cancelled, already completed (`alreadySubmitted: true`) or has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"This link has already been used to submit documents.","alreadySubmitted":true}}}},"429":{"description":"Password locked out after 5 wrong attempts (15 minutes, per request). Also returned when the per-IP rate limit is exceeded (see Retry-After).","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CollectionPublicError"},{"$ref":"#/components/schemas/RateLimitError"}]}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"Something went wrong. Please try again."}}}}},"security":[],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getCollectionRequestPublic_post","summary":"Open a request link (recipient view) (POST)","description":"Public (no token); requires the collection request link token, plus the request password when one is set.\nReturns what the fill page needs and marks a `pending` request `viewed`. `review` is set when the sender requested changes (only the rejected fields need answers). The handler does not check the HTTP method; `token` may also be sent in a JSON body.","tags":["Document Collection (Public)"],"parameters":[{"name":"X-Collection-Password","in":"header","required":false,"description":"Request password, when the sender set one. Preferred over the `password` body field / query parameter.","schema":{"type":"string"},"example":"harbor-42"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string","pattern":"^[a-f0-9]{64}$","description":"The token from the request link."},"password":{"type":"string","writeOnly":true,"description":"Request password, when set (or use the `X-Collection-Password` header)."}}},"example":{"token":"3f9a1c7e5b2d4f6a8c0e1b3d5f7a9c2e4b6d8f0a1c3e5b7d9f2a4c6e8b0d1f3a"}}}},"responses":{"200":{"description":"Recipient view.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicView"},"example":{"request":{"status":"viewed","expires_at":"2026-10-31T23:59:59.000Z","recipient_name":"Dana Whitfield","expiry_linked":true},"review":null,"template":{"name":"New client onboarding - Northwind Dental","description":"Documents we need before the first engagement.","pages":[{"id":"p_company","title":"Company details","order":0,"description_html":"<p>Tell us about your practice.</p>","sections":[{"id":"s_basics","title":"Basics","order":0,"description_html":"","fields":[{"id":"fld_company_name","type":"short_text","label":"Legal company name","required":true,"help_text":""},{"id":"fld_entity","type":"dropdown","label":"Entity type","required":true,"help_text":"","options":["LLC","Corporation","Sole proprietor"]},{"id":"fld_insurance","type":"file_upload","label":"Liability insurance certificate","required":true,"help_text":"PDF or image, current policy year.","allowed_types":["pdf","jpg","png"],"max_size_bytes":26214400,"reference_file":{"original_filename":"sample-certificate.pdf","content_type":"application/pdf","size_bytes":120331}}]}]}]},"organization":{"name":"Contoso Legal","logo_url":null},"draft":{"responses":{"fld_company_name":"Northwind Dental LLC"},"files":{},"last_page_id":"p_company"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Malformed token (`Invalid link`) or invalid input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"Invalid link"}}}},"401":{"description":"The request has a password and it is missing (`{\"requiresPassword\": true}`) or wrong.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"That password didn't work.","requiresPassword":true}}}},"403":{"description":"The sender paused this request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"This request has been temporarily paused by the sender. Please check back later."}}}},"404":{"description":"Unknown token (link rotated, deleted or mistyped), or the template no longer exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"This link is invalid. Please check the URL or ask the sender for a new one."}}}},"410":{"description":"The request was cancelled, already completed (`alreadySubmitted: true`) or has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"This link has already been used to submit documents.","alreadySubmitted":true}}}},"429":{"description":"Password locked out after 5 wrong attempts (15 minutes, per request). Also returned when the per-IP rate limit is exceeded (see Retry-After).","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CollectionPublicError"},{"$ref":"#/components/schemas/RateLimitError"}]}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"Something went wrong. Please try again."}}}}},"security":[],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getCollectionTemplateReferenceFile":{"get":{"operationId":"getCollectionTemplateReferenceFile","summary":"Download a field reference file (recipient)","description":"Public (no token); requires the collection request link token, plus the request password when one is set.\nStreams the example file the sender attached to a template field. `fieldId` and `mode` are read from the query string only. The handler does not check the HTTP method; `token` may also be sent in a JSON body.","tags":["Document Collection (Public)"],"parameters":[{"name":"token","in":"query","required":true,"description":"The 64-character hex token from the request link (`/collect/{token}`).","schema":{"type":"string","pattern":"^[a-f0-9]{64}$"},"example":"3f9a1c7e5b2d4f6a8c0e1b3d5f7a9c2e4b6d8f0a1c3e5b7d9f2a4c6e8b0d1f3a"},{"name":"fieldId","in":"query","required":true,"schema":{"type":"string"},"example":"fld_insurance"},{"name":"mode","in":"query","required":false,"description":"`download` sends `Content-Disposition: attachment`, anything else `inline`.","schema":{"type":"string","enum":["preview","download"],"default":"preview"}},{"name":"X-Collection-Password","in":"header","required":false,"description":"Request password, when the sender set one. Preferred over the `password` body field / query parameter.","schema":{"type":"string"},"example":"harbor-42"},{"name":"password","in":"query","required":false,"deprecated":true,"description":"Legacy: request password in the query string (ends up in logs). Use the `X-Collection-Password` header instead.","schema":{"type":"string"}}],"responses":{"200":{"description":"The file bytes, with the reference file's own `Content-Type` (for example `application/pdf`).","headers":{"Content-Disposition":{"description":"`inline` or `attachment`, with the original file name.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}},"content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Malformed token (`Invalid link`) or invalid input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"Invalid link"}}}},"401":{"description":"The request has a password and it is missing (`{\"requiresPassword\": true}`) or wrong.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"That password didn't work.","requiresPassword":true}}}},"403":{"description":"The sender paused this request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"This request has been temporarily paused by the sender. Please check back later."}}}},"404":{"description":"Unknown token, template gone, or the field has no reference file.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"No reference file for this field"}}}},"410":{"description":"The request was cancelled, already completed (`alreadySubmitted: true`) or has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"This link has already been used to submit documents.","alreadySubmitted":true}}}},"429":{"description":"Password locked out after 5 wrong attempts (15 minutes, per request). Also returned when the per-IP rate limit is exceeded (see Retry-After).","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CollectionPublicError"},{"$ref":"#/components/schemas/RateLimitError"}]}}}},"500":{"description":"Unexpected error, or the storage read failed mid-stream (no JSON body in that case).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"Something went wrong. Please try again."}}}}},"security":[],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getCollectionTemplateReferenceFile_post","summary":"Download a field reference file (recipient) (POST)","description":"Public (no token); requires the collection request link token, plus the request password when one is set.\nStreams the example file the sender attached to a template field. `fieldId` and `mode` are read from the query string only. The handler does not check the HTTP method; `token` may also be sent in a JSON body.","tags":["Document Collection (Public)"],"parameters":[{"name":"token","in":"query","required":true,"description":"The 64-character hex token from the request link (`/collect/{token}`).","schema":{"type":"string","pattern":"^[a-f0-9]{64}$"},"example":"3f9a1c7e5b2d4f6a8c0e1b3d5f7a9c2e4b6d8f0a1c3e5b7d9f2a4c6e8b0d1f3a"},{"name":"fieldId","in":"query","required":true,"schema":{"type":"string"},"example":"fld_insurance"},{"name":"mode","in":"query","required":false,"description":"`download` sends `Content-Disposition: attachment`, anything else `inline`.","schema":{"type":"string","enum":["preview","download"],"default":"preview"}},{"name":"X-Collection-Password","in":"header","required":false,"description":"Request password, when the sender set one. Preferred over the `password` body field / query parameter.","schema":{"type":"string"},"example":"harbor-42"},{"name":"password","in":"query","required":false,"deprecated":true,"description":"Legacy: request password in the query string (ends up in logs). Use the `X-Collection-Password` header instead.","schema":{"type":"string"}}],"responses":{"200":{"description":"The file bytes, with the reference file's own `Content-Type` (for example `application/pdf`).","headers":{"Content-Disposition":{"description":"`inline` or `attachment`, with the original file name.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}},"content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Malformed token (`Invalid link`) or invalid input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"Invalid link"}}}},"401":{"description":"The request has a password and it is missing (`{\"requiresPassword\": true}`) or wrong.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"That password didn't work.","requiresPassword":true}}}},"403":{"description":"The sender paused this request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"This request has been temporarily paused by the sender. Please check back later."}}}},"404":{"description":"Unknown token, template gone, or the field has no reference file.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"No reference file for this field"}}}},"410":{"description":"The request was cancelled, already completed (`alreadySubmitted: true`) or has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"This link has already been used to submit documents.","alreadySubmitted":true}}}},"429":{"description":"Password locked out after 5 wrong attempts (15 minutes, per request). Also returned when the per-IP rate limit is exceeded (see Retry-After).","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CollectionPublicError"},{"$ref":"#/components/schemas/RateLimitError"}]}}}},"500":{"description":"Unexpected error, or the storage read failed mid-stream (no JSON body in that case).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"Something went wrong. Please try again."}}}}},"security":[],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getCollectionUploadUrl":{"post":{"operationId":"getCollectionUploadUrl","summary":"Get upload URL(s) for a file field","description":"Public (no token); requires the collection request link token, plus the request password when one is set.\nFiles up to 20MB get one signed PUT URL (15 minutes, bound to `contentType`). Larger files (up to the field limit, max 500MB) get one signed PUT URL per 20MB chunk (24 hours, `Content-Type: application/octet-stream`); upload every chunk, then call composeCollectionUpload. Send every header in `uploadHeaders` with each PUT. Use the returned `storagePath` in submit/save calls. The handler does not check the HTTP method.","tags":["Document Collection (Public)"],"parameters":[{"name":"X-Collection-Password","in":"header","required":false,"description":"Request password, when the sender set one. Preferred over the `password` body field; the `password` query parameter is deprecated and will be rejected - use the header instead.","schema":{"type":"string"},"example":"harbor-42"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token","fieldId","filename","contentType","sizeBytes"],"properties":{"token":{"type":"string","pattern":"^[a-f0-9]{64}$","description":"The token from the request link."},"password":{"type":"string","writeOnly":true,"description":"Request password, when set (or use the `X-Collection-Password` header)."},"fieldId":{"type":"string","description":"A `file_upload` or `signature` field."},"filename":{"type":"string"},"contentType":{"type":"string","description":"Must match one of the field's `allowed_types`."},"sizeBytes":{"type":"integer","description":"Declared size; must not exceed the field's `max_size_bytes`."}}},"example":{"token":"3f9a1c7e5b2d4f6a8c0e1b3d5f7a9c2e4b6d8f0a1c3e5b7d9f2a4c6e8b0d1f3a","fieldId":"fld_insurance","filename":"liability-certificate-2026.pdf","contentType":"application/pdf","sizeBytes":482113}}}},"responses":{"200":{"description":"Upload instructions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionUploadUrlResult"},"example":{"chunked":false,"uploadUrl":"https://storage.googleapis.com/stylingsphere.appspot.com/organizations/org_northwind/collection-submissions/req_9WkT/fld_insurance-6c1e-liability-certificate-2026.pdf?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Expires=900&X-Goog-Signature=...","storagePath":"organizations/org_northwind/collection-submissions/req_9WkT/fld_insurance-6c1e-liability-certificate-2026.pdf","uploadHeaders":{"Content-Type":"application/pdf"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Malformed token (`Invalid link`) or invalid input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"Invalid link"}}}},"401":{"description":"The request has a password and it is missing (`{\"requiresPassword\": true}`) or wrong.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"That password didn't work.","requiresPassword":true}}}},"403":{"description":"The sender paused this request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"This request has been temporarily paused by the sender. Please check back later."}}}},"404":{"description":"Unknown token (link rotated, deleted or mistyped), or the template no longer exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"This link is invalid. Please check the URL or ask the sender for a new one."}}}},"410":{"description":"The request was cancelled, already completed (`alreadySubmitted: true`) or has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"This link has already been used to submit documents.","alreadySubmitted":true}}}},"429":{"description":"Password locked out after 5 wrong attempts (15 minutes, per request). Also returned when the per-IP rate limit is exceeded (see Retry-After).","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CollectionPublicError"},{"$ref":"#/components/schemas/RateLimitError"}]}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"Something went wrong. Please try again."}}}}},"security":[],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/resubmitCollectionRequestFields":{"post":{"operationId":"resubmitCollectionRequestFields","summary":"Resubmit the fields the reviewer rejected","description":"Public (no token); requires the collection request link token, plus the request password when one is set.\nOnly fields rejected in the last review are read; other values are kept. Previous answers move to `field_history`. Returns 200 (not 201). 410 when there is nothing to resubmit, 409 if the update was already submitted. The handler does not check the HTTP method.","tags":["Document Collection (Public)"],"parameters":[{"name":"X-Collection-Password","in":"header","required":false,"description":"Request password, when the sender set one. Preferred over the `password` body field / query parameter.","schema":{"type":"string"},"example":"harbor-42"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token","responses"],"properties":{"token":{"type":"string","pattern":"^[a-f0-9]{64}$","description":"The token from the request link."},"password":{"type":"string","writeOnly":true,"description":"Request password, when set (or use the `X-Collection-Password` header)."},"responses":{"$ref":"#/components/schemas/CollectionResponses"},"files":{"type":"array","items":{"$ref":"#/components/schemas/CollectionUploadedFileRef"}}}},"example":{"token":"3f9a1c7e5b2d4f6a8c0e1b3d5f7a9c2e4b6d8f0a1c3e5b7d9f2a4c6e8b0d1f3a","responses":{},"files":[{"fieldId":"fld_insurance","storagePath":"organizations/org_northwind/collection-submissions/req_9WkT/fld_insurance-9d2a-liability-certificate-2026-renewed.pdf","originalFilename":"liability-certificate-2026-renewed.pdf"}]}}}},"responses":{"200":{"description":"Updates submitted.","content":{"application/json":{"schema":{"type":"object","required":["message","submission_id"],"properties":{"message":{"type":"string"},"submission_id":{"type":"string"}}},"example":{"message":"Updates submitted","submission_id":"sub_Qm3r"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Malformed token (`Invalid link`) or invalid input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"Invalid link"}}}},"401":{"description":"The request has a password and it is missing (`{\"requiresPassword\": true}`) or wrong.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"That password didn't work.","requiresPassword":true}}}},"403":{"description":"The sender paused this request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"This request has been temporarily paused by the sender. Please check back later."}}}},"404":{"description":"Unknown token (link rotated, deleted or mistyped), or the template no longer exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"This link is invalid. Please check the URL or ask the sender for a new one."}}}},"409":{"description":"These updates were already submitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"These updates were already submitted.","alreadySubmitted":true}}}},"410":{"description":"The request was cancelled, already completed (`alreadySubmitted: true`) or has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"This link has already been used to submit documents.","alreadySubmitted":true}}}},"429":{"description":"Password locked out after 5 wrong attempts (15 minutes, per request). Also returned when the per-IP rate limit is exceeded (see Retry-After).","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CollectionPublicError"},{"$ref":"#/components/schemas/RateLimitError"}]}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"Something went wrong. Please try again."}}}}},"security":[],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/saveCollectionRequestDraft":{"post":{"operationId":"saveCollectionRequestDraft","summary":"Autosave the recipient's progress","description":"Public (no token); requires the collection request link token, plus the request password when one is set.\nReplaces the saved draft (answers, uploaded file references and current page). Invalid values are dropped silently rather than rejected. Returns 410 once the request is no longer open. The handler does not check the HTTP method.","tags":["Document Collection (Public)"],"parameters":[{"name":"X-Collection-Password","in":"header","required":false,"description":"Request password, when the sender set one. Preferred over the `password` body field / query parameter.","schema":{"type":"string"},"example":"harbor-42"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string","pattern":"^[a-f0-9]{64}$","description":"The token from the request link."},"password":{"type":"string","writeOnly":true,"description":"Request password, when set (or use the `X-Collection-Password` header)."},"responses":{"$ref":"#/components/schemas/CollectionResponses"},"files":{"type":"array","items":{"$ref":"#/components/schemas/CollectionUploadedFileRef"}},"lastPageId":{"type":"string","maxLength":200}}},"example":{"token":"3f9a1c7e5b2d4f6a8c0e1b3d5f7a9c2e4b6d8f0a1c3e5b7d9f2a4c6e8b0d1f3a","responses":{"fld_company_name":"Northwind Dental LLC"},"files":[],"lastPageId":"p_company"}}}},"responses":{"200":{"description":"Draft saved.","content":{"application/json":{"schema":{"type":"object","required":["message","filled_count","total_fields"],"properties":{"message":{"type":"string"},"filled_count":{"type":"integer"},"total_fields":{"type":"integer"}}},"example":{"message":"Draft saved","filled_count":1,"total_fields":3}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Malformed token (`Invalid link`) or invalid input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"Invalid link"}}}},"401":{"description":"The request has a password and it is missing (`{\"requiresPassword\": true}`) or wrong.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"That password didn't work.","requiresPassword":true}}}},"403":{"description":"The sender paused this request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"This request has been temporarily paused by the sender. Please check back later."}}}},"404":{"description":"Unknown token (link rotated, deleted or mistyped), or the template no longer exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"This link is invalid. Please check the URL or ask the sender for a new one."}}}},"410":{"description":"The request was cancelled, already completed (`alreadySubmitted: true`) or has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"This link has already been used to submit documents.","alreadySubmitted":true}}}},"429":{"description":"Password locked out after 5 wrong attempts (15 minutes, per request). Also returned when the per-IP rate limit is exceeded (see Retry-After).","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CollectionPublicError"},{"$ref":"#/components/schemas/RateLimitError"}]}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"Something went wrong. Please try again."}}}}},"security":[],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/submitCollectionRequest":{"post":{"operationId":"submitCollectionRequest","summary":"Submit the form","description":"Public (no token); requires the collection request link token, plus the request password when one is set.\nValidates every field (required, options, dates, numbers) and re-checks each uploaded file's type and size in storage, then creates the submission and completes the request. After \"Request changes\" use resubmitCollectionRequestFields instead (this returns 409). The handler does not check the HTTP method.","tags":["Document Collection (Public)"],"parameters":[{"name":"X-Collection-Password","in":"header","required":false,"description":"Request password, when the sender set one. Preferred over the `password` body field / query parameter.","schema":{"type":"string"},"example":"harbor-42"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token","responses"],"properties":{"token":{"type":"string","pattern":"^[a-f0-9]{64}$","description":"The token from the request link."},"password":{"type":"string","writeOnly":true,"description":"Request password, when set (or use the `X-Collection-Password` header)."},"responses":{"$ref":"#/components/schemas/CollectionResponses"},"files":{"type":"array","items":{"$ref":"#/components/schemas/CollectionUploadedFileRef"}}}},"example":{"token":"3f9a1c7e5b2d4f6a8c0e1b3d5f7a9c2e4b6d8f0a1c3e5b7d9f2a4c6e8b0d1f3a","responses":{"fld_company_name":"Northwind Dental LLC","fld_entity":"LLC"},"files":[{"fieldId":"fld_insurance","storagePath":"organizations/org_northwind/collection-submissions/req_9WkT/fld_insurance-6c1e-liability-certificate-2026.pdf","originalFilename":"liability-certificate-2026.pdf"}]}}}},"responses":{"201":{"description":"Submission received.","content":{"application/json":{"schema":{"type":"object","required":["message","submission_id"],"properties":{"message":{"type":"string"},"submission_id":{"type":"string"}}},"example":{"message":"Submission received","submission_id":"sub_Qm3r"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Malformed token (`Invalid link`) or invalid input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"Invalid link"}}}},"401":{"description":"The request has a password and it is missing (`{\"requiresPassword\": true}`) or wrong.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"That password didn't work.","requiresPassword":true}}}},"403":{"description":"The sender paused this request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"This request has been temporarily paused by the sender. Please check back later."}}}},"404":{"description":"Unknown token (link rotated, deleted or mistyped), or the template no longer exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"This link is invalid. Please check the URL or ask the sender for a new one."}}}},"409":{"description":"A submission already exists for this request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"A response was already submitted for this request.","alreadySubmitted":true}}}},"410":{"description":"The request was cancelled, already completed (`alreadySubmitted: true`) or has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"This link has already been used to submit documents.","alreadySubmitted":true}}}},"429":{"description":"Password locked out after 5 wrong attempts (15 minutes, per request). Also returned when the per-IP rate limit is exceeded (see Retry-After).","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/CollectionPublicError"},{"$ref":"#/components/schemas/RateLimitError"}]}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionPublicError"},"example":{"error":"Something went wrong. Please try again."}}}}},"security":[],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/archiveCompliance":{"post":{"operationId":"archiveCompliance","summary":"Archive a compliance catalog entry","description":"Requires: bearer token; role editor or admin.\nPass `id` in the query string or the JSON body.","tags":["Compliance Clients & Projects"],"parameters":[{"name":"id","in":"query","required":false,"description":"Compliance id.","schema":{"type":"string"},"example":"cm_9Pq4zR"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"}}},"example":{"id":"cm_9Pq4zR"}}}},"responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Compliance archived successfully."}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/archiveComplianceClient":{"post":{"operationId":"archiveComplianceClient","summary":"Archive a compliance client","description":"Requires: bearer token; role editor or admin.\nPass `id` in the query string or the JSON body.","tags":["Compliance Clients & Projects"],"parameters":[{"name":"id","in":"query","required":false,"description":"Client id.","schema":{"type":"string"},"example":"cc_7Rt2vQ"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"}}},"example":{"id":"cc_7Rt2vQ"}}}},"responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Client archived successfully."}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/archiveComplianceProject":{"post":{"operationId":"archiveComplianceProject","summary":"Archive a compliance project","description":"Requires: bearer token; role editor or admin.\nPass `id` in the query string or the JSON body.","tags":["Compliance Clients & Projects"],"parameters":[{"name":"id","in":"query","required":false,"description":"Project id.","schema":{"type":"string"},"example":"cp_3Mn8wX"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"}}},"example":{"id":"cp_3Mn8wX"}}}},"responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Project archived successfully."}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/createCompliance":{"post":{"operationId":"createCompliance","summary":"Create a compliance catalog entry","description":"Requires: bearer token; role editor or admin.\nNo plan limit applies.","tags":["Compliance Clients & Projects"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ComplianceInput"}],"required":["name"]},"example":{"name":"HIPAA Security Risk Assessment","description":"Annual security risk analysis required under the HIPAA Security Rule.","category":"Data Privacy","renewalFrequency":"Annually","notes":"Keep the signed report for six years.","status":"Active","customFields":{"regulator":"HHS OCR"}}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"compliance":{"$ref":"#/components/schemas/Compliance"}}},"example":{"message":"Compliance created successfully.","compliance":{"id":"cm_9Pq4zR","name":"HIPAA Security Risk Assessment","description":"Annual security risk analysis required under the HIPAA Security Rule.","category":"Data Privacy","customCategory":"","renewalFrequency":"Annually","notes":"Keep the signed report for six years.","status":"Active","customFields":{"regulator":"HHS OCR"},"isArchived":false,"archivedAt":null,"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/IdempotencyInProgress"},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/createComplianceClient":{"post":{"operationId":"createComplianceClient","summary":"Create a compliance client","description":"Requires: bearer token; role editor or admin.\nNo plan limit applies.","tags":["Compliance Clients & Projects"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ComplianceClientInput"}],"required":["name"]},"example":{"name":"Northwind Dental","primaryContactName":"Priya Shah","email":"priya@northwinddental.example","phone":"+1 555 0142","website":"https://northwinddental.example","address":"12 Harbor Way, Portland, OR","industry":"Healthcare","status":"Active","notes":"Annual license audit in October.","customFields":{"account_manager":"Dana Lee"}}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"client":{"$ref":"#/components/schemas/ComplianceClient"}}},"example":{"message":"Client created successfully.","client":{"id":"cc_7Rt2vQ","name":"Northwind Dental","primaryContactName":"Priya Shah","email":"priya@northwinddental.example","phone":"+1 555 0142","website":"https://northwinddental.example","address":"12 Harbor Way, Portland, OR","industry":"Healthcare","status":"Active","notes":"Annual license audit in October.","customFields":{"account_manager":"Dana Lee"},"isArchived":false,"archivedAt":null,"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/IdempotencyInProgress"},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/createComplianceProject":{"post":{"operationId":"createComplianceProject","summary":"Create a compliance project","description":"Requires: bearer token; role editor or admin.\nReturns 400 when `clientId` is missing or not a client in your organization.","tags":["Compliance Clients & Projects"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ComplianceProjectInput"}],"required":["name","clientId"]},"example":{"name":"Northwind Dental - 2026 License Renewals","clientId":"cc_7Rt2vQ","status":"In Progress","startDate":"2026-09-01","dueDate":"2026-10-15","description":"Collect and renew all state dental licenses.","owner":"Priya Shah","customFields":{"board":"State Board of Dentistry"}}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"project":{"$ref":"#/components/schemas/ComplianceProject"}}},"example":{"message":"Project created successfully.","project":{"id":"cp_3Mn8wX","name":"Northwind Dental - 2026 License Renewals","clientId":"cc_7Rt2vQ","status":"In Progress","startDate":"2026-09-01","dueDate":"2026-10-15","description":"Collect and renew all state dental licenses.","owner":"Priya Shah","customFields":{"board":"State Board of Dentistry"},"isArchived":false,"archivedAt":null,"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/IdempotencyInProgress"},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deleteCompliance":{"post":{"operationId":"deleteCompliance_post","summary":"Delete a compliance catalog entry (POST)","description":"Requires: bearer token; role editor or admin.\nPermanently deletes the record. Pass `id` in the query string (or the JSON body).","tags":["Compliance Clients & Projects"],"parameters":[{"name":"id","in":"query","required":false,"description":"Compliance id.","schema":{"type":"string"},"example":"cm_9Pq4zR"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"}}},"example":{"id":"cm_9Pq4zR"}}}},"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Compliance deleted successfully."}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"delete":{"operationId":"deleteCompliance","summary":"Delete a compliance catalog entry","description":"Requires: bearer token; role editor or admin.\nPermanently deletes the record. Pass `id` in the query string (or the JSON body).","tags":["Compliance Clients & Projects"],"parameters":[{"name":"id","in":"query","required":false,"description":"Compliance id.","schema":{"type":"string"},"example":"cm_9Pq4zR"}],"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Compliance deleted successfully."}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deleteComplianceClient":{"post":{"operationId":"deleteComplianceClient_post","summary":"Delete a compliance client (POST)","description":"Requires: bearer token; role editor or admin.\nPermanently deletes the record. Pass `id` in the query string (or the JSON body). Returns 409 `CLIENT_HAS_PROJECTS` while projects reference the client.","tags":["Compliance Clients & Projects"],"parameters":[{"name":"id","in":"query","required":false,"description":"Client id.","schema":{"type":"string"},"example":"cc_7Rt2vQ"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"}}},"example":{"id":"cc_7Rt2vQ"}}}},"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Client deleted successfully."}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"The client still has projects. Delete or reassign them first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"This client has projects associated with it. Delete or reassign those projects first.","code":"CLIENT_HAS_PROJECTS"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"delete":{"operationId":"deleteComplianceClient","summary":"Delete a compliance client","description":"Requires: bearer token; role editor or admin.\nPermanently deletes the record. Pass `id` in the query string (or the JSON body). Returns 409 `CLIENT_HAS_PROJECTS` while projects reference the client.","tags":["Compliance Clients & Projects"],"parameters":[{"name":"id","in":"query","required":false,"description":"Client id.","schema":{"type":"string"},"example":"cc_7Rt2vQ"}],"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Client deleted successfully."}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"The client still has projects. Delete or reassign them first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"This client has projects associated with it. Delete or reassign those projects first.","code":"CLIENT_HAS_PROJECTS"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deleteComplianceProject":{"post":{"operationId":"deleteComplianceProject_post","summary":"Delete a compliance project (POST)","description":"Requires: bearer token; role editor or admin.\nPermanently deletes the record. Pass `id` in the query string (or the JSON body).","tags":["Compliance Clients & Projects"],"parameters":[{"name":"id","in":"query","required":false,"description":"Project id.","schema":{"type":"string"},"example":"cp_3Mn8wX"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"}}},"example":{"id":"cp_3Mn8wX"}}}},"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Project deleted successfully."}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"delete":{"operationId":"deleteComplianceProject","summary":"Delete a compliance project","description":"Requires: bearer token; role editor or admin.\nPermanently deletes the record. Pass `id` in the query string (or the JSON body).","tags":["Compliance Clients & Projects"],"parameters":[{"name":"id","in":"query","required":false,"description":"Project id.","schema":{"type":"string"},"example":"cp_3Mn8wX"}],"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Project deleted successfully."}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getAllComplianceClients":{"get":{"operationId":"getAllComplianceClients","summary":"List clients","description":"Requires: bearer token; any role (admin, editor or viewer).\nReturns all of the organization's clients, newest first; archived records are excluded unless `includeArchived=true`. Unbounded (no pagination).","tags":["Compliance Clients & Projects"],"parameters":[{"name":"includeArchived","in":"query","required":false,"description":"Set to `true` to include archived records.","schema":{"type":"string","enum":["true","false"]},"example":"true"}],"responses":{"200":{"description":"Array of clients.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ComplianceClient"}},"example":[{"id":"cc_7Rt2vQ","name":"Northwind Dental","primaryContactName":"Priya Shah","email":"priya@northwinddental.example","phone":"+1 555 0142","website":"https://northwinddental.example","address":"12 Harbor Way, Portland, OR","industry":"Healthcare","status":"Active","notes":"Annual license audit in October.","customFields":{"account_manager":"Dana Lee"},"isArchived":false,"archivedAt":null,"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}]}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getAllComplianceClients_post","summary":"List clients (POST)","description":"Requires: bearer token; any role (admin, editor or viewer).\nSame as the GET form; filters are read from the JSON body (`?includeArchived=true` is also honored).","tags":["Compliance Clients & Projects"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"includeArchived":{"type":"boolean"}}},"example":{"includeArchived":true}}}},"responses":{"200":{"description":"Array of clients.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ComplianceClient"}},"example":[{"id":"cc_7Rt2vQ","name":"Northwind Dental","primaryContactName":"Priya Shah","email":"priya@northwinddental.example","phone":"+1 555 0142","website":"https://northwinddental.example","address":"12 Harbor Way, Portland, OR","industry":"Healthcare","status":"Active","notes":"Annual license audit in October.","customFields":{"account_manager":"Dana Lee"},"isArchived":false,"archivedAt":null,"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}]}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getAllComplianceProjects":{"get":{"operationId":"getAllComplianceProjects","summary":"List projects","description":"Requires: bearer token; any role (admin, editor or viewer).\nReturns all of the organization's projects, newest first; archived records are excluded unless `includeArchived=true`. Unbounded (no pagination).","tags":["Compliance Clients & Projects"],"parameters":[{"name":"clientId","in":"query","required":false,"description":"Only projects of this client.","schema":{"type":"string"},"example":"cc_7Rt2vQ"},{"name":"includeArchived","in":"query","required":false,"description":"Set to `true` to include archived records.","schema":{"type":"string","enum":["true","false"]},"example":"true"}],"responses":{"200":{"description":"Array of projects.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ComplianceProject"}},"example":[{"id":"cp_3Mn8wX","name":"Northwind Dental - 2026 License Renewals","clientId":"cc_7Rt2vQ","status":"In Progress","startDate":"2026-09-01","dueDate":"2026-10-15","description":"Collect and renew all state dental licenses.","owner":"Priya Shah","customFields":{"board":"State Board of Dentistry"},"isArchived":false,"archivedAt":null,"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}]}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getAllComplianceProjects_post","summary":"List projects (POST)","description":"Requires: bearer token; any role (admin, editor or viewer).\nSame as the GET form; filters are read from the JSON body.","tags":["Compliance Clients & Projects"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"includeArchived":{"type":"boolean"},"clientId":{"type":"string"}}},"example":{"clientId":"cc_7Rt2vQ","includeArchived":true}}}},"responses":{"200":{"description":"Array of projects.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ComplianceProject"}},"example":[{"id":"cp_3Mn8wX","name":"Northwind Dental - 2026 License Renewals","clientId":"cc_7Rt2vQ","status":"In Progress","startDate":"2026-09-01","dueDate":"2026-10-15","description":"Collect and renew all state dental licenses.","owner":"Priya Shah","customFields":{"board":"State Board of Dentistry"},"isArchived":false,"archivedAt":null,"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}]}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getAllCompliances":{"get":{"operationId":"getAllCompliances","summary":"List compliance catalog entries","description":"Requires: bearer token; any role (admin, editor or viewer).\nReturns all of the organization's compliance catalog entries, newest first; archived records are excluded unless `includeArchived=true`. Unbounded (no pagination).","tags":["Compliance Clients & Projects"],"parameters":[{"name":"includeArchived","in":"query","required":false,"description":"Set to `true` to include archived records.","schema":{"type":"string","enum":["true","false"]},"example":"true"}],"responses":{"200":{"description":"Array of compliance catalog entries.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Compliance"}},"example":[{"id":"cm_9Pq4zR","name":"HIPAA Security Risk Assessment","description":"Annual security risk analysis required under the HIPAA Security Rule.","category":"Data Privacy","customCategory":"","renewalFrequency":"Annually","notes":"Keep the signed report for six years.","status":"Active","customFields":{"regulator":"HHS OCR"},"isArchived":false,"archivedAt":null,"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}]}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getAllCompliances_post","summary":"List compliance catalog entries (POST)","description":"Requires: bearer token; any role (admin, editor or viewer).\nSame as the GET form; filters are read from the JSON body (`?includeArchived=true` is also honored).","tags":["Compliance Clients & Projects"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"includeArchived":{"type":"boolean"}}},"example":{"includeArchived":true}}}},"responses":{"200":{"description":"Array of compliance catalog entries.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Compliance"}},"example":[{"id":"cm_9Pq4zR","name":"HIPAA Security Risk Assessment","description":"Annual security risk analysis required under the HIPAA Security Rule.","category":"Data Privacy","customCategory":"","renewalFrequency":"Annually","notes":"Keep the signed report for six years.","status":"Active","customFields":{"regulator":"HHS OCR"},"isArchived":false,"archivedAt":null,"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}]}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getCompliance":{"get":{"operationId":"getCompliance","summary":"Get a compliance catalog entry","description":"Requires: bearer token; any role (admin, editor or viewer).\nReturns 403 when the record belongs to another organization.","tags":["Compliance Clients & Projects"],"parameters":[{"name":"id","in":"query","required":true,"description":"Compliance id.","schema":{"type":"string"},"example":"cm_9Pq4zR"}],"responses":{"200":{"description":"The compliance catalog entry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Compliance"},"example":{"id":"cm_9Pq4zR","name":"HIPAA Security Risk Assessment","description":"Annual security risk analysis required under the HIPAA Security Rule.","category":"Data Privacy","customCategory":"","renewalFrequency":"Annually","notes":"Keep the signed report for six years.","status":"Active","customFields":{"regulator":"HHS OCR"},"isArchived":false,"archivedAt":null,"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getCompliance_post","summary":"Get a compliance catalog entry (POST)","description":"Requires: bearer token; any role (admin, editor or viewer).\nSame as the GET form; `id` must still be sent in the query string.","tags":["Compliance Clients & Projects"],"parameters":[{"name":"id","in":"query","required":true,"description":"Compliance id.","schema":{"type":"string"},"example":"cm_9Pq4zR"}],"responses":{"200":{"description":"The compliance catalog entry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Compliance"},"example":{"id":"cm_9Pq4zR","name":"HIPAA Security Risk Assessment","description":"Annual security risk analysis required under the HIPAA Security Rule.","category":"Data Privacy","customCategory":"","renewalFrequency":"Annually","notes":"Keep the signed report for six years.","status":"Active","customFields":{"regulator":"HHS OCR"},"isArchived":false,"archivedAt":null,"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getComplianceClient":{"get":{"operationId":"getComplianceClient","summary":"Get a compliance client","description":"Requires: bearer token; any role (admin, editor or viewer).\nReturns 403 when the record belongs to another organization.","tags":["Compliance Clients & Projects"],"parameters":[{"name":"id","in":"query","required":true,"description":"Client id.","schema":{"type":"string"},"example":"cc_7Rt2vQ"}],"responses":{"200":{"description":"The compliance client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComplianceClient"},"example":{"id":"cc_7Rt2vQ","name":"Northwind Dental","primaryContactName":"Priya Shah","email":"priya@northwinddental.example","phone":"+1 555 0142","website":"https://northwinddental.example","address":"12 Harbor Way, Portland, OR","industry":"Healthcare","status":"Active","notes":"Annual license audit in October.","customFields":{"account_manager":"Dana Lee"},"isArchived":false,"archivedAt":null,"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getComplianceClient_post","summary":"Get a compliance client (POST)","description":"Requires: bearer token; any role (admin, editor or viewer).\nSame as the GET form; `id` must still be sent in the query string.","tags":["Compliance Clients & Projects"],"parameters":[{"name":"id","in":"query","required":true,"description":"Client id.","schema":{"type":"string"},"example":"cc_7Rt2vQ"}],"responses":{"200":{"description":"The compliance client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComplianceClient"},"example":{"id":"cc_7Rt2vQ","name":"Northwind Dental","primaryContactName":"Priya Shah","email":"priya@northwinddental.example","phone":"+1 555 0142","website":"https://northwinddental.example","address":"12 Harbor Way, Portland, OR","industry":"Healthcare","status":"Active","notes":"Annual license audit in October.","customFields":{"account_manager":"Dana Lee"},"isArchived":false,"archivedAt":null,"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getComplianceProject":{"get":{"operationId":"getComplianceProject","summary":"Get a compliance project","description":"Requires: bearer token; any role (admin, editor or viewer).\nReturns 403 when the record belongs to another organization.","tags":["Compliance Clients & Projects"],"parameters":[{"name":"id","in":"query","required":true,"description":"Project id.","schema":{"type":"string"},"example":"cp_3Mn8wX"}],"responses":{"200":{"description":"The compliance project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComplianceProject"},"example":{"id":"cp_3Mn8wX","name":"Northwind Dental - 2026 License Renewals","clientId":"cc_7Rt2vQ","status":"In Progress","startDate":"2026-09-01","dueDate":"2026-10-15","description":"Collect and renew all state dental licenses.","owner":"Priya Shah","customFields":{"board":"State Board of Dentistry"},"isArchived":false,"archivedAt":null,"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getComplianceProject_post","summary":"Get a compliance project (POST)","description":"Requires: bearer token; any role (admin, editor or viewer).\nSame as the GET form; `id` must still be sent in the query string.","tags":["Compliance Clients & Projects"],"parameters":[{"name":"id","in":"query","required":true,"description":"Project id.","schema":{"type":"string"},"example":"cp_3Mn8wX"}],"responses":{"200":{"description":"The compliance project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComplianceProject"},"example":{"id":"cp_3Mn8wX","name":"Northwind Dental - 2026 License Renewals","clientId":"cc_7Rt2vQ","status":"In Progress","startDate":"2026-09-01","dueDate":"2026-10-15","description":"Collect and renew all state dental licenses.","owner":"Priya Shah","customFields":{"board":"State Board of Dentistry"},"isArchived":false,"archivedAt":null,"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/unarchiveCompliance":{"post":{"operationId":"unarchiveCompliance","summary":"Unarchive a compliance catalog entry","description":"Requires: bearer token; role editor or admin.\nPass `id` in the query string or the JSON body.","tags":["Compliance Clients & Projects"],"parameters":[{"name":"id","in":"query","required":false,"description":"Compliance id.","schema":{"type":"string"},"example":"cm_9Pq4zR"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"}}},"example":{"id":"cm_9Pq4zR"}}}},"responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Compliance unarchived successfully."}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/unarchiveComplianceClient":{"post":{"operationId":"unarchiveComplianceClient","summary":"Unarchive a compliance client","description":"Requires: bearer token; role editor or admin.\nPass `id` in the query string or the JSON body.","tags":["Compliance Clients & Projects"],"parameters":[{"name":"id","in":"query","required":false,"description":"Client id.","schema":{"type":"string"},"example":"cc_7Rt2vQ"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"}}},"example":{"id":"cc_7Rt2vQ"}}}},"responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Client unarchived successfully."}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/unarchiveComplianceProject":{"post":{"operationId":"unarchiveComplianceProject","summary":"Unarchive a compliance project","description":"Requires: bearer token; role editor or admin.\nPass `id` in the query string or the JSON body.","tags":["Compliance Clients & Projects"],"parameters":[{"name":"id","in":"query","required":false,"description":"Project id.","schema":{"type":"string"},"example":"cp_3Mn8wX"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"}}},"example":{"id":"cp_3Mn8wX"}}}},"responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Project unarchived successfully."}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateCompliance":{"post":{"operationId":"updateCompliance","summary":"Update a compliance catalog entry","description":"Requires: bearer token; role editor or admin.\nPartial update: only the fields you send are changed. `id` goes in the query string.","tags":["Compliance Clients & Projects"],"parameters":[{"name":"id","in":"query","required":true,"description":"Compliance id.","schema":{"type":"string"},"example":"cm_9Pq4zR"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComplianceInput"},"example":{"notes":"Updated after the September review."}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"compliance":{"$ref":"#/components/schemas/Compliance"}}},"example":{"message":"Compliance updated successfully.","compliance":{"id":"cm_9Pq4zR","name":"HIPAA Security Risk Assessment","description":"Annual security risk analysis required under the HIPAA Security Rule.","category":"Data Privacy","customCategory":"","renewalFrequency":"Annually","notes":"Keep the signed report for six years.","status":"Active","customFields":{"regulator":"HHS OCR"},"isArchived":false,"archivedAt":null,"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateComplianceClient":{"post":{"operationId":"updateComplianceClient","summary":"Update a compliance client","description":"Requires: bearer token; role editor or admin.\nPartial update: only the fields you send are changed. `id` goes in the query string.","tags":["Compliance Clients & Projects"],"parameters":[{"name":"id","in":"query","required":true,"description":"Client id.","schema":{"type":"string"},"example":"cc_7Rt2vQ"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComplianceClientInput"},"example":{"notes":"Updated after the September review."}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"client":{"$ref":"#/components/schemas/ComplianceClient"}}},"example":{"message":"Client updated successfully.","client":{"id":"cc_7Rt2vQ","name":"Northwind Dental","primaryContactName":"Priya Shah","email":"priya@northwinddental.example","phone":"+1 555 0142","website":"https://northwinddental.example","address":"12 Harbor Way, Portland, OR","industry":"Healthcare","status":"Active","notes":"Annual license audit in October.","customFields":{"account_manager":"Dana Lee"},"isArchived":false,"archivedAt":null,"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateComplianceProject":{"post":{"operationId":"updateComplianceProject","summary":"Update a compliance project","description":"Requires: bearer token; role editor or admin.\nPartial update: only the fields you send are changed. `id` goes in the query string. A new `clientId` must be a client in your organization (400 otherwise).","tags":["Compliance Clients & Projects"],"parameters":[{"name":"id","in":"query","required":true,"description":"Project id.","schema":{"type":"string"},"example":"cp_3Mn8wX"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComplianceProjectInput"},"example":{"status":"Completed"}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"project":{"$ref":"#/components/schemas/ComplianceProject"}}},"example":{"message":"Project updated successfully.","project":{"id":"cp_3Mn8wX","name":"Northwind Dental - 2026 License Renewals","clientId":"cc_7Rt2vQ","status":"In Progress","startDate":"2026-09-01","dueDate":"2026-10-15","description":"Collect and renew all state dental licenses.","owner":"Priya Shah","customFields":{"board":"State Board of Dentistry"},"isArchived":false,"archivedAt":null,"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/createPortal":{"post":{"operationId":"createPortal","summary":"Create a compliance portal","description":"Requires: bearer token; role admin.\nCreates an active portal and returns its public URL. `expiry_ids` must all be in your organization (max 1000).","tags":["Compliance Portals"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/CompliancePortalInput"}],"required":["name"]},"example":{"name":"Northwind Dental - Vendor Compliance","description":"Current licenses and insurance certificates for Northwind Dental.","client_name":"Northwind Dental","client_email":"priya@northwinddental.example","expiry_ids":["exp_4Tq9sLm2","exp_8Jd3kQw1"],"password":"harbor-2026","link_expires_at":"2026-12-31T23:59:59.000Z"}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"portal":{"$ref":"#/components/schemas/CompliancePortal"},"portalUrl":{"type":"string","format":"uri"}}},"example":{"success":true,"portal":{"id":"prt_5Vb1nK","name":"Northwind Dental - Vendor Compliance","description":"Current licenses and insurance certificates for Northwind Dental.","client_name":"Northwind Dental","client_email":"priya@northwinddental.example","expiry_ids":["exp_4Tq9sLm2","exp_8Jd3kQw1"],"link_expires_at":"2026-12-31T23:59:59.000Z","organization_id":"org_northwind","created_by":"u_71bXq","access_token":"0f5c2a8e-6d4b-4e3a-9c1f-7b2d8e9a3c41","has_password":true,"is_active":true,"view_count":0,"last_viewed_at":null,"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"},"portalUrl":"https://app.expiryedge.com/portal/0f5c2a8e-6d4b-4e3a-9c1f-7b2d8e9a3c41"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deletePortal":{"post":{"operationId":"deletePortal_post","summary":"Delete a compliance portal (POST)","description":"Requires: bearer token; role admin.\nPermanently deletes the portal; its link stops working. `portalId` goes in the JSON body.","tags":["Compliance Portals"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["portalId"],"properties":{"portalId":{"type":"string"}}},"example":{"portalId":"prt_5Vb1nK"}}}},"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"delete":{"operationId":"deletePortal","summary":"Delete a compliance portal","description":"Requires: bearer token; role admin.\nPermanently deletes the portal; its link stops working. `portalId` goes in the JSON body.","tags":["Compliance Portals"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["portalId"],"properties":{"portalId":{"type":"string"}}},"example":{"portalId":"prt_5Vb1nK"}}}},"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getPortalAnalytics":{"get":{"operationId":"getPortalAnalytics","summary":"Get portal view analytics","description":"Requires: bearer token; any role (admin, editor or viewer).\nReturns 404 for an unknown portal or one in another organization. Aggregates at most the latest 100 logged views.","tags":["Compliance Portals"],"parameters":[{"name":"portalId","in":"query","required":true,"description":"Portal id.","schema":{"type":"string"},"example":"prt_5Vb1nK"}],"responses":{"200":{"description":"View analytics.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalAnalytics"},"example":{"success":true,"totalViews":12,"lastViewedAt":"2026-09-26T09:12:00.000Z","recentViews":[{"portal_id":"prt_5Vb1nK","organization_id":"org_northwind","viewed_at":"2026-09-26T09:12:00.000Z","ip":"203.0.113.24"}],"viewsByDay":{"2026-09-25":3,"2026-09-26":1}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getPortalAnalytics_post","summary":"Get portal view analytics (POST)","description":"Requires: bearer token; any role (admin, editor or viewer).\nSame as the GET form; `portalId` must still be sent in the query string.","tags":["Compliance Portals"],"parameters":[{"name":"portalId","in":"query","required":true,"description":"Portal id.","schema":{"type":"string"},"example":"prt_5Vb1nK"}],"responses":{"200":{"description":"View analytics.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalAnalytics"},"example":{"success":true,"totalViews":12,"lastViewedAt":"2026-09-26T09:12:00.000Z","recentViews":[{"portal_id":"prt_5Vb1nK","organization_id":"org_northwind","viewed_at":"2026-09-26T09:12:00.000Z","ip":"203.0.113.24"}],"viewsByDay":{"2026-09-25":3,"2026-09-26":1}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getPortals":{"get":{"operationId":"getPortals","summary":"List compliance portals","description":"Requires: bearer token; any role (admin, editor or viewer).\nReturns all of the organization's portals (including `access_token`), newest first. Unbounded.","tags":["Compliance Portals"],"responses":{"200":{"description":"Portals, newest first.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"portals":{"type":"array","items":{"$ref":"#/components/schemas/CompliancePortal"}}}},"example":{"success":true,"portals":[{"id":"prt_5Vb1nK","name":"Northwind Dental - Vendor Compliance","description":"Current licenses and insurance certificates for Northwind Dental.","client_name":"Northwind Dental","client_email":"priya@northwinddental.example","expiry_ids":["exp_4Tq9sLm2","exp_8Jd3kQw1"],"link_expires_at":"2026-12-31T23:59:59.000Z","organization_id":"org_northwind","created_by":"u_71bXq","access_token":"0f5c2a8e-6d4b-4e3a-9c1f-7b2d8e9a3c41","has_password":true,"is_active":true,"view_count":12,"last_viewed_at":"2026-09-26T09:12:00.000Z","created_at":"2026-09-01T10:00:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getPortals_post","summary":"List compliance portals (POST)","description":"Requires: bearer token; any role (admin, editor or viewer).\nSame as the GET form.","tags":["Compliance Portals"],"responses":{"200":{"description":"Portals, newest first.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"portals":{"type":"array","items":{"$ref":"#/components/schemas/CompliancePortal"}}}},"example":{"success":true,"portals":[{"id":"prt_5Vb1nK","name":"Northwind Dental - Vendor Compliance","description":"Current licenses and insurance certificates for Northwind Dental.","client_name":"Northwind Dental","client_email":"priya@northwinddental.example","expiry_ids":["exp_4Tq9sLm2","exp_8Jd3kQw1"],"link_expires_at":"2026-12-31T23:59:59.000Z","organization_id":"org_northwind","created_by":"u_71bXq","access_token":"0f5c2a8e-6d4b-4e3a-9c1f-7b2d8e9a3c41","has_password":true,"is_active":true,"view_count":12,"last_viewed_at":"2026-09-26T09:12:00.000Z","created_at":"2026-09-01T10:00:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updatePortal":{"put":{"operationId":"updatePortal","summary":"Update a compliance portal","description":"Requires: bearer token; role admin.\nPartial update: only the fields you send change. `password: \"\"` removes the password. Returns 403 for another organization's portal.","tags":["Compliance Portals"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"type":"object","required":["portalId"],"properties":{"portalId":{"type":"string"},"is_active":{"type":"boolean"}}},{"$ref":"#/components/schemas/CompliancePortalInput"}]},"example":{"portalId":"prt_5Vb1nK","is_active":false}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"updatePortal_post","summary":"Update a compliance portal (POST)","description":"Requires: bearer token; role admin.\nPartial update: only the fields you send change. `password: \"\"` removes the password. Returns 403 for another organization's portal.","tags":["Compliance Portals"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"type":"object","required":["portalId"],"properties":{"portalId":{"type":"string"},"is_active":{"type":"boolean"}}},{"$ref":"#/components/schemas/CompliancePortalInput"}]},"example":{"portalId":"prt_5Vb1nK","name":"Northwind Dental - 2026 Compliance"}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/viewPortal":{"get":{"operationId":"viewPortal","summary":"View a public compliance portal","description":"Public (no token); requires the portal access token, plus the portal password when one is set.\nPrefer POST with the password in the body; `?password=` is kept for legacy clients. Each successful view is logged (count, time, IP).","tags":["Compliance Portals"],"parameters":[{"name":"token","in":"query","required":true,"description":"Portal access token (the last segment of the portal URL).","schema":{"type":"string"},"example":"0f5c2a8e-6d4b-4e3a-9c1f-7b2d8e9a3c41"},{"name":"password","in":"query","required":false,"description":"Portal password (legacy; prefer the POST form).","schema":{"type":"string"}}],"responses":{"200":{"description":"Portal contents.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalView"},"example":{"success":true,"portal":{"name":"Northwind Dental - Vendor Compliance","description":"Current licenses and insurance certificates for Northwind Dental.","client_name":"Northwind Dental"},"summary":{"total":2,"compliant":1,"expired":1,"completed":0,"expiring_soon":1,"compliance_rate":50},"expiries":[{"name":"General Liability Insurance","document_type":"Insurance","expiry_date":"2026-09-20","start_date":"2025-09-20","state":"todo","is_done":false,"priority":"High","vendor":"Contoso Insurance","issuing_authority":null,"reference_number":"GL-448120","share_token":null,"days_left":-7,"status":"expired"},{"name":"Northwind Dental - State Dental License","document_type":"License","expiry_date":"2026-10-15","start_date":"2025-10-15","state":"todo","is_done":false,"priority":"High","vendor":null,"issuing_authority":"State Board of Dentistry","reference_number":"DL-20931","share_token":"5c0e7a52-3f7e-4b3e-9a2e-1d2f0b6c9e11","days_left":18,"status":"active"}],"generated_at":"2026-09-27T14:05:00.000Z"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"The portal is password protected and the password is missing or wrong.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"requiresPassword":{"type":"boolean"},"portalName":{"type":"string","description":"Only when the password is missing."},"clientName":{"type":"string","description":"Only when the password is missing."}}},"example":{"error":"Password required","requiresPassword":true,"portalName":"Northwind Dental - Vendor Compliance","clientName":"Northwind Dental"}}}},"404":{"$ref":"#/components/responses/NotFound"},"410":{"description":"The portal link has expired (`link_expires_at` passed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"This portal link has expired"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"viewPortal_post","summary":"View a public compliance portal (password in body)","description":"Public (no token); requires the portal access token, plus the portal password when one is set.\n`token` stays in the query string; send `password` in the JSON body.","tags":["Compliance Portals"],"parameters":[{"name":"token","in":"query","required":true,"description":"Portal access token (the last segment of the portal URL).","schema":{"type":"string"},"example":"0f5c2a8e-6d4b-4e3a-9c1f-7b2d8e9a3c41"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"password":{"type":"string","writeOnly":true}}},"example":{"password":"harbor-2026"}}}},"responses":{"200":{"description":"Portal contents.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalView"},"example":{"success":true,"portal":{"name":"Northwind Dental - Vendor Compliance","description":"Current licenses and insurance certificates for Northwind Dental.","client_name":"Northwind Dental"},"summary":{"total":2,"compliant":1,"expired":1,"completed":0,"expiring_soon":1,"compliance_rate":50},"expiries":[{"name":"General Liability Insurance","document_type":"Insurance","expiry_date":"2026-09-20","start_date":"2025-09-20","state":"todo","is_done":false,"priority":"High","vendor":"Contoso Insurance","issuing_authority":null,"reference_number":"GL-448120","share_token":null,"days_left":-7,"status":"expired"},{"name":"Northwind Dental - State Dental License","document_type":"License","expiry_date":"2026-10-15","start_date":"2025-10-15","state":"todo","is_done":false,"priority":"High","vendor":null,"issuing_authority":"State Board of Dentistry","reference_number":"DL-20931","share_token":"5c0e7a52-3f7e-4b3e-9a2e-1d2f0b6c9e11","days_left":18,"status":"active"}],"generated_at":"2026-09-27T14:05:00.000Z"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"The portal is password protected and the password is missing or wrong.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"requiresPassword":{"type":"boolean"},"portalName":{"type":"string","description":"Only when the password is missing."},"clientName":{"type":"string","description":"Only when the password is missing."}}},"example":{"error":"Password required","requiresPassword":true,"portalName":"Northwind Dental - Vendor Compliance","clientName":"Northwind Dental"}}}},"404":{"$ref":"#/components/responses/NotFound"},"410":{"description":"The portal link has expired (`link_expires_at` passed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"This portal link has expired"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/bulkAttachDirectoryToExpiries":{"post":{"operationId":"bulkAttachDirectoryToExpiries","summary":"Attach a directory entry to many expiries","description":"Requires: bearer token; role editor or admin.\nSets (`mode: replace`, default) or appends (`mode: add`) `directoryEntryId` in each expiry's\n`directory_entry_ids`. Max 1000 `expiryIds`; missing or other-organization expiries are reported in `errors`.\n","tags":["Directory"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["directoryEntryId","expiryIds"],"properties":{"directoryEntryId":{"type":"string"},"expiryIds":{"type":"array","minItems":1,"maxItems":1000,"items":{"type":"string"}},"mode":{"type":"string","enum":["replace","add"],"default":"replace"}}},"example":{"directoryEntryId":"dir_6Hv3","expiryIds":["exp_4Tq9sLm2","exp_9Pd3kWx1"],"mode":"add"}}}},"responses":{"200":{"description":"Attach finished. `errors` is present only when some expiries failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkAttachDirectoryResult"},"example":{"success":true,"mode":"add","attached_count":2,"attached_items":[{"id":"exp_4Tq9sLm2","name":"Northwind Dental - State Dental License"},{"id":"exp_9Pd3kWx1","name":"Northwind Dental - Malpractice Insurance"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/bulkImportDirectoryEntries":{"post":{"operationId":"bulkImportDirectoryEntries","summary":"Import directory entries","description":"Requires: bearer token; role editor or admin.\nUp to 1000 rows per request (`entries`, or `records`). Rows need a `name`; unknown `type` values become `Person`;\n`Other` requires `customType`. Rows duplicating an existing entry or another row (same name + type,\ncase-insensitive) are skipped. No plan limit applies.\n","tags":["Directory"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"entries":{"type":"array","maxItems":1000,"items":{"$ref":"#/components/schemas/DirectoryEntryInput"}},"records":{"type":"array","maxItems":1000,"description":"Alias of `entries`.","items":{"$ref":"#/components/schemas/DirectoryEntryInput"}}}},"example":{"entries":[{"name":"Contoso Legal","type":"Vendor","email":"billing@contosolegal.example"},{"name":"Main Street Clinic","type":"Property","address":"12 Main St, Springfield"}]}}}},"responses":{"200":{"description":"Import finished (possibly partially). Row problems are listed in `errors`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkImportDirectoryEntriesResult"},"example":{"success":true,"created":2,"skippedDuplicates":0,"totalRows":2,"errors":[]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"No rows or more than 1000 rows.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkImportDirectoryEntriesResult"},"example":{"error":"No directory entries to import.","created":0,"errors":["No rows found in the request."]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/createDirectoryEntry":{"post":{"operationId":"createDirectoryEntry","summary":"Create a directory entry","description":"Requires: bearer token; role editor or admin.\n`name` is required; `type` defaults to `Person`. `customType` is kept only when `type` is `Other`.\n`customFields` items without a `label` are dropped.\n","tags":["Directory"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DirectoryEntryInput"},"example":{"name":"Contoso Legal","type":"Vendor","email":"billing@contosolegal.example","phone":"+15550198765","address":"400 Market St, Springfield","notes":"Outside counsel for licensing.","typedFields":{"website":"https://contosolegal.example"},"customFields":[{"label":"Account number","value":"CL-2231"}]}}}},"responses":{"201":{"description":"Entry created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DirectoryEntrySavedResponse"},"example":{"message":"Directory entry created successfully.","entry":{"id":"dir_6Hv3","name":"Contoso Legal","type":"Vendor","customType":"","phone":"+15550198765","email":"billing@contosolegal.example","address":"400 Market St, Springfield","notes":"Outside counsel for licensing.","typedFields":{"website":"https://contosolegal.example"},"customFields":[{"label":"Account number","value":"CL-2231"}],"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deleteDirectoryEntry":{"post":{"operationId":"deleteDirectoryEntry_post","summary":"Delete a directory entry (POST)","description":"Requires: bearer token; role editor or admin.\nSame as `DELETE /deleteDirectoryEntry`; the id may be the `id` query parameter or body `id`.\n","tags":["Directory"],"parameters":[{"name":"id","in":"query","required":false,"description":"Directory entry id (alternative to body `id`).","schema":{"type":"string"},"example":"dir_6Hv3"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"}}},"example":{"id":"dir_6Hv3"}}}},"responses":{"200":{"description":"Entry deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Directory entry deleted successfully."}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"delete":{"operationId":"deleteDirectoryEntry","summary":"Delete a directory entry","description":"Requires: bearer token; role editor or admin.\nPermanently deletes the entry (`id` query parameter). Expiries that reference it keep the id in `directory_entry_ids`.\n","tags":["Directory"],"parameters":[{"name":"id","in":"query","required":true,"description":"Directory entry id.","schema":{"type":"string"},"example":"dir_6Hv3"}],"responses":{"200":{"description":"Entry deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Directory entry deleted successfully."}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getAllDirectoryEntries":{"get":{"operationId":"getAllDirectoryEntries","summary":"List directory entries","description":"Requires: bearer token; any role (admin, editor or viewer).\nReturns every directory entry in the organization as a bare array, newest first. Unbounded; no filters.\n","tags":["Directory"],"responses":{"200":{"description":"Array of entries (empty when none).","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DirectoryEntry"}},"example":[{"id":"dir_6Hv3","name":"Contoso Legal","type":"Vendor","customType":"","phone":"+15550198765","email":"billing@contosolegal.example","address":"400 Market St, Springfield","notes":"Outside counsel for licensing.","typedFields":{"website":"https://contosolegal.example"},"customFields":[{"label":"Account number","value":"CL-2231"}],"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}]}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getAllDirectoryEntries_post","summary":"List directory entries (POST)","description":"Requires: bearer token; any role (admin, editor or viewer).\nSame as `GET /getAllDirectoryEntries`; the body is ignored. Unbounded.\n","tags":["Directory"],"responses":{"200":{"description":"Array of entries (empty when none).","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DirectoryEntry"}},"example":[{"id":"dir_6Hv3","name":"Contoso Legal","type":"Vendor","customType":"","phone":"","email":"billing@contosolegal.example","address":"","notes":"","typedFields":{},"customFields":[],"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}]}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getDirectoryEntry":{"get":{"operationId":"getDirectoryEntry","summary":"Get a directory entry","description":"Requires: bearer token; any role (admin, editor or viewer).\nReturns the entry as a flat object. An entry in another organization returns 403.\n","tags":["Directory"],"parameters":[{"name":"id","in":"query","required":true,"description":"Directory entry id.","schema":{"type":"string"},"example":"dir_6Hv3"}],"responses":{"200":{"description":"The entry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DirectoryEntry"},"example":{"id":"dir_6Hv3","name":"Contoso Legal","type":"Vendor","customType":"","phone":"+15550198765","email":"billing@contosolegal.example","address":"400 Market St, Springfield","notes":"Outside counsel for licensing.","typedFields":{"website":"https://contosolegal.example"},"customFields":[],"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getDirectoryEntry_post","summary":"Get a directory entry (POST)","description":"Requires: bearer token; any role (admin, editor or viewer).\nSame as `GET /getDirectoryEntry`; the id is read only from the `id` query parameter.\n","tags":["Directory"],"parameters":[{"name":"id","in":"query","required":true,"description":"Directory entry id.","schema":{"type":"string"},"example":"dir_6Hv3"}],"responses":{"200":{"description":"The entry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DirectoryEntry"},"example":{"id":"dir_6Hv3","name":"Contoso Legal","type":"Vendor","customType":"","phone":"","email":"billing@contosolegal.example","address":"","notes":"","typedFields":{},"customFields":[],"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateDirectoryEntry":{"post":{"operationId":"updateDirectoryEntry","summary":"Update a directory entry","description":"Requires: bearer token; role editor or admin.\nPartial update of the `DirectoryEntryInput` fields present in the body; id from the `id` query parameter.\n`customType` is kept only when the same request sets `type` to `Other`.\n","tags":["Directory"],"parameters":[{"name":"id","in":"query","required":true,"description":"Directory entry id.","schema":{"type":"string"},"example":"dir_6Hv3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DirectoryEntryInput"},"example":{"phone":"+15550111222","notes":"New billing contact from October."}}}},"responses":{"200":{"description":"Entry updated; returns the stored entry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DirectoryEntrySavedResponse"},"example":{"message":"Directory entry updated successfully.","entry":{"id":"dir_6Hv3","name":"Contoso Legal","type":"Vendor","customType":"","phone":"+15550111222","email":"billing@contosolegal.example","address":"400 Market St, Springfield","notes":"New billing contact from October.","typedFields":{},"customFields":[],"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-28T09:12:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/assignWorkflowStep":{"post":{"operationId":"assignWorkflowStep","summary":"Set workflow step assignees","description":"Requires: bearer token (Firebase ID token or session JWT); role editor or admin.\nReplaces the step assignees. Every id must be a user of your organization.\nThe web app sends PUT; any HTTP method is accepted.","tags":["Workflows"],"parameters":[{"name":"id","in":"query","required":true,"description":"Step id.","schema":{"type":"string"},"example":"stp_9Qm1"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["assignees"],"properties":{"assignees":{"type":"array","items":{"type":"string"}}}},"example":{"assignees":["u_71bXq","u_93kLp"]}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Step assigned successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/createExpiryWorkflowAttachment":{"post":{"operationId":"createExpiryWorkflowAttachment","summary":"Attach a workflow to an expiry","description":"Requires: bearer token; role editor or admin.\nStarts a run of the workflow when the trigger fires (`before_expiry` / `after_expiry` need `trigger_days` 0-365; `manual` attachments start `paused`).\nMax 5 attachments per expiry (400). The same workflow with the same trigger type returns 409 with `existing_attachment_id`. You are always added to `notify_recipients`.","tags":["Workflows"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpiryWorkflowAttachmentInput"},"example":{"expiry_id":"exp_4Tq9sLm2","workflow_id":"wf_3Kd9Tx","trigger_type":"before_expiry","trigger_days":30,"trigger_time":"09:00","carry_to_renewal":true,"notify_on_start":true,"notify_on_completion":true,"notify_on_step_assignment":true,"notify_on_stall":false,"notify_channels":{"email":true,"in_app":true,"sms":false,"whatsapp":false,"teams":false},"notify_recipients":["u_71bXq"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"type":"object","required":["message","attachment"],"properties":{"message":{"type":"string"},"attachment":{"$ref":"#/components/schemas/ExpiryWorkflowAttachment"}}},"example":{"message":"Workflow attachment created successfully","attachment":{"id":"att_1Mz7","expiry_id":"exp_4Tq9sLm2","workflow_id":"wf_3Kd9Tx","workflow_title":"Annual license renewal","expiry_name":"State dental license - Dr. Lee","organization_id":"org_northwind","created_by":"u_71bXq","team_id":null,"trigger_type":"before_expiry","trigger_days":30,"trigger_time":"09:00","trigger_date":"2026-09-15T09:00:00.000Z","trigger_status":"pending","notify_on_start":true,"notify_on_step_assignment":true,"notify_on_completion":true,"notify_on_stall":false,"stall_threshold_days":3,"notify_recipients":["u_71bXq"],"notify_channels":{"email":true,"sms":false,"whatsapp":false,"in_app":true,"teams":false},"current_run_id":null,"last_run_id":null,"run_count":0,"is_deleted":false,"carry_to_renewal":true,"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"This workflow is already attached to the expiry with the same trigger type.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"existing_attachment_id":{"type":"string"}}},"example":{"error":"This workflow is already attached with the same trigger type","existing_attachment_id":"att_1Mz7"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/createWorkflow":{"post":{"operationId":"createWorkflow","summary":"Create a workflow","description":"Requires: bearer token (Firebase ID token or session JWT); role editor or admin.\nCreates a workflow template in your organization. Counts against the plan `workflows` limit (403 `PlanLimitError` when reached).","tags":["Workflows"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowCreateInput"},"example":{"title":"Annual license renewal","description":"Steps to renew a state dental license for Northwind Dental.","due_date":"2026-10-15","tags":["licensing"],"workflow_type_id":"wtype_R4c2"}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"type":"object","required":["message","workflow_id","workflow"],"properties":{"message":{"type":"string"},"workflow_id":{"type":"string"},"workflow":{"$ref":"#/components/schemas/Workflow"}}},"example":{"message":"Workflow created successfully","workflow_id":"wf_3Kd9Tx","workflow":{"id":"wf_3Kd9Tx","title":"Annual license renewal","description":"Steps to renew a state dental license for Northwind Dental.","owner_id":"u_71bXq","organization_id":"org_northwind","team_id":null,"workflow_type_id":"wtype_R4c2","status":"active","progress":0,"tags":["licensing"],"due_date":"2026-10-15T00:00:00.000Z","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this action, the record is in another organization, or the plan limit was reached (body is `PlanLimitError`).","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/PlanLimitError"}]},"example":{"error":"You have reached the maximum number of workflow runs for your plan.","current":50,"limit":50,"remaining":0}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/createWorkflowRun":{"post":{"operationId":"createWorkflowRun","summary":"Start a workflow run","description":"Requires: bearer token; role editor or admin.\nCopies the workflow sections and steps into a new run. Step dates come from `start_offset_days` / `due_offset_days` relative to today when set.\n`assignees` (default: you) must be users of your organization; they get assignment notifications. Counts against the plan `workflow_runs` limit.","tags":["Workflows"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["workflow_id"],"properties":{"workflow_id":{"type":"string"},"title":{"type":"string","description":"Defaults to `<workflow title> - <date>`."},"description":{"type":"string","description":"Defaults to the workflow description."},"assignees":{"type":"array","items":{"type":"string"}}}},"example":{"workflow_id":"wf_3Kd9Tx","title":"License renewal - Dr. Lee","assignees":["u_71bXq","u_93kLp"]}}}},"responses":{"201":{"description":"Run created.","content":{"application/json":{"schema":{"type":"object","required":["message","run"],"properties":{"message":{"type":"string"},"run":{"$ref":"#/components/schemas/WorkflowRun"}}},"example":{"message":"Workflow run created successfully","run":{"id":"run_7Vb3","workflow_id":"wf_3Kd9Tx","workflow_title":"Annual license renewal","title":"License renewal - Dr. Lee","description":"Steps to renew a state dental license for Northwind Dental.","status":"active","progress":0,"owner_id":"u_71bXq","organization_id":"org_northwind","workflow_type_id":"wtype_R4c2","assignees":["u_71bXq"],"tags":["licensing"],"started_at":"2026-09-27T14:05:00.000Z","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this action, the record is in another organization, or the plan limit was reached (body is `PlanLimitError`).","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/PlanLimitError"}]},"example":{"error":"You have reached the maximum number of workflow runs for your plan.","current":50,"limit":50,"remaining":0}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/IdempotencyInProgress"},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/createWorkflowSchedule":{"post":{"operationId":"createWorkflowSchedule","summary":"Schedule a workflow","description":"Requires: bearer token; role editor or admin.\nCreates a schedule that starts runs of the workflow automatically (checked every few minutes). `next_run_at` is computed on save; if there is no future fire time the schedule is created as `completed`.\nScheduled runs count against the plan `workflow_runs` limit when they fire.","tags":["Workflows"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowScheduleInput"},"example":{"workflow_id":"wf_3Kd9Tx","schedule_type":"monthly","start_date":"2026-10-15T09:00:00.000Z","day_of_month":15,"hour":9,"minute":0,"title_template":"License renewal {date}","notify_owner":true}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"type":"object","required":["schedule"],"properties":{"schedule":{"$ref":"#/components/schemas/WorkflowSchedule"}}},"example":{"schedule":{"id":"sch_6Yx4","workflow_id":"wf_3Kd9Tx","workflow_title":"Annual license renewal","owner_id":"u_71bXq","organization_id":"org_northwind","title_template":"License renewal {date}","schedule_type":"monthly","interval_days":null,"weekdays":[],"day_of_month":15,"month_of_year":null,"hour":9,"minute":0,"timezone":"UTC","start_date":"2026-10-15T09:00:00.000Z","end_date":null,"next_run_at":"2026-10-15T09:00:00.000Z","last_run_at":null,"run_count":0,"notify_owner":true,"assignees":["u_71bXq"],"status":"active","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/createWorkflowSection":{"post":{"operationId":"createWorkflowSection","summary":"Add a section to a workflow","description":"Requires: bearer token (Firebase ID token or session JWT); role editor or admin.\n`order` defaults to the end of the workflow. 404 when the workflow is not in your organization.","tags":["Workflows"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["workflow_id","title"],"properties":{"workflow_id":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"order":{"type":"integer"}}},"example":{"workflow_id":"wf_3Kd9Tx","title":"Preparation","description":"Gather documents"}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"type":"object","required":["message","section_id","section"],"properties":{"message":{"type":"string"},"section_id":{"type":"string"},"section":{"$ref":"#/components/schemas/WorkflowSection"}}},"example":{"message":"Section created successfully","section_id":"sec_5Hn2","section":{"id":"sec_5Hn2","workflow_id":"wf_3Kd9Tx","title":"Preparation","description":"Gather documents","order":0,"created_at":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/createWorkflowStep":{"post":{"operationId":"createWorkflowStep","summary":"Add a step to a workflow section","description":"Requires: bearer token (Firebase ID token or session JWT); role editor or admin.\n`assignees` must be users of your organization (400 with `details.assignees` otherwise). New steps start as `todo`; `order` defaults to the end of the section.","tags":["Workflows"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["section_id","workflow_id","title"],"properties":{"section_id":{"type":"string"},"workflow_id":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"assignees":{"type":"array","items":{"type":"string"},"description":"User ids."},"start_date":{"$ref":"#/components/schemas/IsoDate"},"due_date":{"$ref":"#/components/schemas/IsoDate"},"priority":{"type":"string","enum":["low","medium","high"],"default":"medium"},"order":{"type":"integer"}}},"example":{"section_id":"sec_5Hn2","workflow_id":"wf_3Kd9Tx","title":"Collect CE certificates","assignees":["u_71bXq"],"priority":"high","due_date":"2026-10-15"}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"type":"object","required":["message","step_id","step"],"properties":{"message":{"type":"string"},"step_id":{"type":"string"},"step":{"$ref":"#/components/schemas/WorkflowStep"}}},"example":{"message":"Step created successfully","step_id":"stp_9Qm1","step":{"id":"stp_9Qm1","section_id":"sec_5Hn2","workflow_id":"wf_3Kd9Tx","title":"Collect CE certificates","description":"Gather continuing education certificates from each dentist.","assignees":["u_71bXq"],"status":"todo","priority":"high","start_date":null,"due_date":null,"due_offset_days":7,"completed_at":null,"order":0,"dependencies":[],"time_estimate":0,"time_spent":0,"tags":[],"linked_expiry_id":null,"created_at":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/createWorkflowType":{"post":{"operationId":"createWorkflowType","summary":"Create a workflow type","description":"Requires: bearer token; role editor or admin.\nNames are unique within the organization (400 on duplicate). `color` defaults to `#1976d2`.\nAuthentication failures currently return 500 `{error}` from this handler, not 401.","tags":["Workflows"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowTypeInput"},"example":{"name":"Licensing","description":"License and permit renewals","color":"#1976d2"}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowType"},"example":{"id":"wtype_R4c2","name":"Licensing","description":"License and permit renewals","color":"#1976d2","organization_id":"org_northwind","created_by":"u_71bXq","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deleteExpiryWorkflowAttachment":{"post":{"operationId":"deleteExpiryWorkflowAttachment","summary":"Remove an expiry workflow attachment","description":"Requires: bearer token; role editor or admin.\nSoft-deletes the attachment (`trigger_status: cancelled`). With `cancel_active_run: true` in the body, its in-progress run is cancelled too.\nThe web app sends DELETE; any HTTP method is accepted.","tags":["Workflows"],"parameters":[{"name":"id","in":"query","required":false,"description":"Attachment id (or `attachment_id` in query or body).","schema":{"type":"string"},"example":"att_1Mz7"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"attachment_id":{"type":"string"},"cancel_active_run":{"type":"boolean"}}},"example":{"cancel_active_run":true}}}},"responses":{"200":{"description":"Removed.","content":{"application/json":{"schema":{"type":"object","required":["message","attachment_id"],"properties":{"message":{"type":"string"},"attachment_id":{"type":"string"}}},"example":{"message":"Attachment deleted","attachment_id":"att_1Mz7"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deleteWorkflow":{"post":{"operationId":"deleteWorkflow","summary":"Archive or delete a workflow","description":"Requires: bearer token (Firebase ID token or session JWT); role editor or admin.\nArchives the workflow (`status: archived`) by default. With `permanent=true` (query or body) it deletes the workflow, its sections and steps.\nEither way, expiry workflow attachments that use it are cancelled. Existing runs are kept.\nThe web app sends DELETE; any HTTP method is accepted.","tags":["Workflows"],"parameters":[{"name":"id","in":"query","required":true,"description":"Workflow id.","schema":{"type":"string"},"example":"wf_3Kd9Tx"},{"name":"permanent","in":"query","required":false,"description":"Delete permanently instead of archiving (may also be sent in the body).","schema":{"type":"boolean","default":false},"example":false}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"permanent":{"type":"boolean"}}},"example":{"permanent":true}}}},"responses":{"200":{"description":"Archived or deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Workflow archived successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deleteWorkflowRun":{"post":{"operationId":"deleteWorkflowRun","summary":"Delete a workflow run","description":"Requires: bearer token; role editor or admin.\nDeletes the run with its sections and steps and frees one `workflow_runs` usage slot.\nThe web app sends DELETE; any HTTP method is accepted.","tags":["Workflows"],"parameters":[{"name":"id","in":"query","required":true,"description":"Run id.","schema":{"type":"string"},"example":"run_7Vb3"}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Workflow run deleted successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deleteWorkflowSchedule":{"post":{"operationId":"deleteWorkflowSchedule","summary":"Delete a workflow schedule","description":"Requires: bearer token; role editor or admin.\nThe web app sends DELETE; any HTTP method is accepted.","tags":["Workflows"],"parameters":[{"name":"id","in":"query","required":true,"description":"Schedule id.","schema":{"type":"string"},"example":"sch_6Yx4"}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Schedule deleted"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deleteWorkflowSection":{"post":{"operationId":"deleteWorkflowSection","summary":"Delete a workflow section","description":"Requires: bearer token (Firebase ID token or session JWT); role editor or admin.\nDeletes the section and all of its steps.\nThe web app sends DELETE; any HTTP method is accepted.","tags":["Workflows"],"parameters":[{"name":"id","in":"query","required":true,"description":"Section id.","schema":{"type":"string"},"example":"sec_5Hn2"}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Section deleted successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deleteWorkflowStep":{"post":{"operationId":"deleteWorkflowStep","summary":"Delete a workflow step","description":"Requires: bearer token (Firebase ID token or session JWT); role editor or admin.\nThe web app sends DELETE; any HTTP method is accepted.","tags":["Workflows"],"parameters":[{"name":"id","in":"query","required":true,"description":"Step id.","schema":{"type":"string"},"example":"stp_9Qm1"}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Step deleted successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deleteWorkflowType":{"post":{"operationId":"deleteWorkflowType","summary":"Delete a workflow type","description":"Requires: bearer token; role editor or admin.\nIf workflows or runs use the type, send `moveToTypeId` (move them) or `deleteWorkflows: true` (delete those workflows, runs and comments); otherwise 400 with `requires_action: true` and the counts.\nAuthentication failures currently return 500 `{error}` from this handler, not 401.\nThe web app sends DELETE (with a JSON body); any HTTP method is accepted.","tags":["Workflows"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"string"},"moveToTypeId":{"type":"string"},"deleteWorkflows":{"type":"boolean"}}},"example":{"id":"wtype_R4c2","moveToTypeId":"wtype_P0k9"}}}},"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"workflows_affected":{"type":"integer"},"workflow_runs_affected":{"type":"integer"}}},"example":{"success":true,"message":"Workflow type deleted successfully","workflows_affected":3,"workflow_runs_affected":7}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Missing `id`, invalid `moveToTypeId`, or the type is in use and no action was chosen.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"workflow_count":{"type":"integer"},"workflow_run_count":{"type":"integer"},"requires_action":{"type":"boolean"}}},"example":{"error":"Cannot delete workflow type with existing workflows","workflow_count":3,"workflow_run_count":7,"requires_action":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/duplicateWorkflow":{"post":{"operationId":"duplicateWorkflow","summary":"Duplicate a workflow","description":"Requires: bearer token (Firebase ID token or session JWT); role editor or admin.\nCopies the workflow with its sections and steps; the copy is titled `<title> (Copy)`. Counts against the plan `workflows` limit.","tags":["Workflows"],"parameters":[{"name":"id","in":"query","required":true,"description":"Workflow id to copy.","schema":{"type":"string"},"example":"wf_3Kd9Tx"}],"responses":{"201":{"description":"Copy created.","content":{"application/json":{"schema":{"type":"object","required":["message","id","workflow"],"properties":{"message":{"type":"string"},"id":{"type":"string"},"workflow":{"$ref":"#/components/schemas/Workflow"}}},"example":{"message":"Workflow duplicated successfully","id":"wf_8Lp2Za","workflow":{"id":"wf_8Lp2Za","title":"Annual license renewal (Copy)","description":"Steps to renew a state dental license for Northwind Dental.","owner_id":"u_71bXq","organization_id":"org_northwind","team_id":null,"workflow_type_id":"wtype_R4c2","status":"active","progress":50,"tags":["licensing"],"due_date":"2026-10-15T00:00:00.000Z","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this action, the record is in another organization, or the plan limit was reached (body is `PlanLimitError`).","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/PlanLimitError"}]},"example":{"error":"You have reached the maximum number of workflow runs for your plan.","current":50,"limit":50,"remaining":0}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getAllWorkflowRuns":{"get":{"operationId":"getAllWorkflowRuns","summary":"List workflow runs","description":"Requires: bearer token; any role.\nRuns of your organization, newest first, with `owner_name` and live step counts.\nReads the newest `limit` runs (default 50, clamped 1-200), then applies `status` / `workflow_id` / `workflow_type_id` filters and `offset` in memory; `total` is the filtered count within that window.","tags":["Workflows"],"parameters":[{"name":"status","in":"query","required":false,"description":"Filter by run status.","schema":{"type":"string"},"example":"active"},{"name":"workflow_id","in":"query","required":false,"description":"Only runs of this workflow.","schema":{"type":"string"},"example":"wf_3Kd9Tx"},{"name":"workflow_type_id","in":"query","required":false,"description":"Only runs of this workflow type.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Runs to read (clamped 1-200).","schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"example":50},{"name":"offset","in":"query","required":false,"description":"Runs to skip after filtering.","schema":{"type":"integer","minimum":0,"default":0},"example":0}],"responses":{"200":{"description":"Runs.","content":{"application/json":{"schema":{"type":"object","required":["runs"],"properties":{"runs":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowRun"}},"total":{"type":"integer"}}},"example":{"runs":[{"id":"run_7Vb3","workflow_id":"wf_3Kd9Tx","workflow_title":"Annual license renewal","title":"Annual license renewal - 10/15/2026","description":"Steps to renew a state dental license for Northwind Dental.","status":"active","progress":0,"owner_id":"u_71bXq","organization_id":"org_northwind","workflow_type_id":"wtype_R4c2","assignees":["u_71bXq"],"tags":["licensing"],"total_steps":4,"completed_steps":0,"started_at":"2026-09-27T14:05:00.000Z","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z","owner_name":"Marcus Hale"}],"total":1}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getAllWorkflowRuns_post","summary":"List workflow runs","description":"Requires: bearer token; any role.\nRuns of your organization, newest first, with `owner_name` and live step counts.\nReads the newest `limit` runs (default 50, clamped 1-200), then applies `status` / `workflow_id` / `workflow_type_id` filters and `offset` in memory; `total` is the filtered count within that window.\nPOST is accepted with the same query parameters; the web app uses GET.","tags":["Workflows"],"parameters":[{"name":"status","in":"query","required":false,"description":"Filter by run status.","schema":{"type":"string"},"example":"active"},{"name":"workflow_id","in":"query","required":false,"description":"Only runs of this workflow.","schema":{"type":"string"},"example":"wf_3Kd9Tx"},{"name":"workflow_type_id","in":"query","required":false,"description":"Only runs of this workflow type.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Runs to read (clamped 1-200).","schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"example":50},{"name":"offset","in":"query","required":false,"description":"Runs to skip after filtering.","schema":{"type":"integer","minimum":0,"default":0},"example":0}],"responses":{"200":{"description":"Runs.","content":{"application/json":{"schema":{"type":"object","required":["runs"],"properties":{"runs":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowRun"}},"total":{"type":"integer"}}},"example":{"runs":[{"id":"run_7Vb3","workflow_id":"wf_3Kd9Tx","workflow_title":"Annual license renewal","title":"Annual license renewal - 10/15/2026","description":"Steps to renew a state dental license for Northwind Dental.","status":"active","progress":0,"owner_id":"u_71bXq","organization_id":"org_northwind","workflow_type_id":"wtype_R4c2","assignees":["u_71bXq"],"tags":["licensing"],"total_steps":4,"completed_steps":0,"started_at":"2026-09-27T14:05:00.000Z","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z","owner_name":"Marcus Hale"}],"total":1}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getAllWorkflows":{"get":{"operationId":"getAllWorkflows","summary":"List workflows","description":"Requires: bearer token (Firebase ID token or session JWT); any role.\nWorkflow templates of your organization, newest first, each with its sections, steps and run counts.\nPagination: `limit` (default 50, clamped 1-200) and `offset` (applied in memory within the first `limit` results, so prefer raising `limit`).\nTimestamps (`created_at`, `due_date`, ...) are ISO 8601 date-time strings (UTC).","tags":["Workflows"],"parameters":[{"name":"team_id","in":"query","required":false,"description":"Only workflows of this team.","schema":{"type":"string"},"example":"team_2Fq8"},{"name":"status","in":"query","required":false,"description":"Filter by status, e.g. `active` or `archived`.","schema":{"type":"string"},"example":"active"},{"name":"workflow_type_id","in":"query","required":false,"description":"Filter by workflow type id.","schema":{"type":"string"},"example":"wtype_R4c2"},{"name":"limit","in":"query","required":false,"description":"Max workflows (clamped 1-200).","schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"example":50},{"name":"offset","in":"query","required":false,"description":"Records to skip.","schema":{"type":"integer","minimum":0,"default":0},"example":0}],"responses":{"200":{"description":"Workflows with sections and run stats.","content":{"application/json":{"schema":{"type":"object","required":["workflows"],"properties":{"workflows":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowWithDetails"}}}},"example":{"workflows":[{"id":"wf_3Kd9Tx","title":"Annual license renewal","description":"Steps to renew a state dental license for Northwind Dental.","owner_id":"u_71bXq","organization_id":"org_northwind","team_id":null,"workflow_type_id":"wtype_R4c2","status":"active","progress":50,"tags":["licensing"],"due_date":"2026-10-15T00:00:00.000Z","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z","sections":[{"id":"sec_5Hn2","workflow_id":"wf_3Kd9Tx","title":"Preparation","description":"","order":0,"created_at":"2026-09-27T14:05:00.000Z","steps":[{"id":"stp_9Qm1","section_id":"sec_5Hn2","workflow_id":"wf_3Kd9Tx","title":"Collect CE certificates","description":"Gather continuing education certificates from each dentist.","assignees":["u_71bXq"],"status":"todo","priority":"high","start_date":null,"due_date":null,"due_offset_days":7,"completed_at":null,"order":0,"dependencies":[],"time_estimate":0,"time_spent":0,"tags":[],"linked_expiry_id":null,"created_at":"2026-09-27T14:05:00.000Z"}]}],"run_stats":{"total":3,"active":1,"completed":2,"archived":0}}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getAllWorkflows_post","summary":"List workflows","description":"Requires: bearer token (Firebase ID token or session JWT); any role.\nWorkflow templates of your organization, newest first, each with its sections, steps and run counts.\nPagination: `limit` (default 50, clamped 1-200) and `offset` (applied in memory within the first `limit` results, so prefer raising `limit`).\nTimestamps (`created_at`, `due_date`, ...) are ISO 8601 date-time strings (UTC).\nPOST is accepted with the same query parameters; the web app uses GET.","tags":["Workflows"],"parameters":[{"name":"team_id","in":"query","required":false,"description":"Only workflows of this team.","schema":{"type":"string"},"example":"team_2Fq8"},{"name":"status","in":"query","required":false,"description":"Filter by status, e.g. `active` or `archived`.","schema":{"type":"string"},"example":"active"},{"name":"workflow_type_id","in":"query","required":false,"description":"Filter by workflow type id.","schema":{"type":"string"},"example":"wtype_R4c2"},{"name":"limit","in":"query","required":false,"description":"Max workflows (clamped 1-200).","schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"example":50},{"name":"offset","in":"query","required":false,"description":"Records to skip.","schema":{"type":"integer","minimum":0,"default":0},"example":0}],"responses":{"200":{"description":"Workflows with sections and run stats.","content":{"application/json":{"schema":{"type":"object","required":["workflows"],"properties":{"workflows":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowWithDetails"}}}},"example":{"workflows":[{"id":"wf_3Kd9Tx","title":"Annual license renewal","description":"Steps to renew a state dental license for Northwind Dental.","owner_id":"u_71bXq","organization_id":"org_northwind","team_id":null,"workflow_type_id":"wtype_R4c2","status":"active","progress":50,"tags":["licensing"],"due_date":"2026-10-15T00:00:00.000Z","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z","sections":[{"id":"sec_5Hn2","workflow_id":"wf_3Kd9Tx","title":"Preparation","description":"","order":0,"created_at":"2026-09-27T14:05:00.000Z","steps":[{"id":"stp_9Qm1","section_id":"sec_5Hn2","workflow_id":"wf_3Kd9Tx","title":"Collect CE certificates","description":"Gather continuing education certificates from each dentist.","assignees":["u_71bXq"],"status":"todo","priority":"high","start_date":null,"due_date":null,"due_offset_days":7,"completed_at":null,"order":0,"dependencies":[],"time_estimate":0,"time_spent":0,"tags":[],"linked_expiry_id":null,"created_at":"2026-09-27T14:05:00.000Z"}]}],"run_stats":{"total":3,"active":1,"completed":2,"archived":0}}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getAllWorkflowSchedules":{"get":{"operationId":"getAllWorkflowSchedules","summary":"List all workflow schedules","description":"Requires: bearer token; any role.\nAll schedules of your organization, newest first. Returns all records; unbounded.","tags":["Workflows"],"responses":{"200":{"description":"Schedules.","content":{"application/json":{"schema":{"type":"object","required":["schedules"],"properties":{"schedules":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowSchedule"}}}},"example":{"schedules":[{"id":"sch_6Yx4","workflow_id":"wf_3Kd9Tx","workflow_title":"Annual license renewal","owner_id":"u_71bXq","organization_id":"org_northwind","title_template":"License renewal {date}","schedule_type":"monthly","interval_days":null,"weekdays":[],"day_of_month":15,"month_of_year":null,"hour":9,"minute":0,"timezone":"UTC","start_date":"2026-10-15T09:00:00.000Z","end_date":null,"next_run_at":"2026-10-15T09:00:00.000Z","last_run_at":null,"run_count":0,"notify_owner":true,"assignees":["u_71bXq"],"status":"active","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getAllWorkflowSchedules_post","summary":"List all workflow schedules","description":"Requires: bearer token; any role.\nAll schedules of your organization, newest first. Returns all records; unbounded.\nPOST is accepted with the same query parameters; the web app uses GET.","tags":["Workflows"],"responses":{"200":{"description":"Schedules.","content":{"application/json":{"schema":{"type":"object","required":["schedules"],"properties":{"schedules":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowSchedule"}}}},"example":{"schedules":[{"id":"sch_6Yx4","workflow_id":"wf_3Kd9Tx","workflow_title":"Annual license renewal","owner_id":"u_71bXq","organization_id":"org_northwind","title_template":"License renewal {date}","schedule_type":"monthly","interval_days":null,"weekdays":[],"day_of_month":15,"month_of_year":null,"hour":9,"minute":0,"timezone":"UTC","start_date":"2026-10-15T09:00:00.000Z","end_date":null,"next_run_at":"2026-10-15T09:00:00.000Z","last_run_at":null,"run_count":0,"notify_owner":true,"assignees":["u_71bXq"],"status":"active","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getExpiryWorkflowAttachments":{"get":{"operationId":"getExpiryWorkflowAttachments","summary":"List workflow attachments of an expiry","description":"Requires: bearer token; any role.\nActive (not deleted) attachments of the expiry, newest first, each with `current_run_status` when a run is in progress. `expiry_id` may also be sent in the body.","tags":["Workflows"],"parameters":[{"name":"expiry_id","in":"query","required":true,"description":"Expiry id.","schema":{"type":"string"},"example":"exp_4Tq9sLm2"}],"responses":{"200":{"description":"Attachments.","content":{"application/json":{"schema":{"type":"object","required":["attachments"],"properties":{"attachments":{"type":"array","items":{"$ref":"#/components/schemas/ExpiryWorkflowAttachment"}}}},"example":{"attachments":[{"id":"att_1Mz7","expiry_id":"exp_4Tq9sLm2","workflow_id":"wf_3Kd9Tx","workflow_title":"Annual license renewal","expiry_name":"State dental license - Dr. Lee","organization_id":"org_northwind","created_by":"u_71bXq","team_id":null,"trigger_type":"before_expiry","trigger_days":30,"trigger_time":"09:00","trigger_date":"2026-09-15T09:00:00.000Z","trigger_status":"pending","notify_on_start":true,"notify_on_step_assignment":true,"notify_on_completion":true,"notify_on_stall":false,"stall_threshold_days":3,"notify_recipients":["u_71bXq"],"notify_channels":{"email":true,"sms":false,"whatsapp":false,"in_app":true,"teams":false},"current_run_id":null,"last_run_id":null,"run_count":0,"is_deleted":false,"carry_to_renewal":true,"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z","current_run_status":null}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getExpiryWorkflowAttachments_post","summary":"List workflow attachments of an expiry","description":"Requires: bearer token; any role.\nActive (not deleted) attachments of the expiry, newest first, each with `current_run_status` when a run is in progress. `expiry_id` may also be sent in the body.\nPOST is accepted with the same query parameters; the web app uses GET.","tags":["Workflows"],"parameters":[{"name":"expiry_id","in":"query","required":true,"description":"Expiry id.","schema":{"type":"string"},"example":"exp_4Tq9sLm2"}],"responses":{"200":{"description":"Attachments.","content":{"application/json":{"schema":{"type":"object","required":["attachments"],"properties":{"attachments":{"type":"array","items":{"$ref":"#/components/schemas/ExpiryWorkflowAttachment"}}}},"example":{"attachments":[{"id":"att_1Mz7","expiry_id":"exp_4Tq9sLm2","workflow_id":"wf_3Kd9Tx","workflow_title":"Annual license renewal","expiry_name":"State dental license - Dr. Lee","organization_id":"org_northwind","created_by":"u_71bXq","team_id":null,"trigger_type":"before_expiry","trigger_days":30,"trigger_time":"09:00","trigger_date":"2026-09-15T09:00:00.000Z","trigger_status":"pending","notify_on_start":true,"notify_on_step_assignment":true,"notify_on_completion":true,"notify_on_stall":false,"stall_threshold_days":3,"notify_recipients":["u_71bXq"],"notify_channels":{"email":true,"sms":false,"whatsapp":false,"in_app":true,"teams":false},"current_run_id":null,"last_run_id":null,"run_count":0,"is_deleted":false,"carry_to_renewal":true,"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z","current_run_status":null}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getScheduledWorkflowTriggers":{"get":{"operationId":"getScheduledWorkflowTriggers","summary":"List expiry workflow triggers","description":"Requires: bearer token; any role.\nExpiry workflow attachments of your organization filtered by `trigger_status` (default `pending`; `all` for every status), with per-status `counts`.\n`pending` / `all` sort by `trigger_date` ascending, other statuses by most recently updated. `limit` (default 100) and `offset` page an in-memory list; `total` is the full filtered count.","tags":["Workflows"],"parameters":[{"name":"status","in":"query","required":false,"description":"Trigger status filter.","schema":{"type":"string","enum":["pending","triggered","cancelled","paused","error","all"],"default":"pending"},"example":"pending"},{"name":"limit","in":"query","required":false,"description":"Page size.","schema":{"type":"integer","minimum":1,"default":100},"example":100},{"name":"offset","in":"query","required":false,"description":"Records to skip.","schema":{"type":"integer","minimum":0,"default":0},"example":0}],"responses":{"200":{"description":"Triggers.","content":{"application/json":{"schema":{"type":"object","required":["triggers","total","counts"],"properties":{"triggers":{"type":"array","items":{"$ref":"#/components/schemas/ExpiryWorkflowAttachment"}},"total":{"type":"integer"},"counts":{"type":"object","properties":{"pending":{"type":"integer"},"triggered":{"type":"integer"},"cancelled":{"type":"integer"},"paused":{"type":"integer"},"error":{"type":"integer"}}}}},"example":{"triggers":[{"id":"att_1Mz7","expiry_id":"exp_4Tq9sLm2","workflow_id":"wf_3Kd9Tx","workflow_title":"Annual license renewal","expiry_name":"State dental license - Dr. Lee","organization_id":"org_northwind","created_by":"u_71bXq","team_id":null,"trigger_type":"before_expiry","trigger_days":30,"trigger_time":"09:00","trigger_date":"2026-09-15T09:00:00.000Z","trigger_status":"pending","notify_on_start":true,"notify_on_step_assignment":true,"notify_on_completion":true,"notify_on_stall":false,"stall_threshold_days":3,"notify_recipients":["u_71bXq"],"notify_channels":{"email":true,"sms":false,"whatsapp":false,"in_app":true,"teams":false},"current_run_id":null,"last_run_id":null,"run_count":0,"is_deleted":false,"carry_to_renewal":true,"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}],"total":1,"counts":{"pending":1,"triggered":4,"cancelled":0,"paused":2,"error":0}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getScheduledWorkflowTriggers_post","summary":"List expiry workflow triggers","description":"Requires: bearer token; any role.\nExpiry workflow attachments of your organization filtered by `trigger_status` (default `pending`; `all` for every status), with per-status `counts`.\n`pending` / `all` sort by `trigger_date` ascending, other statuses by most recently updated. `limit` (default 100) and `offset` page an in-memory list; `total` is the full filtered count.\nPOST is accepted with the same query parameters; the web app uses GET.","tags":["Workflows"],"parameters":[{"name":"status","in":"query","required":false,"description":"Trigger status filter.","schema":{"type":"string","enum":["pending","triggered","cancelled","paused","error","all"],"default":"pending"},"example":"pending"},{"name":"limit","in":"query","required":false,"description":"Page size.","schema":{"type":"integer","minimum":1,"default":100},"example":100},{"name":"offset","in":"query","required":false,"description":"Records to skip.","schema":{"type":"integer","minimum":0,"default":0},"example":0}],"responses":{"200":{"description":"Triggers.","content":{"application/json":{"schema":{"type":"object","required":["triggers","total","counts"],"properties":{"triggers":{"type":"array","items":{"$ref":"#/components/schemas/ExpiryWorkflowAttachment"}},"total":{"type":"integer"},"counts":{"type":"object","properties":{"pending":{"type":"integer"},"triggered":{"type":"integer"},"cancelled":{"type":"integer"},"paused":{"type":"integer"},"error":{"type":"integer"}}}}},"example":{"triggers":[{"id":"att_1Mz7","expiry_id":"exp_4Tq9sLm2","workflow_id":"wf_3Kd9Tx","workflow_title":"Annual license renewal","expiry_name":"State dental license - Dr. Lee","organization_id":"org_northwind","created_by":"u_71bXq","team_id":null,"trigger_type":"before_expiry","trigger_days":30,"trigger_time":"09:00","trigger_date":"2026-09-15T09:00:00.000Z","trigger_status":"pending","notify_on_start":true,"notify_on_step_assignment":true,"notify_on_completion":true,"notify_on_stall":false,"stall_threshold_days":3,"notify_recipients":["u_71bXq"],"notify_channels":{"email":true,"sms":false,"whatsapp":false,"in_app":true,"teams":false},"current_run_id":null,"last_run_id":null,"run_count":0,"is_deleted":false,"carry_to_renewal":true,"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}],"total":1,"counts":{"pending":1,"triggered":4,"cancelled":0,"paused":2,"error":0}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getWorkflow":{"get":{"operationId":"getWorkflow","summary":"Get a workflow","description":"Requires: bearer token (Firebase ID token or session JWT); any role.\nReturns the workflow and its sections (sorted by `order`), each with its steps.","tags":["Workflows"],"parameters":[{"name":"id","in":"query","required":true,"description":"Workflow id.","schema":{"type":"string"},"example":"wf_3Kd9Tx"}],"responses":{"200":{"description":"The workflow and its sections.","content":{"application/json":{"schema":{"type":"object","required":["workflow","sections"],"properties":{"workflow":{"$ref":"#/components/schemas/Workflow"},"sections":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowSection"}}}},"example":{"workflow":{"id":"wf_3Kd9Tx","title":"Annual license renewal","description":"Steps to renew a state dental license for Northwind Dental.","owner_id":"u_71bXq","organization_id":"org_northwind","team_id":null,"workflow_type_id":"wtype_R4c2","status":"active","progress":50,"tags":["licensing"],"due_date":"2026-10-15T00:00:00.000Z","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"},"sections":[{"id":"sec_5Hn2","workflow_id":"wf_3Kd9Tx","title":"Preparation","description":"","order":0,"created_at":"2026-09-27T14:05:00.000Z","steps":[{"id":"stp_9Qm1","section_id":"sec_5Hn2","workflow_id":"wf_3Kd9Tx","title":"Collect CE certificates","description":"Gather continuing education certificates from each dentist.","assignees":["u_71bXq"],"status":"todo","priority":"high","start_date":null,"due_date":null,"due_offset_days":7,"completed_at":null,"order":0,"dependencies":[],"time_estimate":0,"time_spent":0,"tags":[],"linked_expiry_id":null,"created_at":"2026-09-27T14:05:00.000Z"}]}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getWorkflow_post","summary":"Get a workflow","description":"Requires: bearer token (Firebase ID token or session JWT); any role.\nReturns the workflow and its sections (sorted by `order`), each with its steps.\nPOST is accepted with the same query parameters; the web app uses GET.","tags":["Workflows"],"parameters":[{"name":"id","in":"query","required":true,"description":"Workflow id.","schema":{"type":"string"},"example":"wf_3Kd9Tx"}],"responses":{"200":{"description":"The workflow and its sections.","content":{"application/json":{"schema":{"type":"object","required":["workflow","sections"],"properties":{"workflow":{"$ref":"#/components/schemas/Workflow"},"sections":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowSection"}}}},"example":{"workflow":{"id":"wf_3Kd9Tx","title":"Annual license renewal","description":"Steps to renew a state dental license for Northwind Dental.","owner_id":"u_71bXq","organization_id":"org_northwind","team_id":null,"workflow_type_id":"wtype_R4c2","status":"active","progress":50,"tags":["licensing"],"due_date":"2026-10-15T00:00:00.000Z","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"},"sections":[{"id":"sec_5Hn2","workflow_id":"wf_3Kd9Tx","title":"Preparation","description":"","order":0,"created_at":"2026-09-27T14:05:00.000Z","steps":[{"id":"stp_9Qm1","section_id":"sec_5Hn2","workflow_id":"wf_3Kd9Tx","title":"Collect CE certificates","description":"Gather continuing education certificates from each dentist.","assignees":["u_71bXq"],"status":"todo","priority":"high","start_date":null,"due_date":null,"due_offset_days":7,"completed_at":null,"order":0,"dependencies":[],"time_estimate":0,"time_spent":0,"tags":[],"linked_expiry_id":null,"created_at":"2026-09-27T14:05:00.000Z"}]}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getWorkflowRun":{"get":{"operationId":"getWorkflowRun","summary":"Get a workflow run","description":"Requires: bearer token; any role.\nReturns the run and its sections (sorted by `order`), each with its steps.","tags":["Workflows"],"parameters":[{"name":"id","in":"query","required":true,"description":"Run id.","schema":{"type":"string"},"example":"run_7Vb3"}],"responses":{"200":{"description":"The run and its sections.","content":{"application/json":{"schema":{"type":"object","required":["run","sections"],"properties":{"run":{"$ref":"#/components/schemas/WorkflowRun"},"sections":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowRunSection"}}}},"example":{"run":{"id":"run_7Vb3","workflow_id":"wf_3Kd9Tx","workflow_title":"Annual license renewal","title":"Annual license renewal - 10/15/2026","description":"Steps to renew a state dental license for Northwind Dental.","status":"active","progress":0,"owner_id":"u_71bXq","organization_id":"org_northwind","workflow_type_id":"wtype_R4c2","assignees":["u_71bXq"],"tags":["licensing"],"total_steps":4,"completed_steps":0,"started_at":"2026-09-27T14:05:00.000Z","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"},"sections":[{"id":"rsec_8Pa1","run_id":"run_7Vb3","workflow_id":"wf_3Kd9Tx","title":"Preparation","description":"","order":0,"created_at":"2026-09-27T14:05:00.000Z","steps":[{"id":"rstp_2Wd8","run_id":"run_7Vb3","section_id":"rsec_8Pa1","workflow_id":"wf_3Kd9Tx","title":"Collect CE certificates","description":"","status":"todo","priority":"high","order":0,"assignees":["u_71bXq"],"dependencies":[],"depends_on":null,"start_date":null,"due_date":"2026-10-22T23:59:59.000Z","start_offset_days":null,"due_offset_days":7,"created_at":"2026-09-27T14:05:00.000Z"}]}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getWorkflowRun_post","summary":"Get a workflow run","description":"Requires: bearer token; any role.\nReturns the run and its sections (sorted by `order`), each with its steps.\nPOST is accepted with the same query parameters; the web app uses GET.","tags":["Workflows"],"parameters":[{"name":"id","in":"query","required":true,"description":"Run id.","schema":{"type":"string"},"example":"run_7Vb3"}],"responses":{"200":{"description":"The run and its sections.","content":{"application/json":{"schema":{"type":"object","required":["run","sections"],"properties":{"run":{"$ref":"#/components/schemas/WorkflowRun"},"sections":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowRunSection"}}}},"example":{"run":{"id":"run_7Vb3","workflow_id":"wf_3Kd9Tx","workflow_title":"Annual license renewal","title":"Annual license renewal - 10/15/2026","description":"Steps to renew a state dental license for Northwind Dental.","status":"active","progress":0,"owner_id":"u_71bXq","organization_id":"org_northwind","workflow_type_id":"wtype_R4c2","assignees":["u_71bXq"],"tags":["licensing"],"total_steps":4,"completed_steps":0,"started_at":"2026-09-27T14:05:00.000Z","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"},"sections":[{"id":"rsec_8Pa1","run_id":"run_7Vb3","workflow_id":"wf_3Kd9Tx","title":"Preparation","description":"","order":0,"created_at":"2026-09-27T14:05:00.000Z","steps":[{"id":"rstp_2Wd8","run_id":"run_7Vb3","section_id":"rsec_8Pa1","workflow_id":"wf_3Kd9Tx","title":"Collect CE certificates","description":"","status":"todo","priority":"high","order":0,"assignees":["u_71bXq"],"dependencies":[],"depends_on":null,"start_date":null,"due_date":"2026-10-22T23:59:59.000Z","start_offset_days":null,"due_offset_days":7,"created_at":"2026-09-27T14:05:00.000Z"}]}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getWorkflowSchedules":{"get":{"operationId":"getWorkflowSchedules","summary":"List schedules of a workflow","description":"Requires: bearer token; any role.\nSchedules of one workflow, newest first. Returns all records; unbounded.","tags":["Workflows"],"parameters":[{"name":"workflow_id","in":"query","required":true,"description":"Workflow id.","schema":{"type":"string"},"example":"wf_3Kd9Tx"}],"responses":{"200":{"description":"Schedules.","content":{"application/json":{"schema":{"type":"object","required":["schedules"],"properties":{"schedules":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowSchedule"}}}},"example":{"schedules":[{"id":"sch_6Yx4","workflow_id":"wf_3Kd9Tx","workflow_title":"Annual license renewal","owner_id":"u_71bXq","organization_id":"org_northwind","title_template":"License renewal {date}","schedule_type":"monthly","interval_days":null,"weekdays":[],"day_of_month":15,"month_of_year":null,"hour":9,"minute":0,"timezone":"UTC","start_date":"2026-10-15T09:00:00.000Z","end_date":null,"next_run_at":"2026-10-15T09:00:00.000Z","last_run_at":null,"run_count":0,"notify_owner":true,"assignees":["u_71bXq"],"status":"active","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getWorkflowSchedules_post","summary":"List schedules of a workflow","description":"Requires: bearer token; any role.\nSchedules of one workflow, newest first. Returns all records; unbounded.\nPOST is accepted with the same query parameters; the web app uses GET.","tags":["Workflows"],"parameters":[{"name":"workflow_id","in":"query","required":true,"description":"Workflow id.","schema":{"type":"string"},"example":"wf_3Kd9Tx"}],"responses":{"200":{"description":"Schedules.","content":{"application/json":{"schema":{"type":"object","required":["schedules"],"properties":{"schedules":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowSchedule"}}}},"example":{"schedules":[{"id":"sch_6Yx4","workflow_id":"wf_3Kd9Tx","workflow_title":"Annual license renewal","owner_id":"u_71bXq","organization_id":"org_northwind","title_template":"License renewal {date}","schedule_type":"monthly","interval_days":null,"weekdays":[],"day_of_month":15,"month_of_year":null,"hour":9,"minute":0,"timezone":"UTC","start_date":"2026-10-15T09:00:00.000Z","end_date":null,"next_run_at":"2026-10-15T09:00:00.000Z","last_run_at":null,"run_count":0,"notify_owner":true,"assignees":["u_71bXq"],"status":"active","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getWorkflowTypes":{"get":{"operationId":"getWorkflowTypes","summary":"List workflow types","description":"Requires: bearer token; any role.\nReturns a bare array of your organization's workflow types sorted by name, each with `workflow_count`. Unbounded.\nAuthentication failures currently return 500 `{error}` from this handler, not 401.","tags":["Workflows"],"responses":{"200":{"description":"Workflow types.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowType"}},"example":[{"id":"wtype_R4c2","name":"Licensing","description":"License and permit renewals","color":"#1976d2","organization_id":"org_northwind","created_by":"u_71bXq","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z","workflow_count":3}]}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getWorkflowTypes_post","summary":"List workflow types","description":"Requires: bearer token; any role.\nReturns a bare array of your organization's workflow types sorted by name, each with `workflow_count`. Unbounded.\nAuthentication failures currently return 500 `{error}` from this handler, not 401.\nPOST is accepted with the same query parameters; the web app uses GET.","tags":["Workflows"],"responses":{"200":{"description":"Workflow types.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowType"}},"example":[{"id":"wtype_R4c2","name":"Licensing","description":"License and permit renewals","color":"#1976d2","organization_id":"org_northwind","created_by":"u_71bXq","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z","workflow_count":3}]}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/moveWorkflowsToType":{"post":{"operationId":"moveWorkflowsToType","summary":"Move workflows to a type","description":"Requires: bearer token; role editor or admin.\nSets `workflow_type_id` on each workflow and its runs; `targetTypeId: null` removes the type. Workflows outside your organization are skipped silently (the message count includes them).\nAuthentication failures currently return 500 `{error}` from this handler, not 401.","tags":["Workflows"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["workflowIds"],"properties":{"workflowIds":{"type":"array","minItems":1,"items":{"type":"string"}},"targetTypeId":{"type":["string","null"]}}},"example":{"workflowIds":["wf_3Kd9Tx","wf_8Lp2Za"],"targetTypeId":"wtype_R4c2"}}}},"responses":{"200":{"description":"Moved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true,"message":"2 workflow(s) moved successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/reorderWorkflowSections":{"post":{"operationId":"reorderWorkflowSections","summary":"Reorder workflow sections","description":"Requires: bearer token (Firebase ID token or session JWT); role editor or admin.\nSets `order` on each listed section. Every section must belong to a workflow in your organization (404 otherwise). Applied in one batch (max 500 items).\nThe web app sends PUT; any HTTP method is accepted.","tags":["Workflows"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["sections"],"properties":{"sections":{"type":"array","maxItems":500,"items":{"$ref":"#/components/schemas/WorkflowOrderItem"}}}},"example":{"sections":[{"id":"sec_5Hn2","order":1},{"id":"sec_0Bv4","order":0}]}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Sections reordered successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/reorderWorkflowSteps":{"post":{"operationId":"reorderWorkflowSteps","summary":"Reorder workflow steps","description":"Requires: bearer token (Firebase ID token or session JWT); role editor or admin.\nSets `order` on each listed step. Every step must belong to a workflow in your organization (404 otherwise). Applied in one batch (max 500 items).\nThe web app sends PUT; any HTTP method is accepted.","tags":["Workflows"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["steps"],"properties":{"steps":{"type":"array","maxItems":500,"items":{"$ref":"#/components/schemas/WorkflowOrderItem"}}}},"example":{"steps":[{"id":"stp_9Qm1","order":1},{"id":"stp_1Ak5","order":0}]}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Steps reordered successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/triggerExpiryWorkflow":{"post":{"operationId":"triggerExpiryWorkflow","summary":"Trigger an expiry workflow now","description":"Requires: bearer token; role editor or admin.\nStarts a run from the attachment immediately (manual attachments or re-runs). Returns 201.\n400 when the workflow or expiry was deleted, either is archived, or the plan `workflow_runs` limit is reached.","tags":["Workflows"],"parameters":[{"name":"id","in":"query","required":false,"description":"Attachment id (or `attachment_id` in query or body).","schema":{"type":"string"},"example":"att_1Mz7"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"attachment_id":{"type":"string"}}},"example":{}}}},"responses":{"201":{"description":"Run started.","content":{"application/json":{"schema":{"type":"object","required":["message","run_id","attachment_id"],"properties":{"message":{"type":"string"},"run_id":{"type":"string"},"attachment_id":{"type":"string"}}},"example":{"message":"Workflow run triggered successfully","run_id":"run_7Vb3","attachment_id":"att_1Mz7"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/triggerWorkflowScheduleNow":{"post":{"operationId":"triggerWorkflowScheduleNow","summary":"Run a schedule now","description":"Requires: bearer token; role editor or admin.\nStarts a run from the schedule immediately, notifies the owner when `notify_owner` is set, and advances `next_run_at` (a `once` schedule becomes `completed`).\nCounts against the plan `workflow_runs` limit (403 `PlanLimitError`).","tags":["Workflows"],"parameters":[{"name":"id","in":"query","required":true,"description":"Schedule id.","schema":{"type":"string"},"example":"sch_6Yx4"}],"responses":{"200":{"description":"Run started.","content":{"application/json":{"schema":{"type":"object","required":["message","run_id"],"properties":{"message":{"type":"string"},"run_id":{"type":"string"}}},"example":{"message":"Run started","run_id":"run_7Vb3"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your role does not allow this action, the record is in another organization, or the plan limit was reached (body is `PlanLimitError`).","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/PlanLimitError"}]},"example":{"error":"You have reached the maximum number of workflow runs for your plan.","current":50,"limit":50,"remaining":0}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateExpiryWorkflowAttachment":{"post":{"operationId":"updateExpiryWorkflowAttachment","summary":"Update an expiry workflow attachment","description":"Requires: bearer token; role editor or admin.\nId in `?id=` (or `attachment_id` in query or body). Changing `trigger_type` recomputes `trigger_date` and resets a not-yet-fired trigger to `pending` (`paused` for manual).\nNotification settings must be sent nested in `notification_config` here (flat fields are ignored on update).\nThe web app sends PUT; any HTTP method is accepted.","tags":["Workflows"],"parameters":[{"name":"id","in":"query","required":false,"description":"Attachment id.","schema":{"type":"string"},"example":"att_1Mz7"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"attachment_id":{"type":"string","description":"Alternative to `?id=`."},"trigger_type":{"$ref":"#/components/schemas/ExpiryWorkflowTriggerType"},"trigger_days":{"type":"integer","minimum":0,"maximum":365},"trigger_time":{"type":"string","pattern":"^\\d{2}:\\d{2}$"},"carry_to_renewal":{"type":"boolean"},"notification_config":{"$ref":"#/components/schemas/ExpiryWorkflowNotificationConfig"}}},"example":{"trigger_type":"before_expiry","trigger_days":45,"trigger_time":"08:30"}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"type":"object","required":["message","attachment_id"],"properties":{"message":{"type":"string"},"attachment_id":{"type":"string"}}},"example":{"message":"Attachment updated","attachment_id":"att_1Mz7"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateRunStep":{"post":{"operationId":"updateRunStep","summary":"Update a run step","description":"Requires: bearer token; role editor or admin.\nOnly `title`, `description`, `status`, `priority`, `assignees`, `due_date`, `start_date`, `notes`, `dependencies` and `depends_on` are applied. Recomputes run progress.\nThe web app sends PUT; any HTTP method is accepted.","tags":["Workflows"],"parameters":[{"name":"id","in":"query","required":true,"description":"Run step id.","schema":{"type":"string"},"example":"rstp_2Wd8"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"status":{"type":"string"},"priority":{"type":"string","enum":["low","medium","high"]},"assignees":{"type":"array","items":{"type":"string"}},"due_date":{"type":["string","null"]},"start_date":{"type":["string","null"]},"notes":{"type":"string"},"dependencies":{"type":"array","items":{"type":"string"}},"depends_on":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}},{"type":"null"}]}}},"example":{"assignees":["u_93kLp"],"due_date":"2026-10-15T23:59:59.000Z","notes":"Waiting on Dr. Ortiz."}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Step updated successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateRunStepStatus":{"post":{"operationId":"updateRunStepStatus","summary":"Set a run step status","description":"Requires: bearer token; role editor or admin.\nBoth `id` and `status` are query parameters. `done` stamps `completed_at`. Recomputes run progress.\nThe web app sends PUT; any HTTP method is accepted.","tags":["Workflows"],"parameters":[{"name":"id","in":"query","required":true,"description":"Run step id.","schema":{"type":"string"},"example":"rstp_2Wd8"},{"name":"status","in":"query","required":true,"description":"New status.","schema":{"type":"string","examples":["todo","in_progress","done"]},"example":"done"}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Step status updated successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateWorkflow":{"post":{"operationId":"updateWorkflow","summary":"Update a workflow","description":"Requires: bearer token (Firebase ID token or session JWT); role editor or admin.\nOnly `title`, `description`, `team_id`, `due_date`, `tags`, `workflow_type_id` and `status` are applied; other keys are ignored.\nThe web app sends PUT; any HTTP method is accepted.","tags":["Workflows"],"parameters":[{"name":"id","in":"query","required":true,"description":"Workflow id.","schema":{"type":"string"},"example":"wf_3Kd9Tx"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowUpdateInput"},"example":{"title":"Annual license renewal (2026)","due_date":"2026-10-15"}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Workflow updated successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateWorkflowRun":{"post":{"operationId":"updateWorkflowRun","summary":"Update a workflow run","description":"Requires: bearer token; role editor or admin.\nOnly `title`, `description`, `status`, `tags` and `assignees` are applied. `status: completed` stamps `completed_at` and sends the attachment completion notice when the run came from an expiry workflow attachment.\nThe web app archives a run by sending `{status: archived}`. New assignees (users of your organization) get assignment notifications.\nThe web app sends PUT; any HTTP method is accepted.","tags":["Workflows"],"parameters":[{"name":"id","in":"query","required":true,"description":"Run id.","schema":{"type":"string"},"example":"run_7Vb3"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"status":{"type":"string","examples":["active","completed","archived","cancelled"]},"tags":{"type":"array","items":{"type":"string"}},"assignees":{"type":"array","items":{"type":"string"}}}},"example":{"status":"completed"}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Workflow run updated successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateWorkflowSchedule":{"post":{"operationId":"updateWorkflowSchedule","summary":"Update a workflow schedule","description":"Requires: bearer token; role editor or admin.\nApplies any of the schedule fields plus `status` (`active` / `paused` / `cancelled`). `next_run_at` is recomputed while the schedule is active.\nReturns the merged schedule; `updated_at` in the response is the time of the update (ISO 8601).\nThe web app sends PUT; any HTTP method is accepted.","tags":["Workflows"],"parameters":[{"name":"id","in":"query","required":true,"description":"Schedule id.","schema":{"type":"string"},"example":"sch_6Yx4"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/WorkflowScheduleFields"},{"type":"object","properties":{"status":{"type":"string","enum":["active","paused","completed","cancelled"]}}}]},"example":{"status":"paused"}}}},"responses":{"200":{"description":"Updated schedule.","content":{"application/json":{"schema":{"type":"object","required":["schedule"],"properties":{"schedule":{"$ref":"#/components/schemas/WorkflowSchedule"}}},"example":{"schedule":{"id":"sch_6Yx4","workflow_id":"wf_3Kd9Tx","workflow_title":"Annual license renewal","owner_id":"u_71bXq","organization_id":"org_northwind","title_template":"License renewal {date}","schedule_type":"monthly","interval_days":null,"weekdays":[],"day_of_month":15,"month_of_year":null,"hour":9,"minute":0,"timezone":"UTC","start_date":"2026-10-15T09:00:00.000Z","end_date":null,"next_run_at":"2026-10-15T09:00:00.000Z","last_run_at":null,"run_count":0,"notify_owner":true,"assignees":["u_71bXq"],"status":"paused","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateWorkflowSection":{"post":{"operationId":"updateWorkflowSection","summary":"Update a workflow section","description":"Requires: bearer token (Firebase ID token or session JWT); role editor or admin.\nOnly `title`, `description` and `order` are applied.\nThe web app sends PUT; any HTTP method is accepted.","tags":["Workflows"],"parameters":[{"name":"id","in":"query","required":true,"description":"Section id.","schema":{"type":"string"},"example":"sec_5Hn2"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"order":{"type":"integer"}}},"example":{"title":"Preparation and intake"}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Section updated successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateWorkflowStep":{"post":{"operationId":"updateWorkflowStep","summary":"Update a workflow step","description":"Requires: bearer token (Firebase ID token or session JWT); role editor or admin.\nApplies the body to the step, except ownership and parent fields (`id`, `organization_id`, `workflow_id`, `section_id`, `run_id`, `completed_at`, ...), which are ignored.\nSetting `status: done` stamps `completed_at`. `assignees` must be users of your organization.\nThe web app sends PUT; any HTTP method is accepted.","tags":["Workflows"],"parameters":[{"name":"id","in":"query","required":true,"description":"Step id.","schema":{"type":"string"},"example":"stp_9Qm1"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowStepUpdateInput"},"example":{"title":"Collect CE certificates (all dentists)","due_offset_days":10,"depends_on":"stp_1Ak5"}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Step updated successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateWorkflowStepStatus":{"post":{"operationId":"updateWorkflowStepStatus","summary":"Set a workflow step status","description":"Requires: bearer token (Firebase ID token or session JWT); role editor or admin.\n`done` stamps `completed_at`; any other value clears it. Recomputes the workflow `progress`.\nThe web app sends PUT; any HTTP method is accepted.","tags":["Workflows"],"parameters":[{"name":"id","in":"query","required":true,"description":"Step id.","schema":{"type":"string"},"example":"stp_9Qm1"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","examples":["todo","in_progress","done"]}}},"example":{"status":"done"}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Step status updated successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateWorkflowType":{"post":{"operationId":"updateWorkflowType","summary":"Update a workflow type","description":"Requires: bearer token; role editor or admin.\nThe id goes in the body. `name` is required; `description` and `color` are reset to defaults when omitted.\nAuthentication failures currently return 500 `{error}` from this handler, not 401.\nThe web app sends PUT; any HTTP method is accepted.","tags":["Workflows"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/WorkflowTypeInput"},{"type":"object","required":["id"],"properties":{"id":{"type":"string"}}}]},"example":{"id":"wtype_R4c2","name":"Licensing & permits","description":"License and permit renewals","color":"#2e7d32"}}}},"responses":{"200":{"description":"Updated type.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowType"},"example":{"id":"wtype_R4c2","name":"Licensing & permits","description":"License and permit renewals","color":"#2e7d32","organization_id":"org_northwind","created_by":"u_71bXq","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/createExpiryComment":{"post":{"operationId":"createExpiryComment","summary":"Add a comment to an expiry","description":"Requires: bearer token; any member of the expiry's organization.\n`text` is plain text (max 5000 characters); it is stored HTML-escaped with newlines as `<br>`.\n","tags":["Comments & Activity"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expiryId","text"],"properties":{"expiryId":{"type":"string"},"text":{"type":"string","maxLength":5000}}},"example":{"expiryId":"exp_4Tq9sLm2","text":"Renewal form submitted to the board."}}}},"responses":{"201":{"description":"Comment created.","content":{"application/json":{"schema":{"type":"object","required":["comment"],"properties":{"comment":{"$ref":"#/components/schemas/Comment"}}},"example":{"comment":{"id":"cmt_2Lx8","expiryId":"exp_4Tq9sLm2","text":"Renewal form submitted to the board.","userId":"u_71bXq","username":"Priya Shah","edited":false,"timestamp":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"409":{"$ref":"#/components/responses/IdempotencyInProgress"},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/createWorkflowComment":{"post":{"operationId":"createWorkflowComment","summary":"Comment on a workflow or run","description":"Requires: bearer token (Firebase ID token or session JWT); any role.\n`workflow_id` may be a workflow id or a workflow run id. Optionally scope the comment to a step or section.\nMentioned users outside your organization are dropped; the rest get an in-app mention notification.","tags":["Comments & Activity"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["workflow_id","content"],"properties":{"workflow_id":{"type":"string","description":"Workflow id or workflow run id."},"content":{"type":"string"},"step_id":{"type":"string"},"section_id":{"type":"string"},"mentioned_users":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowMentionedUser"}}}},"example":{"workflow_id":"run_7Vb3","step_id":"rstp_2Wd8","content":"@Priya certificates for Dr. Lee are uploaded.","mentioned_users":[{"id":"u_93kLp","name":"Priya Shah"}]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"type":"object","required":["message","comment_id","comment"],"properties":{"message":{"type":"string"},"comment_id":{"type":"string"},"comment":{"$ref":"#/components/schemas/WorkflowComment"}}},"example":{"message":"Comment created successfully","comment_id":"wcm_4Rt6","comment":{"id":"wcm_4Rt6","workflow_id":"run_7Vb3","step_id":"rstp_2Wd8","section_id":null,"user_id":"u_71bXq","content":"@Priya certificates for Dr. Lee are uploaded.","mentioned_users":[{"id":"u_93kLp","name":"Priya Shah"}],"is_edited":false,"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/deleteExpiryComment":{"post":{"operationId":"deleteExpiryComment","summary":"Delete a comment","description":"Requires: bearer token; comment author or an org admin. Also accepts DELETE (`commentId` in the query string).\n","tags":["Comments & Activity"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["commentId"],"properties":{"commentId":{"type":"string"}}},"example":{"commentId":"cmt_2Lx8"}}}},"responses":{"200":{"description":"Comment deleted.","content":{"application/json":{"schema":{"type":"object","required":["deleted","id"],"properties":{"deleted":{"type":"boolean","const":true},"id":{"type":"string"}}},"example":{"deleted":true,"id":"cmt_2Lx8"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/deleteWorkflowComment":{"post":{"operationId":"deleteWorkflowComment","summary":"Delete a workflow comment","description":"Requires: bearer token (Firebase ID token or session JWT); comment author or admin.\nThe web app sends DELETE; any HTTP method is accepted.","tags":["Comments & Activity"],"parameters":[{"name":"id","in":"query","required":true,"description":"Comment id.","schema":{"type":"string"},"example":"wcm_4Rt6"}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Comment deleted successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getActivityStats":{"get":{"operationId":"getActivityStats","summary":"Get activity counts for your organization","description":"Requires: bearer token; any role.\nCounts over the last `days` days (default 7, clamped to 1-365) by activity type, entity type and user name. `recent_activities` is always empty. A query `organization_id` is ignored.\n","tags":["Comments & Activity"],"parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":365,"default":7},"example":30}],"responses":{"200":{"description":"Activity statistics.","content":{"application/json":{"schema":{"type":"object","properties":{"stats":{"type":"object","properties":{"total_activities":{"type":"integer"},"by_type":{"type":"object","additionalProperties":{"type":"integer"}},"by_entity":{"type":"object","additionalProperties":{"type":"integer"}},"by_user":{"type":"object","additionalProperties":{"type":"integer"}},"recent_activities":{"type":"array","maxItems":0,"items":{}}}}}},"example":{"stats":{"total_activities":57,"by_type":{"created":20,"updated":31,"deleted":6},"by_entity":{"expiry":44,"contact":13},"by_user":{"Priya Shah":40,"Tom Becker":17},"recent_activities":[]}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getEntityActivities":{"get":{"operationId":"getEntityActivities","summary":"Get the activity log for one record","description":"Requires: bearer token; any role.\nNewest first. `limit` defaults to 50 and is clamped to 1-200. Entries from other organizations are filtered out, so fewer than `limit` may be returned. No cursor.\n","tags":["Comments & Activity"],"parameters":[{"name":"entity_type","in":"query","required":true,"schema":{"type":"string"},"description":"expiry, contact, member, template, workflow, workflow_section, workflow_step, backup_rule.","example":"expiry"},{"name":"entity_id","in":"query","required":true,"schema":{"type":"string"},"example":"exp_4Tq9sLm2"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}}],"responses":{"200":{"description":"Activity entries.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivityList"},"example":{"activities":[{"id":"act_9Rk2mQ","entity_type":"expiry","entity_id":"exp_4Tq9sLm2","entity_name":"Business License - Northwind Dental","activity_type":"updated","user_id":"u_71bXq","user_name":"Priya Shah","organization_id":"org_northwind","changes":[{"field":"expiry_date","field_label":"Expiry Date","old_value":"2026-10-15","new_value":"2027-10-15","change_type":"primitive"}],"metadata":{"changes_count":1},"created_at":"2026-09-27T14:05:00.000Z","timestamp":"2026-09-27T14:05:00.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getExpiryComments":{"get":{"operationId":"getExpiryComments","summary":"List comments on an expiry","description":"Requires: bearer token; any member of the expiry's organization.\nNewest first, up to `limit` (1-100, default 50). The handler accepts any method; `expiryId` may also be sent in a JSON body.\n","tags":["Comments & Activity"],"parameters":[{"name":"expiryId","in":"query","required":true,"schema":{"type":"string"},"example":"exp_4Tq9sLm2"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"Comments, newest first.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Comment"}}}},"example":{"data":[{"id":"cmt_2Lx8","expiryId":"exp_4Tq9sLm2","text":"Renewal form submitted to the board.","userId":"u_71bXq","username":"Priya Shah","edited":false,"timestamp":"2026-09-27T14:05:00.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getOrganizationActivities":{"get":{"operationId":"getOrganizationActivities","summary":"Get the activity log for your organization","description":"Requires: bearer token; any role.\nNewest first. `limit` defaults to 100 and is clamped to 1-200; no cursor. A query `organization_id` (sent by the web app) is ignored.\n","tags":["Comments & Activity"],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":100},"example":50},{"name":"entity_type","in":"query","required":false,"schema":{"type":"string"},"example":"contact"},{"name":"organization_id","in":"query","required":false,"deprecated":true,"schema":{"type":"string"},"description":"Ignored."}],"responses":{"200":{"description":"Activity entries.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivityList"},"example":{"activities":[{"id":"act_3Lp8vW","entity_type":"contact","entity_id":"c_8Hk2pQ","entity_name":"Maria Lopez","activity_type":"created","user_id":"u_71bXq","user_name":"Priya Shah","organization_id":"org_northwind","changes":[],"metadata":{"email":"maria@contoso-legal.com"},"created_at":"2026-09-27T14:05:00.000Z","timestamp":"2026-09-27T14:05:00.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getWorkflowComments":{"get":{"operationId":"getWorkflowComments","summary":"List workflow comments","description":"Requires: bearer token (Firebase ID token or session JWT); any role.\nComments on a workflow or run, newest first, with the author `user_name` and `user_email`. Filter by `step_id` or else `section_id`. Returns all matches; unbounded.","tags":["Comments & Activity"],"parameters":[{"name":"workflow_id","in":"query","required":true,"description":"Workflow id or workflow run id.","schema":{"type":"string"},"example":"run_7Vb3"},{"name":"step_id","in":"query","required":false,"description":"Only comments on this step.","schema":{"type":"string"},"example":"rstp_2Wd8"},{"name":"section_id","in":"query","required":false,"description":"Only comments on this section (ignored when `step_id` is set).","schema":{"type":"string"}}],"responses":{"200":{"description":"Comments.","content":{"application/json":{"schema":{"type":"object","required":["comments"],"properties":{"comments":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowComment"}}}},"example":{"comments":[{"id":"wcm_4Rt6","workflow_id":"run_7Vb3","step_id":"rstp_2Wd8","section_id":null,"user_id":"u_71bXq","content":"@Priya certificates for Dr. Lee are uploaded.","mentioned_users":[{"id":"u_93kLp","name":"Priya Shah"}],"is_edited":false,"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z","user_name":"Marcus Hale","user_email":"marcus@northwinddental.com"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getWorkflowComments_post","summary":"List workflow comments","description":"Requires: bearer token (Firebase ID token or session JWT); any role.\nComments on a workflow or run, newest first, with the author `user_name` and `user_email`. Filter by `step_id` or else `section_id`. Returns all matches; unbounded.\nPOST is accepted with the same query parameters; the web app uses GET.","tags":["Comments & Activity"],"parameters":[{"name":"workflow_id","in":"query","required":true,"description":"Workflow id or workflow run id.","schema":{"type":"string"},"example":"run_7Vb3"},{"name":"step_id","in":"query","required":false,"description":"Only comments on this step.","schema":{"type":"string"},"example":"rstp_2Wd8"},{"name":"section_id","in":"query","required":false,"description":"Only comments on this section (ignored when `step_id` is set).","schema":{"type":"string"}}],"responses":{"200":{"description":"Comments.","content":{"application/json":{"schema":{"type":"object","required":["comments"],"properties":{"comments":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowComment"}}}},"example":{"comments":[{"id":"wcm_4Rt6","workflow_id":"run_7Vb3","step_id":"rstp_2Wd8","section_id":null,"user_id":"u_71bXq","content":"@Priya certificates for Dr. Lee are uploaded.","mentioned_users":[{"id":"u_93kLp","name":"Priya Shah"}],"is_edited":false,"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z","user_name":"Marcus Hale","user_email":"marcus@northwinddental.com"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateExpiryComment":{"post":{"operationId":"updateExpiryComment","summary":"Edit your own comment","description":"Requires: bearer token; comment author only. Also accepts PUT.\nSets `edited: true`.\n","tags":["Comments & Activity"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["commentId","text"],"properties":{"commentId":{"type":"string"},"text":{"type":"string","maxLength":5000}}},"example":{"commentId":"cmt_2Lx8","text":"Renewal form submitted and receipt received."}}}},"responses":{"200":{"description":"Updated comment.","content":{"application/json":{"schema":{"type":"object","required":["comment"],"properties":{"comment":{"$ref":"#/components/schemas/Comment"}}},"example":{"comment":{"id":"cmt_2Lx8","expiryId":"exp_4Tq9sLm2","text":"Renewal form submitted and receipt received.","userId":"u_71bXq","username":"Priya Shah","edited":true,"timestamp":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/updateWorkflowComment":{"post":{"operationId":"updateWorkflowComment","summary":"Edit a workflow comment","description":"Requires: bearer token (Firebase ID token or session JWT); comment author only.\nReplaces `content` and sets `is_edited: true`.\nThe web app sends PUT; any HTTP method is accepted.","tags":["Comments & Activity"],"parameters":[{"name":"id","in":"query","required":true,"description":"Comment id.","schema":{"type":"string"},"example":"wcm_4Rt6"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["content"],"properties":{"content":{"type":"string"}}},"example":{"content":"Certificates for Dr. Lee and Dr. Ortiz are uploaded."}}}},"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Comment updated successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/listCollectionSubmissions":{"get":{"operationId":"listCollectionSubmissions","summary":"Poll Document Collection submissions received since a point in time","description":"Requires: bearer token; any member of an organization.\nReturns submissions with `submitted_at` after `since`, oldest first, up to 100 per page. Files are\nmetadata only - download them with getCollectionSubmissionFile.\n","tags":["Change Feeds"],"parameters":[{"name":"since","in":"query","required":false,"description":"ISO 8601 timestamp; required unless `cursor` is given.","schema":{"type":"string","format":"date-time"},"example":"2026-09-01T00:00:00Z"},{"name":"cursor","in":"query","required":false,"description":"Opaque `next_cursor` from the previous page.","schema":{"type":"string"}},{"name":"review_status","in":"query","required":false,"schema":{"type":"string","enum":["pending","approved","changes_requested","rejected"]}},{"name":"limit","in":"query","required":false,"description":"Page size, 1-100 (default 50).","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"A page of submissions, oldest first.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CollectionSubmission"}}}}]},"example":{"data":[{"id":"sub_Qm3r","request_id":"req_9WkT","template_id":"tpl_W9onboard","submitted_at":"2026-09-26T16:42:10.000Z","review_status":"pending","revision":1,"responses":{"fld_company_name":"Northwind Dental LLC"},"files":[{"field_id":"fld_insurance","name":"liability-certificate-2026.pdf","content_type":"application/pdf","size":482113}]}],"next_cursor":"WyIyMDI2LTA5LTI2VDE2OjQyOjEwLjAwMFoiLCJzdWJfUW0zciJd","has_more":false}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/listExpiryChanges":{"get":{"operationId":"listExpiryChanges","summary":"Poll expiries created, updated or completed since a point in time","description":"Requires: bearer token; any member of an organization.\nReturns expiries whose `created_at` / `updated_at` / `completed_at` is after `since`, oldest first,\nin pages of up to 100. Continue with `cursor` (the previous `next_cursor`) instead of `since`.\n`share_password` is never returned. This is the recommended way to poll for changes.\n","tags":["Change Feeds"],"parameters":[{"name":"event","in":"query","required":false,"description":"Which timestamp to page on. `completed` also filters to `is_done = true`.","schema":{"type":"string","enum":["created","updated","completed"],"default":"updated"}},{"name":"since","in":"query","required":false,"description":"ISO 8601 timestamp; required unless `cursor` is given.","schema":{"type":"string","format":"date-time"},"example":"2026-09-01T00:00:00Z"},{"name":"cursor","in":"query","required":false,"description":"Opaque `next_cursor` from the previous page.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Page size, 1-100 (values above 100 are clamped; default 50).","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"A page of expiries, oldest change first.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Expiry"}}}}]},"example":{"data":[{"id":"exp_4Tq9sLm2","name":"Northwind Dental - State Dental License","type":"License","expiry_date":"2026-10-15","priority":"High","state":"todo","is_done":false,"is_archive":false,"organization_id":"org_northwind","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T15:10:00.000Z"}],"next_cursor":"WyIyMDI2LTA5LTI3VDE1OjEwOjAwLjAwMFoiLCJleHBfNFRxOXNMbTIiXQ","has_more":false}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/bulkExtractDocuments":{"post":{"operationId":"bulkExtractDocuments","summary":"Extract expiry data from up to 10 files in Cloud Storage","description":"Requires: bearer token; any role.\n1-10 files; each must be inside your organization's upload area. The whole batch must fit the remaining AI scan quota (429 `AiScanQuotaError` otherwise); only successfully processed files are counted. Per-file failures appear as `error` in `results`.\n","tags":["Document Intelligence"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["files"],"properties":{"files":{"type":"array","minItems":1,"maxItems":10,"items":{"type":"object","required":["storagePath"],"properties":{"storagePath":{"type":"string"},"fileName":{"type":"string"},"mimeType":{"$ref":"#/components/schemas/AiDocumentMimeType"}}}}}},"example":{"files":[{"storagePath":"user_files/org_northwind/uploads/dea-registration.pdf","fileName":"dea-registration.pdf","mimeType":"application/pdf"},{"storagePath":"user_files/org_northwind/uploads/fire-permit.jpg","fileName":"fire-permit.jpg","mimeType":"image/jpeg"}]}}}},"responses":{"200":{"description":"Per-file results.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"results":{"type":"array","items":{"type":"object","properties":{"fileName":{"type":"string"},"documents":{"type":"array","items":{"$ref":"#/components/schemas/ExtractedDocument"}},"error":{"type":"string","description":"Present when this file failed (File not found, File too large, Extraction failed)."}}}}}},"example":{"success":true,"results":[{"fileName":"dea-registration.pdf","documents":[{"name":"DEA Registration","document_type":"registration","issuing_authority":"Drug Enforcement Administration","holder_name":"Dr. Priya Shah","reference_number":"FS1234567","start_date":null,"expiry_date":"2027-01-31","vendor":null,"notes":null,"confidence":"high","extracted_fields":[]}]},{"fileName":"fire-permit.jpg","error":"File not found","documents":[]}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"A file is outside your organization, or the plan has no AI scans (`ai_scan_feature_not_available`).","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/AiScanFeatureError"}]},"example":{"error":"You do not have access to one or more files","code":"FORBIDDEN"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":30,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/extractDocument":{"post":{"operationId":"extractDocument","summary":"Extract expiry data from a file already in Cloud Storage","description":"Requires: bearer token; any role.\nUses 1 AI scan from the monthly quota. `storagePath` must be inside your organization's upload area (`user_files/{orgId}/...`, `user_files/{uid}/{orgId}/...` or `organizations/{orgId}/...`). Max 20MB.\nWhen the monthly AI scan quota is used up it returns 429 with an `AiScanQuotaError` body.\n","tags":["Document Intelligence"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["storagePath"],"properties":{"storagePath":{"type":"string"},"fileName":{"type":"string"},"mimeType":{"$ref":"#/components/schemas/AiDocumentMimeType"}}},"example":{"storagePath":"user_files/org_northwind/exp_4Tq9sLm2/business-license.pdf","fileName":"business-license.pdf","mimeType":"application/pdf"}}}},"responses":{"200":{"description":"Extraction result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentExtractionResponse"},"example":{"success":true,"extraction":{"documents":[{"name":"Business License","document_type":"license","issuing_authority":"City of Seattle","holder_name":"Northwind Dental LLC","reference_number":"BL-2025-44817","start_date":"2025-10-15","expiry_date":"2026-10-15","vendor":null,"notes":null,"confidence":"high","extracted_fields":[{"label":"License Class","value":"Health Services","category":"legal"}]}]},"fileName":"business-license.pdf","ai_scans":{"used":8,"limit":25,"remaining":17,"resets_at":"2026-10-01T00:00:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":30,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/extractDocumentDirect":{"post":{"operationId":"extractDocumentDirect","summary":"Extract expiry data from a base64-encoded file","description":"Requires: bearer token; any role.\nThe file is sent as base64 inside a JSON body (not multipart); max 10MB decoded. Uses 1 AI scan. Returns 403 `ai_scan_feature_not_available` when the plan has no AI scans.\nWhen the monthly AI scan quota is used up it returns 429 with an `AiScanQuotaError` body.\n","tags":["Document Intelligence"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["base64Data"],"properties":{"base64Data":{"type":"string","contentEncoding":"base64","description":"Raw file bytes, base64 encoded, without a `data:` URL prefix."},"fileName":{"type":"string"},"mimeType":{"$ref":"#/components/schemas/AiDocumentMimeType"}}},"example":{"base64Data":"JVBERi0xLjcKJcfsj6IKNSAwIG9iago8PC9MZW5ndGggNiAwIFI+PgpzdHJlYW0K","fileName":"malpractice-insurance.pdf","mimeType":"application/pdf"}}}},"responses":{"200":{"description":"Extraction result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentExtractionResponse"},"example":{"success":true,"extraction":{"documents":[{"name":"Professional Liability Policy","document_type":"insurance","issuing_authority":"Contoso Mutual","holder_name":"Northwind Dental LLC","reference_number":"PL-889120","start_date":"2025-10-15","expiry_date":"2026-10-15","vendor":"Contoso Mutual","notes":null,"confidence":"high","extracted_fields":[]}]},"fileName":"malpractice-insurance.pdf","ai_scans":{"used":8,"limit":25,"remaining":17,"resets_at":"2026-10-01T00:00:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The organization's plan does not include AI scans.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AiScanFeatureError"},"example":{"error":"ai_scan_feature_not_available","message":"AI Document Scanner is available on the Business Max and Power User plans. Please upgrade to access this feature.","plan_required":["Business Max","Power User"],"upgrade_url":"/dashboard/manage-subscription"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":30,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getEmailIngestionLog":{"get":{"operationId":"getEmailIngestionLog","summary":"List recent inbound email processing results","description":"Requires: bearer token; any role.\nNewest first. `limit` defaults to 20 and is clamped to 1-100; no cursor.\n","tags":["Document Intelligence"],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"example":20}],"responses":{"200":{"description":"Log entries.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"logs":{"type":"array","items":{"$ref":"#/components/schemas/EmailIngestionLogEntry"}}}},"example":{"success":true,"logs":[{"id":"eil_5Wq2nB","organization_id":"org_northwind","from_email":"frontdesk@northwind-dental.com","subject":"Renewed fire permit","attachment_count":1,"created":1,"updated":0,"results":[{"file":"fire-permit.pdf","item":"Fire Safety Permit","action":"created","expiryId":"exp_4Tq9sLm2"}],"processed_at":"2026-09-27T14:05:00.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/mapImportColumns":{"post":{"operationId":"mapImportColumns","summary":"Map spreadsheet columns to expiry fields with AI","description":"Requires: bearer token; any role.\nUses 1 AI scan. Max 100 headers; only the first 5 sample rows are used and values are truncated to 80 chars. Each standard field is used at most once; unmatched useful columns map to `custom`.\nWhen the monthly AI scan quota is used up it returns 429 with an `AiScanQuotaError` body.\n","tags":["Document Intelligence"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["headers"],"properties":{"headers":{"type":"array","minItems":1,"maxItems":100,"items":{"type":"string"}},"sampleRows":{"type":"array","maxItems":5,"items":{"type":"object","additionalProperties":true,"description":"Row keyed by header."}}}},"example":{"headers":["Doc Name","Renewal Date","Owner Email","Ph#","Row"],"sampleRows":[{"Doc Name":"Business License","Renewal Date":"15/10/2026","Owner Email":"priya@northwind-dental.com","Ph#":"+14155550123","Row":1}]}}}},"responses":{"200":{"description":"Column mapping.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportColumnMapping"},"example":{"success":true,"mapping":{"Doc Name":"name","Renewal Date":"expiry_date","Owner Email":"email","Ph#":"custom","Row":"ignore"},"custom_fields":[{"header":"Ph#","label":"Phone","type":"phone"}],"date_format":"DD/MM/YYYY","confidence":"high","warnings":[],"ai_scans":{"used":9,"limit":25,"remaining":16,"resets_at":"2026-10-01T00:00:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The organization's plan does not include AI scans.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AiScanFeatureError"},"example":{"error":"ai_scan_feature_not_available","message":"AI Document Scanner is available on the Business Max and Power User plans. Please upgrade to access this feature.","plan_required":["Business Max","Power User"],"upgrade_url":"/dashboard/manage-subscription"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":30,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/setupInboundEmail":{"post":{"operationId":"setupInboundEmail","summary":"Get or create the organization's inbound email address","description":"Requires: bearer token; role admin.\nDocuments emailed to this address are scanned and turned into expiries. The first call generates a random slug; later calls return the same address.\n","tags":["Document Intelligence"],"responses":{"200":{"description":"Inbound address.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"inbound_email":{"type":"string","format":"email"},"slug":{"type":"string"},"enabled":{"type":"boolean"}}},"example":{"success":true,"inbound_email":"docs-northwind-dental-k3x9qa@inbound.expiryedge.com","slug":"northwind-dental-k3x9qa","enabled":true}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/acknowledgeSetupItems":{"post":{"operationId":"acknowledgeSetupItems","summary":"Acknowledge setup checklist items","description":"Requires: bearer token; any role.\nMarks optional onboarding checklist items as reviewed for the whole organization (idempotent).\n","tags":["Settings"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ids"],"properties":{"ids":{"type":"array","minItems":1,"items":{"type":"string"}}}},"example":{"ids":["reminder_sequence","default_contacts"]}}}},"responses":{"200":{"description":"Acknowledged.","content":{"application/json":{"schema":{"type":"object","required":["success","acknowledged"],"properties":{"success":{"type":"boolean"},"acknowledged":{"type":"array","items":{"type":"string"}}}},"example":{"success":true,"acknowledged":["reminder_sequence","default_contacts"]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getEscalationConfig":{"get":{"operationId":"getEscalationConfig","summary":"Get the email escalation settings","description":"Requires: bearer token; any role.\nEscalation (re-sending unopened reminders by SMS/WhatsApp and notifying a manager) is opt-in. `effective` is the stored config merged\nover `defaults`; `stored` is null until an admin saves once. `bounds` gives the validation limits.\n","tags":["Settings"],"responses":{"200":{"description":"Escalation configuration.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EscalationConfigResponse"},"example":{"effective":{"enabled":false,"escalation_window_days":7,"unopened_threshold_days":2,"channels":["sms","whatsapp"],"notify_manager":true},"stored":null,"bounds":{"escalation_window_days":{"min":1,"max":60},"unopened_threshold_days":{"min":1,"max":30},"channels_allowed":["sms","whatsapp","email"]},"defaults":{"enabled":false,"escalation_window_days":7,"unopened_threshold_days":2,"channels":["sms","whatsapp"],"notify_manager":true},"org_has_saved":false}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/getEscalationSettings":{"get":{"operationId":"getEscalationSettings","summary":"Get default escalation days","description":"Requires: bearer token; any role.\nOrganization default for escalation notifications: days before expiry, sorted descending. Defaults to `[7]`.\n","tags":["Settings"],"responses":{"200":{"$ref":"#/components/responses/EscalationSettingsOk"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getEscalationSettings_post","summary":"Get default escalation days (POST)","description":"Requires: bearer token; any role.\nSame as GET.\n","tags":["Settings"],"responses":{"200":{"$ref":"#/components/responses/EscalationSettingsOk"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getNotificationPreferences":{"get":{"operationId":"getNotificationPreferences","summary":"Get your push notification preferences","description":"Requires: bearer token; any role.\nReturns the caller's mobile push preferences. If none were saved, returns the defaults (disabled, `assigned_to_me`).\n","tags":["Settings"],"responses":{"200":{"description":"Current preferences.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationPreferences"},"example":{"push_enabled":true,"push_scope":"assigned_to_me"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/getOrganizationSettings":{"get":{"operationId":"getOrganizationSettings","summary":"Get organization settings","description":"Requires: bearer token; any role.\nProfile, branding, email sender, digest, currency, billing and expiry-form settings of your organization.\nErrors from this endpoint (including token failures) return 500 with `{error, message}`.\n","tags":["Settings"],"responses":{"200":{"$ref":"#/components/responses/OrganizationSettingsOk"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getOrganizationSettings_post","summary":"Get organization settings (POST)","description":"Requires: bearer token; any role.\nSame as GET.\n","tags":["Settings"],"responses":{"200":{"$ref":"#/components/responses/OrganizationSettingsOk"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getReminderSettings":{"get":{"operationId":"getReminderSettings","summary":"Get the organization's default reminder sequence","description":"Requires: bearer token; any role.\nDefault reminders applied to new expiries. When none are saved,\nreturns the built-in default (1, 2, 5 and 30 days before at 09:00 UTC).\n","tags":["Settings"],"responses":{"200":{"$ref":"#/components/responses/ReminderSettingsOk"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getReminderSettings_post","summary":"Get the default reminder sequence (POST)","description":"Requires: bearer token; any role.\nSame as GET.\n","tags":["Settings"],"responses":{"200":{"$ref":"#/components/responses/ReminderSettingsOk"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getWebhooks":{"get":{"operationId":"getWebhooks","summary":"Get Teams and Slack webhook URLs","description":"Requires: bearer token; any role.\nOrganization-level incoming-webhook URLs used for reminder notifications; `null` when unset.\n","tags":["Settings"],"responses":{"200":{"$ref":"#/components/responses/WebhookSettingsOk"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getWebhooks_post","summary":"Get Teams and Slack webhook URLs (POST)","description":"Requires: bearer token; any role.\nSame as GET.\n","tags":["Settings"],"responses":{"200":{"$ref":"#/components/responses/WebhookSettingsOk"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/re-engagement/unsubscribe":{"get":{"operationId":"reEngagementUnsubscribe","summary":"Unsubscribe from re-engagement emails (link)","description":"Public (no token); requires the `uid` + `token` pair from the email's unsubscribe link.\nSets the user's marketing opt-out and redirects (302) to the app's confirmation page. Errors are plain text, not JSON.\n","tags":["Settings"],"parameters":[{"$ref":"#/components/parameters/ReEngagementUid"},{"$ref":"#/components/parameters/ReEngagementToken"}],"responses":{"302":{"description":"Unsubscribed; redirects to `https://app.expiryedge.com/unsubscribed?source=marketing`.","headers":{"Location":{"schema":{"type":"string"}}}},"400":{"$ref":"#/components/responses/ReEngagementPlainTextError"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ReEngagementPlainTextError"}},"security":[],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"post":{"operationId":"reEngagementUnsubscribe_post","summary":"Unsubscribe from re-engagement emails (one-click)","description":"Public (no token); requires the `uid` + `token` pair in the query string.\nRFC 8058 one-click unsubscribe used by mail clients. No body expected; returns 200 with an empty body.\n","tags":["Settings"],"parameters":[{"$ref":"#/components/parameters/ReEngagementUid"},{"$ref":"#/components/parameters/ReEngagementToken"}],"responses":{"200":{"description":"Unsubscribed (empty body).","headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/ReEngagementPlainTextError"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ReEngagementPlainTextError"}},"security":[],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/removeOrganizationLogo":{"post":{"operationId":"removeOrganizationLogo","summary":"Remove the organization logo","description":"Requires: bearer token; role admin.\n","tags":["Settings"],"responses":{"200":{"description":"Logo removed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true,"message":"Logo removed successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/saveEscalationSettings":{"post":{"operationId":"saveEscalationSettings","summary":"Save default escalation days","description":"Requires: bearer token; role editor or admin.\nValues are coerced to integers 0-365, de-duplicated and sorted descending; at least one valid value is required.\n","tags":["Settings"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EscalationSettings"},"example":{"default_escalation_notification_days":[14,7,1]}}}},"responses":{"200":{"description":"Saved.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/EscalationSettings"},{"type":"object","required":["message"],"properties":{"message":{"type":"string"}}}]},"example":{"message":"Escalation settings saved successfully","default_escalation_notification_days":[14,7,1]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/saveReminderSettings":{"post":{"operationId":"saveReminderSettings","summary":"Replace the default reminder sequence","description":"Requires: bearer token; role editor or admin.\nReplaces all saved default reminders (max 50). `timezone` is applied to every row (default `UTC`).\nValidation errors return `code: VALIDATION_ERROR`.\n","tags":["Settings"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reminders"],"properties":{"timezone":{"type":"string","maxLength":64,"description":"IANA timezone applied to all reminders."},"reminders":{"type":"array","maxItems":50,"items":{"$ref":"#/components/schemas/ReminderSetting"}}}},"example":{"timezone":"America/Chicago","reminders":[{"amount":30,"time_unit":"day","period":"before","time":"09:00"},{"amount":1,"time_unit":"week","period":"before","time":"09:00"}]}}}},"responses":{"200":{"description":"Saved.","content":{"application/json":{"schema":{"type":"object","required":["message","reminders"],"properties":{"message":{"type":"string"},"reminders":{"type":"array","items":{"$ref":"#/components/schemas/ReminderSetting"}}}},"example":{"message":"Reminder settings saved successfully","reminders":[{"amount":30,"time_unit":"day","period":"before","time":"09:00","timezone":"America/Chicago"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/saveWebhooks":{"post":{"operationId":"saveWebhooks","summary":"Save Teams and Slack webhook URLs","description":"Requires: bearer token; role editor or admin.\nURLs must be https on Microsoft Teams / Power Automate hosts or `hooks.slack.com` (`code: INVALID_WEBHOOK_URL` otherwise).\nOmitted or empty values clear the webhook.\n","tags":["Settings"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSettings"},"example":{"teams_webhook":"https://northwind.webhook.office.com/webhookb2/7c1e2a90","slack_webhook":"https://hooks.slack.com/services/T0001/B0001/XXXXXXXX"}}}},"responses":{"200":{"description":"Saved.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/WebhookSettings"},{"type":"object","required":["message"],"properties":{"message":{"type":"string"}}}]},"example":{"message":"Webhooks saved successfully","teams_webhook":"https://northwind.webhook.office.com/webhookb2/7c1e2a90","slack_webhook":"https://hooks.slack.com/services/T0001/B0001/XXXXXXXX"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Invalid webhook URL or no organization.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string","enum":["INVALID_WEBHOOK_URL"]}}},"example":{"error":"Invalid Slack webhook URL. Use an https://hooks.slack.com/... URL.","code":"INVALID_WEBHOOK_URL"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/subscribeNewsletter":{"post":{"operationId":"subscribeNewsletter","summary":"Subscribe an email address to the newsletter","description":"Public (no token).\nStores the address in the newsletter list. The email is not validated; a missing email returns 400.\n","tags":["Settings"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}},"example":{"email":"office@northwind-dental.com"}}}},"responses":{"200":{"description":"Subscribed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Successfully subscribed to newsletter"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateEscalationConfig":{"post":{"operationId":"updateEscalationConfig","summary":"Update the email escalation settings","description":"Requires: bearer token; role admin (or an organization session JWT).\nPartial update merged over the saved config. `escalation_window_days` must be >= `unopened_threshold_days` when both are sent.\n","tags":["Settings"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EscalationConfig"},"example":{"enabled":true,"escalation_window_days":10,"unopened_threshold_days":3,"channels":["sms"],"notify_manager":true}}}},"responses":{"200":{"description":"Saved; returns the same shape as getEscalationConfig.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EscalationConfigResponse"},"example":{"effective":{"enabled":true,"escalation_window_days":10,"unopened_threshold_days":3,"channels":["sms"],"notify_manager":true},"stored":{"enabled":true,"escalation_window_days":10,"unopened_threshold_days":3,"channels":["sms"],"notify_manager":true},"bounds":{"escalation_window_days":{"min":1,"max":60},"unopened_threshold_days":{"min":1,"max":30},"channels_allowed":["sms","whatsapp","email"]},"defaults":{"enabled":false,"escalation_window_days":7,"unopened_threshold_days":2,"channels":["sms","whatsapp"],"notify_manager":true},"org_has_saved":true}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/updateExpiryFormSettings":{"post":{"operationId":"updateExpiryFormSettings","summary":"Configure optional expiry form sections","description":"Requires: bearer token; role admin.\nShows/hides and orders the optional sections of the expiry form. Unknown keys are dropped; sections\nnot set to `false` stay visible; missing sections are appended to the order.\n","tags":["Settings"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["section_visibility"],"properties":{"section_visibility":{"$ref":"#/components/schemas/ExpiryFormSectionVisibility"},"section_order":{"type":"array","items":{"$ref":"#/components/schemas/ExpiryFormSectionKey"}}}},"example":{"section_visibility":{"recurring":true,"checklist":false,"custom_fields":true,"directory":true,"workflow":false},"section_order":["custom_fields","recurring","directory","checklist","workflow"]}}}},"responses":{"200":{"description":"Saved (values as stored).","content":{"application/json":{"schema":{"type":"object","required":["success","section_visibility","section_order"],"properties":{"success":{"type":"boolean"},"section_visibility":{"$ref":"#/components/schemas/ExpiryFormSectionVisibility"},"section_order":{"type":"array","items":{"$ref":"#/components/schemas/ExpiryFormSectionKey"}}}},"example":{"success":true,"section_visibility":{"recurring":true,"checklist":false,"custom_fields":true,"directory":true,"workflow":false},"section_order":["custom_fields","recurring","directory","checklist","workflow"]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateNotificationPreferences":{"post":{"operationId":"updateNotificationPreferences","summary":"Update your push notification preferences","description":"Requires: bearer token; any role.\nPartial update: only the fields sent are changed (a non-boolean `push_enabled` is ignored).\n","tags":["Settings"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationPreferences"},"example":{"push_enabled":true,"push_scope":"all"}}}},"responses":{"200":{"description":"Preferences saved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/updateOrganizationSettings":{"post":{"operationId":"updateOrganizationSettings","summary":"Update organization settings","description":"Requires: bearer token; role admin.\nPartial update: only fields present are changed; other fields in the body (e.g. `logo_url`) are ignored.\nSetting a non-empty email sender identity requires a paid (non-trial) plan (402 `PLAN_UPGRADE_REQUIRED`).\n","tags":["Settings"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationSettingsInput"},"example":{"name":"Northwind Dental","timezone":"America/Chicago","base_currency":"USD","email_reminder_mode":"DIGEST","digest_send_time":"08:30","billing_email":"billing@northwinddental.com","tax_id":{"type":"us_ein","value":"12-3456789"}}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true,"message":"Organization settings updated successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"Custom sender identity requires a paid plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusCodedError"},"example":{"error":"A custom email sender identity is available on paid plans. Upgrade to personalize how your reminders appear.","code":"PLAN_UPGRADE_REQUIRED"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/updateUserLocale":{"post":{"operationId":"updateUserLocale","summary":"Set your preferred language","description":"Requires: bearer token (Firebase ID token or session JWT); any role. Saves `preferred_locale` on the caller's user profile (used for emails and the UI).","tags":["Settings"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["locale"],"properties":{"locale":{"type":"string","enum":["en","es","de","fr","vi","cn","ar"]}}},"example":{"locale":"es"}}}},"responses":{"200":{"description":"Locale saved.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"locale":{"type":"string"},"message":{"type":"string"}}},"example":{"success":true,"locale":"es","message":"Language preference updated to es"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/uploadOrganizationLogo":{"post":{"operationId":"uploadOrganizationLogo","summary":"Upload the organization logo","description":"Requires: bearer token; role admin.\nMultipart upload with an image in the `logo` field, max 2 MB. Images over 300 KB are resized and re-encoded as JPEG.\nReplaces any existing logo; the returned URL is public.\n","tags":["Settings"],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["logo"],"properties":{"logo":{"type":"string","format":"binary","description":"Image file (`image/*`), at most 2 MB."}}},"encoding":{"logo":{"contentType":"image/*"}}}}},"responses":{"200":{"description":"Logo uploaded.","content":{"application/json":{"schema":{"type":"object","required":["success","logo_url"],"properties":{"success":{"type":"boolean"},"logo_url":{"type":"string","format":"uri"},"message":{"type":"string"}}},"example":{"success":true,"logo_url":"https://storage.googleapis.com/stylingsphere.appspot.com/organizations/org_northwind/logo/1790517900000_logo.png","message":"Logo uploaded successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/additionalLicenses":{"get":{"operationId":"additionalLicenses","summary":"Get additional user-license state","description":"Requires: bearer token; any role.\nBase plan, purchasable periods, per-seat prices, the current additional-licenses subscription and seat math.","tags":["Billing & Add-ons"],"responses":{"200":{"description":"Current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdditionalLicensesState"},"example":{"planName":"Starter Lite","isTrial":false,"baseBillingPeriod":"month","allowedPeriods":["month","year"],"prices":{"month":{"unit_amount":1000,"currency":"USD","display":"10.00"},"year":{"unit_amount":10000,"currency":"USD","display":"100.00"}},"current":{"count":2,"period":"month","status":"active","stripe_subscription_id":"sub_1QgA9bNorthwind","last_invoice_at":"2026-09-15T00:00:00.000Z","next_invoice_at":"2026-10-15T00:00:00.000Z","cancel_at_period_end":false},"seats":{"active_users":6,"base_user_limit":5,"effective_limit":7,"min_allowed_extra":1}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"additionalLicenses_post","summary":"Get additional user-license state (POST alias)","description":"Requires: bearer token; any role.\nBase plan, purchasable periods, per-seat prices, the current additional-licenses subscription and seat math.","tags":["Billing & Add-ons"],"responses":{"200":{"description":"Current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdditionalLicensesState"},"example":{"planName":"Starter Lite","isTrial":false,"baseBillingPeriod":"month","allowedPeriods":["month","year"],"prices":{"month":{"unit_amount":1000,"currency":"USD","display":"10.00"},"year":{"unit_amount":10000,"currency":"USD","display":"100.00"}},"current":{"count":2,"period":"month","status":"active","stripe_subscription_id":"sub_1QgA9bNorthwind","last_invoice_at":"2026-09-15T00:00:00.000Z","next_invoice_at":"2026-10-15T00:00:00.000Z","cancel_at_period_end":false},"seats":{"active_users":6,"base_user_limit":5,"effective_limit":7,"min_allowed_extra":1}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/additionalLicensesCancel":{"post":{"operationId":"additionalLicensesCancel","summary":"Cancel additional licenses at period end","description":"Requires: bearer token; role admin.\nIf the organization has more members than the base plan allows, returns 409 `CANCEL_WILL_OVERFLOW_BASE` unless `acknowledge_overage: true` is sent.","tags":["Billing & Add-ons"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"acknowledge_overage":{"type":"boolean"}}},"example":{"acknowledge_overage":true}}}},"responses":{"200":{"description":"Cancellation scheduled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingCancelResult"},"example":{"ok":true,"status":"active","cancel_at_period_end":true}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Cancelling would leave more members than base-plan seats.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingConflictError"},"example":{"error":"Cancelling will drop your seat count to 5 after the current billing period, but your organisation has 6 members. Please remove 1 member before the cancellation takes effect, or send acknowledge_overage:true to proceed anyway.","code":"CANCEL_WILL_OVERFLOW_BASE","current_users":6,"base_user_limit":5,"period_end":null}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/additionalLicensesCheckout":{"post":{"operationId":"additionalLicensesCheckout","summary":"Start Checkout for additional user licenses","description":"Requires: bearer token; role admin.\n1-100 seats. Trial organizations get 402; yearly base plans can only buy yearly seats. Use `additionalLicensesUpdateQuantity` when a subscription already exists (409).","tags":["Billing & Add-ons"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["quantity"],"properties":{"quantity":{"type":"integer","minimum":1,"maximum":100},"period":{"type":"string","enum":["month","year"],"default":"month"},"return_url":{"type":"string","description":"App-relative path (must start with `/`). Default `/dashboard/manage-subscription`."}}},"example":{"quantity":3,"period":"month","return_url":"/dashboard/manage-subscription"}}}},"responses":{"201":{"description":"Checkout session created; redirect to `url`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StripeCheckoutRedirect"},"example":{"url":"https://checkout.stripe.com/c/pay/cs_live_n3P4q5R6","sessionId":"cs_live_n3P4q5R6"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"The organization is on Trial (`code: TRIAL_UPGRADE_REQUIRED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingConflictError"},"example":{"error":"Additional licenses require a paid plan. Please upgrade your subscription first.","code":"TRIAL_UPGRADE_REQUIRED"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"An additional-licenses subscription is already active (`code: ALREADY_HAS_LICENSES`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingConflictError"},"example":{"error":"You already have an active additional-licenses subscription. Use \"Update quantity\" instead.","code":"ALREADY_HAS_LICENSES"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/additionalLicensesUpdateQuantity":{"post":{"operationId":"additionalLicensesUpdateQuantity","summary":"Change the number of additional licenses","description":"Requires: bearer token; role admin.\n0-100 seats; the prorated difference is invoiced immediately. `quantity: 0` behaves exactly like `additionalLicensesCancel` (same response).\nReducing below the active member count returns 409 `SEATS_BELOW_ACTIVE_USERS`.","tags":["Billing & Add-ons"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["quantity"],"properties":{"quantity":{"type":"integer","minimum":0,"maximum":100}}},"example":{"quantity":4}}}},"responses":{"200":{"description":"Quantity updated.","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"ok":{"type":"boolean","const":true},"count":{"type":"integer"},"status":{"type":"string"}}},{"$ref":"#/components/schemas/BillingCancelResult"}]},"example":{"ok":true,"count":4,"status":"active"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"New seat limit would be below the active member count.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingConflictError"},"example":{"error":"Cannot reduce to 0 additional seats - your organisation currently has 6 members, which would exceed the new 5-seat limit. Remove members first, then try again.","code":"SEATS_BELOW_ACTIVE_USERS","current_users":6,"proposed_effective":5,"minimum_extra_seats":1}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/addons":{"get":{"operationId":"addons","summary":"Get the add-ons package and SMS credit balance","description":"Requires: bearer token; any role.\nShows each add-on's purchased quantity, limits, usage and Stripe prices, plus rollover SMS/WhatsApp credits.","tags":["Billing & Add-ons"],"responses":{"200":{"description":"Current add-ons state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddonsState"},"example":{"period":"month","status":"active","cancel_at_period_end":false,"pending":null,"has_subscription":true,"items":{"expiries":{"label":"Extra Expiries","unit_size":50,"quantity":2,"base_limit":500,"limit":600,"usage":540,"prices":{"month":{"unit_amount":500,"currency":"USD","display":"5.00"},"year":{"unit_amount":5000,"currency":"USD","display":"50.00"}}}},"sms_credits":{"block_size":50,"balance":120,"total_purchased":200,"plan_balance":15,"price":{"unit_amount":1000,"currency":"USD","display":"10.00"}}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No organisation found for this user."}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"addons_post","summary":"Get the add-ons package and SMS credit balance (POST alias)","description":"Requires: bearer token; any role.\nShows each add-on's purchased quantity, limits, usage and Stripe prices, plus rollover SMS/WhatsApp credits.","tags":["Billing & Add-ons"],"responses":{"200":{"description":"Current add-ons state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddonsState"},"example":{"period":"month","status":"active","cancel_at_period_end":false,"pending":null,"has_subscription":true,"items":{"expiries":{"label":"Extra Expiries","unit_size":50,"quantity":2,"base_limit":500,"limit":600,"usage":540,"prices":{"month":{"unit_amount":500,"currency":"USD","display":"5.00"},"year":{"unit_amount":5000,"currency":"USD","display":"50.00"}}}},"sms_credits":{"block_size":50,"balance":120,"total_purchased":200,"plan_balance":15,"price":{"unit_amount":1000,"currency":"USD","display":"10.00"}}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No organisation found for this user."}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/addonsBuySmsCredits":{"post":{"operationId":"addonsBuySmsCredits","summary":"Buy SMS/WhatsApp credit blocks","description":"Requires: bearer token; role admin.\nOne-time Stripe Checkout for 1-200 credit blocks; credits never expire. Works with or without an add-ons package.","tags":["Billing & Add-ons"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["blocks"],"properties":{"blocks":{"type":"integer","minimum":1,"maximum":200},"return_url":{"type":"string","description":"App-relative path (must start with `/`)."}}},"example":{"blocks":2,"return_url":"/dashboard/addons"}}}},"responses":{"201":{"description":"Checkout session created; redirect to `url`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StripeCheckoutRedirect"},"example":{"url":"https://checkout.stripe.com/c/pay/cs_live_j9K0l1M2","sessionId":"cs_live_j9K0l1M2"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/addonsCancel":{"post":{"operationId":"addonsCancel","summary":"Cancel the add-ons package at period end","description":"Requires: bearer token; role admin.","tags":["Billing & Add-ons"],"responses":{"200":{"description":"Cancellation scheduled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingCancelResult"},"example":{"ok":true,"status":"active","cancel_at_period_end":true}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/addonsCheckout":{"post":{"operationId":"addonsCheckout","summary":"Start Checkout for an add-ons package","description":"Requires: bearer token; role admin.\nCreates a Stripe Checkout session for add-on units (recurring) and/or SMS/WhatsApp credit blocks (one-time).\nMax 100 units per item and 200 SMS blocks per request. Use `addonsUpdate` once a package exists (409 otherwise).","tags":["Billing & Add-ons"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"period":{"type":"string","enum":["month","year"],"default":"month"},"items":{"$ref":"#/components/schemas/AddonQuantities"},"sms_credit_blocks":{"type":"integer","minimum":0,"maximum":200},"return_url":{"type":"string","description":"App-relative path to return to (must start with `/`). Default `/dashboard/addons`."}}},"example":{"period":"month","items":{"expiries":2,"teams":1},"sms_credit_blocks":1,"return_url":"/dashboard/addons"}}}},"responses":{"201":{"description":"Checkout session created; redirect to `url`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StripeCheckoutRedirect"},"example":{"url":"https://checkout.stripe.com/c/pay/cs_live_e5F6g7H8","sessionId":"cs_live_e5F6g7H8"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"The organization already has an active add-ons package (`code: ALREADY_HAS_ADDONS`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingConflictError"},"example":{"error":"You already have an active add-ons package. Use \"Update package\" to change quantities instead.","code":"ALREADY_HAS_ADDONS"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/addonsUpdate":{"post":{"operationId":"addonsUpdate","summary":"Change add-on quantities","description":"Requires: bearer token; role admin.\nSets new quantities on the existing add-ons subscription and immediately invoices the prorated difference. Quantity 0 removes an item.\nA reduction below current usage is rejected with 409 `USAGE_ABOVE_NEW_LIMIT`. Out-of-range quantities return 500.","tags":["Billing & Add-ons"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"$ref":"#/components/schemas/AddonQuantities"}}},"example":{"items":{"expiries":3,"teams":0}}}}},"responses":{"200":{"description":"Quantities updated (or `unchanged: true` when nothing changed).","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"unchanged":{"type":"boolean"},"status":{"type":"string"},"items":{"type":"object","additionalProperties":{"type":"object","properties":{"quantity":{"type":"integer"},"unit_size":{"type":"integer"},"stripe_item_id":{"type":["string","null"]}}}}}},"example":{"ok":true,"status":"active","items":{"expiries":{"quantity":3,"unit_size":50,"stripe_item_id":"si_Q1r2S3"}}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Reduction would drop the limit below current usage.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingConflictError"},"example":{"error":"Cannot reduce \"Extra Expiries\" below your current usage (540). Reduce usage first, or keep at least 1 unit(s).","code":"USAGE_ABOVE_NEW_LIMIT","key":"expiries","current_usage":540,"proposed_effective":500}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/calculateSMSCost":{"post":{"operationId":"calculateSMSCost","summary":"Calculate SMS credits for a number and message","description":"Public (no token).\nResolves the destination country tier from `phone_number` and counts segments in `message` (160 GSM / 70 Unicode chars). Only POST is accepted.\n","tags":["Billing & Add-ons"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["phone_number"],"properties":{"phone_number":{"type":"string","description":"E.164 phone number."},"message":{"type":"string","description":"Message text. Defaults to an empty string."}}},"example":{"phone_number":"+14155550123","message":"Reminder: Northwind Dental business license expires on 2026-10-15."}}}},"responses":{"200":{"description":"Credit calculation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmsCostResponse"},"example":{"success":true,"data":{"phone_number":"+14155550123","country":"US","country_name":"United States","message_length":68,"segments":1,"credits_per_segment":1,"total_credits":1,"has_unicode":false}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/cancelSubscription":{"post":{"operationId":"cancelSubscription","summary":"Cancel the plan at the end of the billing period","description":"Requires: bearer token; role admin.\nSets the organization's Stripe subscription to cancel at period end and records the feedback.","tags":["Billing & Add-ons"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelSubscriptionInput"},"example":{"reason":"too_expensive","feedback":"We are consolidating tools at Northwind Dental."}}}},"responses":{"200":{"description":"Cancellation scheduled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelSubscriptionResult"},"example":{"status":"success","message":"Subscription will be canceled at the end of the billing period","cancel_at":"2026-10-15T00:00:00.000Z"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"No active subscription found, or another failure.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/BillingStatusError"},{"$ref":"#/components/schemas/Error"}]},"example":{"status":"error","message":"No active subscription found"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"User record not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingStatusError"},"example":{"status":"error","message":"User not found"}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/checkAppSumoLicense":{"get":{"operationId":"checkAppSumoLicense","summary":"Preview an AppSumo license before signup","description":"Public (no token); requires the single-use AppSumo `link_token` from the AppSumo redirect.\nRead-only preview; the license is linked at `register` (`appsumo_link_token`). Invalid, used or expired tokens and\nserver errors return 200 with `valid: false`.\n","tags":["Billing & Add-ons"],"parameters":[{"name":"link_token","in":"query","required":true,"description":"Single-use link token from the AppSumo redirect.","schema":{"type":"string"},"example":"9f2c4a7e1b3d5f60718293a4b5c6d7e8f9a0b1c2d3e4f5a6"}],"responses":{"200":{"description":"License preview.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppSumoLicenseCheck"},"example":{"valid":true,"tier":2,"plan_key":"appsumo_tier2","plan_name":"AppSumo Tier 2","limits":{"expiries":1000,"categories":50,"users":10,"teams":5,"contacts":500,"workflows":20,"workflow_runs":500,"collection_templates":20,"collection_requests":200}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Missing `link_token`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppSumoLicenseCheck"},"example":{"valid":false,"reason":"missing_link_token"}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"checkAppSumoLicense_post","summary":"Preview an AppSumo license before signup (POST)","description":"Public (no token); requires the single-use AppSumo `link_token`.\nSame as the GET form; `link_token` is still read from the query string.\n","tags":["Billing & Add-ons"],"parameters":[{"name":"link_token","in":"query","required":true,"description":"Single-use link token from the AppSumo redirect.","schema":{"type":"string"},"example":"9f2c4a7e1b3d5f60718293a4b5c6d7e8f9a0b1c2d3e4f5a6"}],"responses":{"200":{"description":"License preview.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppSumoLicenseCheck"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Missing `link_token`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppSumoLicenseCheck"}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/checkPromoCode":{"get":{"operationId":"checkPromoCode","summary":"Preview a promo code before signup","description":"Public (no token).\nRead-only preview for the signup page; the code is applied at `register` (`promo_code`). Unknown codes and\nserver errors return 200 with `valid: false`.\n","tags":["Billing & Add-ons"],"parameters":[{"name":"code","in":"query","required":true,"description":"Promo code (case-insensitive).","schema":{"type":"string"},"example":"LAUNCH80"}],"responses":{"200":{"description":"Code validity.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromoCodeCheck"},"example":{"valid":true,"code":"LAUNCH80","discount_percentage":80,"expiry_window_hours":72}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Missing `code`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromoCodeCheck"},"example":{"valid":false,"reason":"missing_code"}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[],"x-rate-limit":{"limit":10,"window":"15m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"checkPromoCode_post","summary":"Preview a promo code before signup (POST)","description":"Public (no token).\nSame as the GET form; `code` is still read from the query string.\n","tags":["Billing & Add-ons"],"parameters":[{"name":"code","in":"query","required":true,"description":"Promo code (case-insensitive).","schema":{"type":"string"},"example":"LAUNCH80"}],"responses":{"200":{"description":"Code validity.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromoCodeCheck"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Missing `code`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromoCodeCheck"}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[],"x-rate-limit":{"limit":10,"window":"15m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/createSubscription":{"post":{"operationId":"createSubscription","summary":"Start a plan checkout or change the current plan","description":"Requires: bearer token; role admin.\nWith no active plan subscription, creates a Stripe Checkout session (201) - redirect to `url`.\nWith an active/trialing/past_due subscription, swaps the price in place with an immediate prorated charge (200).\nReferral, promo and early-bird discounts are applied server-side from the organization's state.","tags":["Billing & Add-ons"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSubscriptionInput"},"example":{"subscription_plan_id":"prod_SJbWzGAm137eGN","frequency":"Year","price":348,"withdrawal_consent":true,"withdrawal_consent_at":"2026-09-27T14:05:00.000Z"}}}},"responses":{"200":{"description":"Existing subscription changed in place; the prorated difference was charged.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionUpgradeResult"},"example":{"status":"success","type":"upgrade","subscription_id":"sub_1QfT8sLm2Northwind","message":"Your plan has been updated. The prorated difference has been charged to your card on file."}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"201":{"description":"Stripe Checkout session created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutSessionResult"},"example":{"status":"success","sessionId":"cs_live_a1B2c3D4","url":"https://checkout.stripe.com/c/pay/cs_live_a1B2c3D4","message":"Checkout session created successfully."}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"Unknown plan, price mismatch, or another failure (`code: SUBSCRIPTION_ERROR`).","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/BillingStatusError"},{"$ref":"#/components/schemas/Error"}]},"example":{"status":"error","code":"INVALID_PRICE","message":"The selected price is not valid for this plan. Please refresh and try again."}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Existing subscription is in an inconsistent state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingStatusError"},"example":{"status":"error","message":"Existing subscription has no items - inconsistent state"}}}}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getAllSubscriptions":{"get":{"operationId":"getAllSubscriptions","summary":"List the caller's plan orders","description":"Requires: bearer token; any role.\nReturns every order bought by the calling user, newest first. Returns all records; unbounded.","tags":["Billing & Add-ons"],"responses":{"200":{"description":"Orders, newest first.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Subscription"}},"example":[{"id":"ord_3Jk8Wq","user_id":"u_71bXq","organization_id":"org_northwind","plan_type":"prod_SJbWzGAm137eGN","subscription_period":1,"period_unit":"month","subscription_id":"sub_1QfT8sLm2Northwind","currency":"usd","total_amount":29,"net_amount":29,"status":"completed","order_date":"2026-09-27T14:05:00.000Z","createdAt":"2026-09-27T14:05:00.000Z"}]}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getAllSubscriptions_post","summary":"List the caller's plan orders (POST alias)","description":"Requires: bearer token; any role.\nReturns every order bought by the calling user, newest first. Returns all records; unbounded.","tags":["Billing & Add-ons"],"responses":{"200":{"description":"Orders, newest first.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Subscription"}},"example":[{"id":"ord_3Jk8Wq","user_id":"u_71bXq","organization_id":"org_northwind","plan_type":"prod_SJbWzGAm137eGN","subscription_period":1,"period_unit":"month","subscription_id":"sub_1QfT8sLm2Northwind","currency":"usd","total_amount":29,"net_amount":29,"status":"completed","order_date":"2026-09-27T14:05:00.000Z","createdAt":"2026-09-27T14:05:00.000Z"}]}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getEarlyBirdStatus":{"get":{"operationId":"getEarlyBirdStatus","summary":"Early-bird discount eligibility","description":"Requires: bearer token; any role.\nTrial organizations that subscribe within 72 hours of signup get 30% off yearly. The organization is always the\ncaller's own; an `org_id` query parameter is ignored.\n","tags":["Billing & Add-ons"],"responses":{"200":{"description":"Eligibility.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EarlyBirdStatus"},"example":{"eligible":true,"discount_pct":30,"discount_period":"yearly","expires_at":"2026-09-30T14:05:00.000Z"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getEarlyBirdStatus_post","summary":"Early-bird discount eligibility (POST)","description":"Requires: bearer token; any role.\nSame as the GET form.\n","tags":["Billing & Add-ons"],"responses":{"200":{"description":"Eligibility.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EarlyBirdStatus"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getInvoices":{"post":{"operationId":"getInvoices","summary":"List the organization's invoices and refunds","description":"Requires: bearer token; any role.\nMerges the latest 50 Stripe invoices and refunds from the latest 100 charges, newest first. Returns `[]` when the organization has no Stripe customer.","tags":["Billing & Add-ons"],"responses":{"200":{"description":"Invoices and refunds.","content":{"application/json":{"schema":{"type":"object","required":["invoices"],"properties":{"invoices":{"type":"array","items":{"$ref":"#/components/schemas/BillingInvoice"}}}},"example":{"invoices":[{"id":"in_1QfU2aNorthwind","type":"invoice","number":"NW-0012","date":1790517900000,"periodStart":1790517900000,"periodEnd":1793196300000,"amount":29,"amountDue":29,"currency":"usd","status":"paid","pdfUrl":"https://pay.stripe.com/invoice/acct_1/in_1QfU2aNorthwind/pdf","hostedUrl":"https://invoice.stripe.com/i/acct_1/in_1QfU2aNorthwind","description":"Personal Lite (monthly)"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getMyReferral":{"get":{"operationId":"getMyReferral","summary":"Your affiliate referral dashboard","description":"Requires: bearer token; any role (self only).\nFinds the referral code whose referee is the caller and returns referred organizations, commissions (latest 200),\nevents and revenue totals. `hasReferralCode: false` when the caller has no code.\n","tags":["Billing & Add-ons"],"responses":{"200":{"description":"Referral summary.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyReferralSummary"},"example":{"hasReferralCode":true,"referralCode":{"referral_code":"CONTOSO25","referee_name":"Contoso Legal","referee_email":"partners@contosolegal.com","active":true},"referredOrgs":[{"organization_id":"org_northwind","organization_name":"Northwind Dental","organization_domain":"northwinddental.com","subscription_plan":"pro","subscription_status":"active","referred_at":"2026-09-27T14:05:00.000Z","discount_percentage":25,"referrer_commission_percentage":20,"last_payment_amount":290,"events":[],"org_revenue":290,"org_commission":58}],"commissions":[],"events":[],"revenueByPeriod":[{"year":2026,"month":9,"revenue":290,"commission":58,"payments":1,"label":"Sep 2026"}],"revenueByYear":[{"year":2026,"revenue":290,"commission":58,"payments":1}],"stats":{"totalReferrals":1,"activeSubscribers":1,"totalRevenueGenerated":290,"totalRefunded":0,"netRevenue":290,"totalCommissionEarned":58,"pendingCommission":58,"conversionRate":100,"commissionPercentage":20}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getMyReferral_post","summary":"Your affiliate referral dashboard (POST)","description":"Requires: bearer token; any role (self only).\nSame as the GET form.\n","tags":["Billing & Add-ons"],"responses":{"200":{"description":"Referral summary.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyReferralSummary"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getNotificationPricing":{"get":{"operationId":"getNotificationPricing","summary":"Get notification credit pricing","description":"Public (no token).\nStatic price sheet for email, SMS (per-country tier and segment) and WhatsApp credits. Only GET is accepted.\n","tags":["Billing & Add-ons"],"responses":{"200":{"description":"Pricing information.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationPricingResponse"},"example":{"success":true,"data":{"whatsapp":{"credits_per_message":1,"description":"WhatsApp messages cost 1 credit per message, regardless of destination country or message length (up to template limits)."},"sms":{"description":"SMS pricing varies by destination country and message length.","pricing_tiers":{"tier1":{"credits_per_segment":1,"description":"US, Canada, UK, Australia, and select countries","countries":["US","CA","GB","AU"]},"tier2":{"credits_per_segment":2,"description":"European countries and select regions","countries":["DE","FR","ES"]},"tier3":{"credits_per_segment":3,"description":"Asia, South America, Africa, Middle East, and other regions","countries":["IN","BR","ZA"]}},"segments_info":{"standard_gsm":{"chars_per_segment":160,"description":"Standard text messages without special characters"},"unicode":{"chars_per_segment":70,"description":"Messages with emoji or non-Latin characters"}}},"email":{"credits_per_email":1,"description":"Email notifications cost 1 credit per email sent."},"examples":[{"type":"SMS","destination":"United States (+1)","message_length":"150 characters","credits_required":1},{"type":"SMS","destination":"India (+91)","message_length":"320 characters (2 segments)","credits_required":6}]}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/getPaymentConfig":{"get":{"operationId":"getPaymentConfig","summary":"Get public payment provider keys","description":"Public (no token).\nReturns the Stripe publishable key and Razorpay key id. Development keys are returned when `env=development` or the request Origin contains `localhost`.\n","tags":["Billing & Add-ons"],"parameters":[{"name":"env","in":"query","required":false,"schema":{"type":"string","enum":["development","production"]},"example":"production"}],"responses":{"200":{"description":"Public payment configuration.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentConfigResponse"},"example":{"success":true,"data":{"stripe":{"publishableKey":"pk_live_51Nw2xExampleKey","environment":"production"},"razorpay":{"key":"rzp_live_ExampleKeyId","environment":"production"}}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The keys could not be loaded.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":false},"error":{"type":"string"}}},"example":{"success":false,"error":"Failed to fetch payment configuration"}}}}},"security":[],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getPaymentMethods":{"post":{"operationId":"getPaymentMethods","summary":"List cards on file","description":"Requires: bearer token; any role.\nUp to 25 cards on the organization's Stripe customer, with the default flagged. Returns `[]` when there is no Stripe customer.","tags":["Billing & Add-ons"],"responses":{"200":{"description":"Cards on file.","content":{"application/json":{"schema":{"type":"object","required":["payment_methods"],"properties":{"payment_methods":{"type":"array","items":{"$ref":"#/components/schemas/BillingPaymentMethod"}}}},"example":{"payment_methods":[{"id":"pm_1QfT7xNorthwind","brand":"visa","last4":"4242","exp_month":12,"exp_year":2028,"funding":"credit","country":"US","wallet":null,"default":true}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getPricingPlans":{"get":{"operationId":"getPricingPlans","summary":"List pricing plans","description":"Public (no token).\nReturns the `subscription_plans` documents whose `plan_type` equals `type`. Unbounded (small, static list).","tags":["Billing & Add-ons"],"parameters":[{"name":"type","in":"query","required":false,"description":"Plan cadence to list.","schema":{"type":"string","default":"monthly"},"example":"monthly"}],"responses":{"200":{"description":"Matching plans.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PricingPlan"}},"example":[{"id":"plan_personal_lite_monthly","plan_type":"monthly","name":"Personal Lite","price":29,"currency":"USD","subscription_plan_id":"prod_SJbWzGAm137eGN","features":["basic_tracking","email_notifications","recurring_expiries"]}]}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getPricingPlans_post","summary":"List pricing plans (POST alias)","description":"Public (no token).\nReturns the `subscription_plans` documents whose `plan_type` equals `type`. Unbounded (small, static list).","tags":["Billing & Add-ons"],"parameters":[{"name":"type","in":"query","required":false,"description":"Plan cadence to list.","schema":{"type":"string","default":"monthly"},"example":"monthly"}],"responses":{"200":{"description":"Matching plans.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PricingPlan"}},"example":[{"id":"plan_personal_lite_monthly","plan_type":"monthly","name":"Personal Lite","price":29,"currency":"USD","subscription_plan_id":"prod_SJbWzGAm137eGN","features":["basic_tracking","email_notifications","recurring_expiries"]}]}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getPromoStatus":{"get":{"operationId":"getPromoStatus","summary":"Live promo offer for the caller's organization","description":"Requires: bearer token; any role.\nReturns the organization's pending (unexpired, unpaid) promo code from signup, if any. An `org_id` query\nparameter is ignored.\n","tags":["Billing & Add-ons"],"responses":{"200":{"description":"Promo status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromoStatus"},"example":{"eligible":true,"code":"LAUNCH80","discount_percentage":80,"expires_at":"2026-09-30T14:05:00.000Z"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getPromoStatus_post","summary":"Live promo offer for the caller's organization (POST)","description":"Requires: bearer token; any role.\nSame as the GET form.\n","tags":["Billing & Add-ons"],"responses":{"200":{"description":"Promo status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromoStatus"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getReferralCodeDetails":{"get":{"operationId":"getReferralCodeDetails","summary":"Details and referred organizations for your referral code","description":"Requires: bearer token and the code's owner (referee) or a superadmin for the full view; no token for the public view.\nWithout an Authorization header the response is only the public offer terms (`ReferralCodePublic`, strict rate tier\n10 / 15 min per IP) - used by the signup page's referral banner. With a token, codes you do not own return 404, same\nas unknown codes; lists every referred organization; unbounded.\n","tags":["Billing & Add-ons"],"parameters":[{"name":"code","in":"query","required":true,"schema":{"type":"string"},"example":"CONTOSO25"}],"responses":{"200":{"description":"Code details (full view with a token, public offer terms without one).","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ReferralCodeDetails"},{"$ref":"#/components/schemas/ReferralCodePublic"}]},"example":{"code_info":{"referral_code":"CONTOSO25","referee_name":"Contoso Legal","referee_email":"partners@contosolegal.com","active":true,"current_uses":3,"user_discount_percentage":25,"user_discount_period":"monthly","user_discount_duration":3,"referrer_commission_percentage":20,"created_at":"2026-06-01T10:00:00.000Z"},"organizations":[{"organization_id":"org_northwind","organization_name":"Northwind Dental","created_at":"2026-09-27T14:05:00.000Z","referred_at":"2026-09-27T14:05:00.000Z","subscription_plan":"trial","subscription_status":"active"}],"total_organizations":1}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[{},{"bearerAuth":[]}],"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getReferralCodeDetails_post","summary":"Details and referred organizations for your referral code (POST)","description":"Requires: bearer token; the code's owner (referee) or a superadmin.\nSame as the GET form; `code` is still read from the query string.\n","tags":["Billing & Add-ons"],"parameters":[{"name":"code","in":"query","required":true,"schema":{"type":"string"},"example":"CONTOSO25"}],"responses":{"200":{"description":"Code details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReferralCodeDetails"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getSubscription":{"get":{"operationId":"getSubscription","summary":"Get a subscription record or the latest order","description":"Requires: bearer token; any role.\nWith `id`, returns that `subscriptions` document if it belongs to the caller or the caller's organization (404 otherwise).\nWithout `id`, returns the caller's most recent order as `{subscription}` (or `null`).","tags":["Billing & Add-ons"],"parameters":[{"name":"id","in":"query","required":false,"description":"A `subscriptions` document id (usually the organization id).","schema":{"type":"string"},"example":"org_northwind"}],"responses":{"200":{"description":"The subscription document (`{id, ...}`) when `id` is given, else `{subscription}`.","content":{"application/json":{"schema":{"oneOf":[{"type":"object","additionalProperties":true,"properties":{"id":{"type":"string"}},"required":["id"]},{"type":"object","required":["subscription"],"properties":{"subscription":{"oneOf":[{"$ref":"#/components/schemas/Subscription"},{"type":"null"}]}}}]},"example":{"subscription":{"id":"ord_3Jk8Wq","user_id":"u_71bXq","organization_id":"org_northwind","plan_type":"prod_SJbWzGAm137eGN","subscription_period":1,"period_unit":"month","subscription_id":"sub_1QfT8sLm2Northwind","currency":"usd","total_amount":29,"net_amount":29,"status":"completed","order_date":"2026-09-27T14:05:00.000Z","createdAt":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"getSubscription_post","summary":"Get a subscription record or the latest order (POST alias)","description":"Requires: bearer token; any role.\nWith `id`, returns that `subscriptions` document if it belongs to the caller or the caller's organization (404 otherwise).\nWithout `id`, returns the caller's most recent order as `{subscription}` (or `null`).","tags":["Billing & Add-ons"],"parameters":[{"name":"id","in":"query","required":false,"description":"A `subscriptions` document id (usually the organization id).","schema":{"type":"string"},"example":"org_northwind"}],"responses":{"200":{"description":"The subscription document (`{id, ...}`) when `id` is given, else `{subscription}`.","content":{"application/json":{"schema":{"oneOf":[{"type":"object","additionalProperties":true,"properties":{"id":{"type":"string"}},"required":["id"]},{"type":"object","required":["subscription"],"properties":{"subscription":{"oneOf":[{"$ref":"#/components/schemas/Subscription"},{"type":"null"}]}}}]},"example":{"subscription":{"id":"ord_3Jk8Wq","user_id":"u_71bXq","organization_id":"org_northwind","plan_type":"prod_SJbWzGAm137eGN","subscription_period":1,"period_unit":"month","subscription_id":"sub_1QfT8sLm2Northwind","currency":"usd","total_amount":29,"net_amount":29,"status":"completed","order_date":"2026-09-27T14:05:00.000Z","createdAt":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/checkContactLimit":{"post":{"operationId":"checkContactLimit","summary":"Check whether more contacts can be created","description":"Requires: bearer token; any role.\nLegacy check that counts contacts against the plan. Prefer `checkResourceLimit` with `resourceType: contacts`. Token failures return 500 (not 401) with `allowed: false`.\n","tags":["Usage & Limits"],"responses":{"200":{"description":"Limit check. `allowed` is false (still 200) when the limit is reached; `remaining` is omitted then.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LegacyResourceLimitCheck"},"example":{"allowed":false,"message":"You have reached your subscription limit of 50 contacts. Please upgrade your plan to add more.","current":50,"limit":50,"planName":"StarterLite"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The check failed (including an invalid or missing token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LegacyLimitCheckError"},"example":{"allowed":false,"message":"Unable to verify subscription limits. Please try again.","error":"Unauthorized - Invalid token"}}}}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/checkExpiryLimit":{"post":{"operationId":"checkExpiryLimit","summary":"Check whether more expiries can be created","description":"Requires: bearer token; any role.\nLegacy check that counts open (not archived, not done) expiries against the plan. Prefer `checkResourceLimit` with `resourceType: expiries`. Token failures return 500 (not 401) with `allowed: false`.\n","tags":["Usage & Limits"],"responses":{"200":{"description":"Limit check. `allowed` is false (still 200) when the limit is reached; `remaining` is omitted then.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LegacyResourceLimitCheck"},"example":{"allowed":true,"message":"You can create more expiries","current":42,"limit":200,"remaining":158,"planName":"StarterLite"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The check failed (including an invalid or missing token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LegacyLimitCheckError"},"example":{"allowed":false,"message":"Unable to verify subscription limits. Please try again.","error":"Unauthorized - Invalid token"}}}}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/checkResourceLimit":{"post":{"operationId":"checkResourceLimit","summary":"Check whether an amount of a resource fits the plan","description":"Requires: bearer token; any role.\nReturns `allowed: false` (still 200) when `current + amount` would exceed the limit or the subscription is not active. If the check itself fails it returns `allowed: true` with `reason: Error checking limits`. Token failures return 500 with `details`.\n","tags":["Usage & Limits"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["resourceType"],"properties":{"resourceType":{"type":"string","description":"A limit key, for example expiries, contacts, storage, users, workflows, workflow_runs, ai_scans, teams, categories, collection_templates, collection_requests, email_templates, custom_field_templates."},"amount":{"type":"integer","default":1,"description":"How many will be added (bytes for storage)."}}},"example":{"resourceType":"expiries","amount":1}}}},"responses":{"200":{"description":"Limit check.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/ResourceLimitCheck"}}},"example":{"success":true,"data":{"allowed":true,"current":312,"limit":1000,"remaining":688}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The request failed (including an invalid or missing token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageEndpointError"},"example":{"success":false,"error":"Unable to retrieve usage summary","details":"Unauthorized - Invalid token"}}}}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/checkUpload":{"post":{"operationId":"checkUpload","summary":"Check whether an upload fits the storage plan","description":"Requires: bearer token; any role.\nCall before uploading files. `file_size` is the total bytes of the planned upload (positive integer, max 524288000). Body `organization_id` and `plan_name` are ignored.\n","tags":["Usage & Limits"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file_size"],"properties":{"file_size":{"type":"integer","minimum":1,"maximum":524288000,"description":"Bytes to upload."}}},"example":{"file_size":2097152}}}},"responses":{"200":{"description":"Whether the upload is allowed. `allowed` is false (still 200) when it would exceed the limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorageUploadCheck"},"example":{"success":true,"allowed":true,"current_usage":52428800,"current_usage_formatted":"50 MB","limit":1073741824,"limit_formatted":"1 GB","file_size":2097152,"file_size_formatted":"2 MB"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getStorageUsage":{"post":{"operationId":"getStorageUsage","summary":"Get storage usage for your organization","description":"Requires: bearer token; any role.\nThe organization always comes from the caller's user record; a body `organization_id` sent by older clients is ignored.\n","tags":["Usage & Limits"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"organization_id":{"type":"string","deprecated":true,"description":"Ignored."}}},"example":{}}}},"responses":{"200":{"description":"Storage usage.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorageUsageResponse"},"example":{"success":true,"data":{"organization_id":"org_northwind","total_bytes_used":52428800,"total_used_formatted":"50 MB","limit_bytes":1073741824,"limit_formatted":"1 GB","percentage_used":"4.88","plan_name":"StarterLite","remaining_bytes":1021313024,"remaining_formatted":"974 MB"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getSubscriptionDetails":{"post":{"operationId":"getSubscriptionDetails","summary":"Get the organization's subscription, effective limits and add-ons","description":"Requires: bearer token; any role.\nLimits include per-organization overrides, additional user licenses and purchased add-ons. `status` is derived from the account expiry date. Token failures return 500 with `details`.\n","tags":["Usage & Limits"],"responses":{"200":{"description":"Subscription details.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/OrganizationSubscriptionDetails"}}},"example":{"success":true,"data":{"subscription_id":"prod_SK2RjJ132uucmf","plan_name":"Pro Essentials","status":"active","limits":{"expiries":1000,"contacts":250,"storage":5368709120,"users":5,"ai_scans":25,"carry_over":true},"features":["basic_tracking","email_notifications","recurring_expiries"],"has_custom_limits":false,"addons":{"status":"none","period":null,"stripe_subscription_id":null,"cancel_at_period_end":false,"items":{"expiries":{"quantity":0,"unit_size":0}}},"additional_user_licenses":{"count":0,"period":null,"status":"none","stripe_subscription_id":null,"cancel_at_period_end":false,"last_invoice_at":null,"last_payment_failed_at":null},"base_user_limit":5}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The request failed (including an invalid or missing token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageEndpointError"},"example":{"success":false,"error":"Unable to retrieve usage summary","details":"Unauthorized - Invalid token"}}}}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getSubscriptionLimits":{"post":{"operationId":"getSubscriptionLimits","summary":"Get plan limits with current expiry and contact counts","description":"Requires: bearer token; any role.\nLegacy endpoint; `getUsageSummary` covers every resource. Unlimited values serialize as `null` in `limits` and as the string `Unlimited` in `remaining`.\n","tags":["Usage & Limits"],"responses":{"200":{"description":"Plan limits and counts.","content":{"application/json":{"schema":{"type":"object","properties":{"planName":{"type":"string"},"limits":{"$ref":"#/components/schemas/PlanLimitValues"},"current":{"type":"object","properties":{"expiries":{"type":"integer"},"contacts":{"type":"integer"}}},"remaining":{"type":"object","properties":{"expiries":{"oneOf":[{"type":"integer"},{"type":"string","const":"Unlimited"}]},"contacts":{"oneOf":[{"type":"integer"},{"type":"string","const":"Unlimited"}]}}}}},"example":{"planName":"StarterLite","limits":{"name":"Starter Lite","expiries":200,"contacts":50,"storage":1073741824,"users":2,"teams":1,"categories":15,"email_credits":null,"message_credits":85,"workflows":5,"workflow_runs":50,"ai_scans":0},"current":{"expiries":42,"contacts":18},"remaining":{"expiries":158,"contacts":32}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/getUsageSummary":{"post":{"operationId":"getUsageSummary","summary":"Get plan, limits, usage and remaining capacity","description":"Requires: bearer token; any role.\nThe main usage endpoint used by the web app. `ai_scans` and `collection_requests` are counted per calendar month (UTC). Unlimited limits serialize as `null`; unlimited `remaining` values are the string `Unlimited`. Token failures return 500 with `details`.\n","tags":["Usage & Limits"],"responses":{"200":{"description":"Usage summary.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/UsageSummary"}}},"example":{"success":true,"data":{"subscription":{"id":"prod_SK2RjJ132uucmf","plan_name":"Pro Essentials","status":"active","features":["basic_tracking","email_notifications","sms_notifications","recurring_expiries"]},"limits":{"expiries":1000,"contacts":250,"storage":5368709120,"users":5,"email_credits":null,"message_credits":300,"workflows":20,"workflow_runs":200,"ai_scans":25,"teams":5,"categories":50,"collection_templates":25,"collection_requests":150,"email_templates":15,"custom_field_templates":10,"carry_over":true},"usage":{"expiries":312,"contacts":88,"storage":157286400,"users":4,"workflows":6,"workflow_runs":41,"ai_scans":7,"teams":2,"categories":12,"collection_templates":3,"collection_requests":19,"email_templates":2,"custom_field_templates":1},"remaining":{"expiries":688,"contacts":162,"storage":5211422720,"users":1,"ai_scans":18},"percentage":{"expiries":31,"contacts":35,"storage":3,"users":80,"ai_scans":28},"ai_scans_reset_at":"2026-10-01T00:00:00.000Z","ai_scans_bucket":"2026-09","base_user_limit":5,"additional_user_licenses":{"count":0,"period":null,"status":"none","stripe_subscription_id":null,"cancel_at_period_end":false,"last_invoice_at":null,"last_payment_failed_at":null}}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The request failed (including an invalid or missing token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageEndpointError"},"example":{"success":false,"error":"Unable to retrieve usage summary","details":"Unauthorized - Invalid token"}}}}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/recalculateStorage":{"post":{"operationId":"recalculateStorage","summary":"Recalculate storage usage from Cloud Storage","description":"Requires: bearer token; role admin.\nScans the organization's files in Cloud Storage and overwrites the storage counter. Slow for large organizations.\n","tags":["Usage & Limits"],"responses":{"200":{"description":"Recalculated usage.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"total_bytes":{"type":"integer"},"total_formatted":{"type":"string"}}},"example":{"success":true,"message":"Storage recalculated","total_bytes":54525952,"total_formatted":"52 MB"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/recalculateUsage":{"post":{"operationId":"recalculateUsage","summary":"Recount usage counters from scratch","description":"Requires: bearer token; any role.\nRecounts expiries (including archived and done), contacts, users, workflows, workflow runs and stored bytes, saves them and returns the counts. Token failures return 500 with `details`.\n","tags":["Usage & Limits"],"responses":{"200":{"description":"Recalculated counts.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"data":{"type":"object","properties":{"expiries":{"type":"integer"},"contacts":{"type":"integer"},"storage":{"type":"integer","description":"Bytes."},"users":{"type":"integer"},"workflows":{"type":"integer"},"workflow_runs":{"type":"integer"}}}}},"example":{"success":true,"message":"Usage recalculated successfully","data":{"expiries":312,"contacts":88,"storage":157286400,"users":4,"workflows":6,"workflow_runs":41}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The request failed (including an invalid or missing token).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageEndpointError"},"example":{"success":false,"error":"Unable to retrieve usage summary","details":"Unauthorized - Invalid token"}}}}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/recordUpload":{"post":{"operationId":"recordUpload","summary":"Record uploaded bytes against storage usage","description":"Requires: bearer token; any role.\nCall after a successful upload to add `file_size` bytes (positive integer, max 524288000) to the organization's storage counter.\n","tags":["Usage & Limits"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["file_size"],"properties":{"file_size":{"type":"integer","minimum":1,"maximum":524288000}}},"example":{"file_size":2097152}}}},"responses":{"200":{"description":"Storage counter updated.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"new_total":{"type":"integer","description":"Total bytes used after the update."},"new_total_formatted":{"type":"string"}}},"example":{"success":true,"message":"Storage updated","new_total":54525952,"new_total_formatted":"52 MB"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/createWebhookSubscription":{"post":{"operationId":"createWebhookSubscription","summary":"Register an outgoing webhook URL for one or more events","description":"Requires: bearer token; role admin.\nThe URL must be https (port 443 or 8443) on a public hostname - IP addresses, localhost and internal names are rejected.\nReturns the signing secret once; later reads only show `secret_hint`. At most 20 subscriptions per organization.\nDeliveries are signed POSTs of a `WebhookEvent` (see the webhooks guide).\n","tags":["Integrations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionInput"},"example":{"url":"https://hooks.northwind-dental.com/expiryedge","events":["expiry.completed","collection_submission.received"],"description":"CRM sync"}}}},"responses":{"201":{"description":"Created. `subscription.secret` is shown only in this response.","content":{"application/json":{"schema":{"type":"object","required":["subscription"],"properties":{"subscription":{"allOf":[{"$ref":"#/components/schemas/WebhookSubscription"},{"type":"object","required":["secret"],"properties":{"secret":{"type":"string","description":"HMAC-SHA256 signing secret (`whsec_` + 64 hex). Store it now."}}}]}}},"example":{"subscription":{"id":"wh_3kQ9xLm2","url":"https://hooks.northwind-dental.com/expiryedge","events":["expiry.completed","collection_submission.received"],"description":"CRM sync","active":true,"disabled_reason":null,"disabled_at":null,"consecutive_failures":0,"last_delivery_at":null,"last_delivery_status":null,"secret_hint":"whsec_...9f2c","created_by":"u_71bXq","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z","secret":"whsec_4b1e0c2d7a9e5f3b8c1d6e4f2a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f9f2c"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"409":{"description":"The organization already has 20 webhook subscriptions (code WEBHOOK_LIMIT_REACHED).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"An organization can have at most 20 webhook subscriptions. Delete one first.","code":"WEBHOOK_LIMIT_REACHED"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/deleteWebhookSubscription":{"post":{"operationId":"deleteWebhookSubscription","summary":"Delete a webhook subscription and its delivery log","description":"Requires: bearer token; role admin.\nPending retries for this subscription are dropped. Also accepts DELETE with `?id=`.\n","tags":["Integrations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionIdRequest"},"example":{"id":"wh_3kQ9xLm2"}}}},"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","required":["deleted","id"],"properties":{"deleted":{"type":"boolean"},"id":{"type":"string"}}},"example":{"deleted":true,"id":"wh_3kQ9xLm2"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"delete":{"operationId":"deleteWebhookSubscription_delete","summary":"Delete a webhook subscription (DELETE form)","description":"Requires: bearer token; role admin.\nSame as the POST form, with the id as a query parameter.\n","tags":["Integrations"],"parameters":[{"name":"id","in":"query","required":true,"schema":{"type":"string"},"example":"wh_3kQ9xLm2"}],"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","required":["deleted","id"],"properties":{"deleted":{"type":"boolean"},"id":{"type":"string"}}},"example":{"deleted":true,"id":"wh_3kQ9xLm2"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/getWebhookDeliveries":{"get":{"operationId":"getWebhookDeliveries","summary":"Recent deliveries for one webhook","description":"Requires: bearer token; role admin.\nReturns the last 50 deliveries, newest first. Failed attempts are retried after 5m, 30m, 2h, 6h and 15h (6 attempts), then marked `failed`.\n","tags":["Integrations"],"parameters":[{"name":"id","in":"query","required":true,"description":"Subscription id.","schema":{"type":"string"},"example":"wh_3kQ9xLm2"}],"responses":{"200":{"description":"Delivery log.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","maxItems":50,"items":{"$ref":"#/components/schemas/WebhookDelivery"}}}},"example":{"data":[{"id":"dlv_5c2e9a0b1d4f6a8c3e7b9d1f","event_id":"evt_8c1f0a2b3c4d5e6f7a8b9c0d","type":"expiry.completed","status":"pending","attempts":2,"created_at":"2026-10-03T08:14:22.120Z","last_attempt_at":"2026-10-03T08:19:30.004Z","next_attempt_at":"2026-10-03T08:49:30.004Z","last_status_code":503,"last_error":"HTTP 503","duration_ms":412}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/getWebhookSubscriptions":{"get":{"operationId":"getWebhookSubscriptions","summary":"List the organization's webhook subscriptions","description":"Requires: bearer token; role admin.\nReturns all subscriptions (at most 20), oldest first, without secrets, plus the list of subscribable event types.\n","tags":["Integrations"],"responses":{"200":{"description":"Subscriptions and the available event types.","content":{"application/json":{"schema":{"type":"object","required":["data","events"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookSubscription"}},"events":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEventType"}}}},"example":{"data":[{"id":"wh_3kQ9xLm2","url":"https://hooks.northwind-dental.com/expiryedge","events":["expiry.completed"],"description":"CRM sync","active":true,"disabled_reason":null,"disabled_at":null,"consecutive_failures":0,"last_delivery_at":"2026-10-03T08:14:22.500Z","last_delivery_status":"succeeded","secret_hint":"whsec_...9f2c","created_by":"u_71bXq","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}],"events":["expiry.created","expiry.updated","expiry.completed","expiry.deleted","collection_request.completed","collection_submission.received"]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/integrations":{"get":{"operationId":"integrations","summary":"List integrations and their connection state","description":"Requires: bearer token; any role.","tags":["Integrations"],"responses":{"200":{"description":"All registered providers.","content":{"application/json":{"schema":{"type":"object","required":["integrations"],"properties":{"integrations":{"type":"array","items":{"$ref":"#/components/schemas/IntegrationSummary"}}}},"example":{"integrations":[{"id":"asana","name":"Asana","connected":true,"enabled":true},{"id":"clickup","name":"ClickUp","connected":false,"enabled":false}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"integrations_post","summary":"List integrations and their connection state (POST alias)","description":"Requires: bearer token; any role.","tags":["Integrations"],"responses":{"200":{"description":"All registered providers.","content":{"application/json":{"schema":{"type":"object","required":["integrations"],"properties":{"integrations":{"type":"array","items":{"$ref":"#/components/schemas/IntegrationSummary"}}}},"example":{"integrations":[{"id":"asana","name":"Asana","connected":true,"enabled":true},{"id":"clickup","name":"ClickUp","connected":false,"enabled":false}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/{provider}/claimConnection":{"post":{"operationId":"integrationsClaimConnection","summary":"Claim a connection started from the provider","description":"Requires: bearer token; role editor or admin.\nAfter an install started in the provider's app directory, moves the pending tokens identified by `nonce` into the caller's organization.","tags":["Integrations"],"parameters":[{"name":"provider","in":"path","required":true,"description":"Integration provider.","schema":{"$ref":"#/components/schemas/IntegrationProviderId"},"example":"asana"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["nonce"],"properties":{"nonce":{"type":"string","description":"The pending-connection nonce from the redirect URL."}}},"example":{"nonce":"9f2c4e1a7b"}}}},"responses":{"200":{"description":"Connected.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true,"message":"Asana connected successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Unknown provider, or the pending connection was not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Pending connection not found or expired. Please try connecting again."}}}},"410":{"description":"The pending connection expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Pending connection expired. Please try connecting again."}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/{provider}/disconnect":{"post":{"operationId":"integrationsDisconnect","summary":"Disconnect an integration","description":"Requires: bearer token; role editor or admin.\nClears the stored tokens and disables the integration; saved settings are kept for a later reconnect.","tags":["Integrations"],"parameters":[{"name":"provider","in":"path","required":true,"description":"Integration provider.","schema":{"$ref":"#/components/schemas/IntegrationProviderId"},"example":"asana"}],"responses":{"200":{"description":"Disconnected.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true,"message":"Asana disconnected successfully"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Unknown provider.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntegrationUnknownProviderError"},"example":{"error":"Integration 'slack' not found","available":["asana","clickup"]}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/{provider}/oauthUrl":{"get":{"operationId":"integrationsOauthUrl","summary":"Get the OAuth authorization URL","description":"Requires: bearer token; role editor or admin.\nReturns the provider consent URL with a single-use state valid for 10 minutes. Open it in a popup.","tags":["Integrations"],"parameters":[{"name":"provider","in":"path","required":true,"description":"Integration provider.","schema":{"$ref":"#/components/schemas/IntegrationProviderId"},"example":"asana"},{"name":"from","in":"query","required":false,"description":"Set when the provider itself opened the popup; other values are ignored.","schema":{"type":"string","enum":["asana","clickup"]}}],"responses":{"200":{"description":"Authorization URL.","content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri"}}},"example":{"url":"https://app.asana.com/-/oauth_authorize?response_type=code&client_id=120000000000&state=eyJhbGciOi..."}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Unknown provider.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntegrationUnknownProviderError"},"example":{"error":"Integration 'slack' not found","available":["asana","clickup"]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"integrationsOauthUrl_post","summary":"Get the OAuth authorization URL (POST alias)","description":"Requires: bearer token; role editor or admin.\nReturns the provider consent URL with a single-use state valid for 10 minutes. Open it in a popup.","tags":["Integrations"],"parameters":[{"name":"provider","in":"path","required":true,"description":"Integration provider.","schema":{"$ref":"#/components/schemas/IntegrationProviderId"},"example":"asana"},{"name":"from","in":"query","required":false,"description":"Set when the provider itself opened the popup; other values are ignored.","schema":{"type":"string","enum":["asana","clickup"]}}],"responses":{"200":{"description":"Authorization URL.","content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri"}}},"example":{"url":"https://app.asana.com/-/oauth_authorize?response_type=code&client_id=120000000000&state=eyJhbGciOi..."}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Unknown provider.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntegrationUnknownProviderError"},"example":{"error":"Integration 'slack' not found","available":["asana","clickup"]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/{provider}/status":{"get":{"operationId":"integrationsStatus","summary":"Get connection status","description":"Requires: bearer token; any role.","tags":["Integrations"],"parameters":[{"name":"provider","in":"path","required":true,"description":"Integration provider.","schema":{"$ref":"#/components/schemas/IntegrationProviderId"},"example":"asana"}],"responses":{"200":{"description":"Status for the caller's organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntegrationStatus"},"example":{"connected":true,"enabled":true,"connected_at":"2026-09-27T14:05:00.000Z","asana_user_name":"Dana Whitfield","asana_user_email":"dana@northwinddental.com","workspaces":[{"gid":"1200000000000001","name":"Northwind Dental"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Unknown provider.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntegrationUnknownProviderError"},"example":{"error":"Integration 'slack' not found","available":["asana","clickup"]}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"integrationsStatus_post","summary":"Get connection status (POST alias)","description":"Requires: bearer token; any role.","tags":["Integrations"],"parameters":[{"name":"provider","in":"path","required":true,"description":"Integration provider.","schema":{"$ref":"#/components/schemas/IntegrationProviderId"},"example":"asana"}],"responses":{"200":{"description":"Status for the caller's organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntegrationStatus"},"example":{"connected":true,"enabled":true,"connected_at":"2026-09-27T14:05:00.000Z","asana_user_name":"Dana Whitfield","asana_user_email":"dana@northwinddental.com","workspaces":[{"gid":"1200000000000001","name":"Northwind Dental"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Unknown provider.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntegrationUnknownProviderError"},"example":{"error":"Integration 'slack' not found","available":["asana","clickup"]}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/asana/createExpiry":{"post":{"operationId":"integrationsAsanaCreateExpiry","summary":"Create an expiry from an Asana task","description":"Requires: bearer token; role editor or admin.\nCopies the task name, notes and due date into a new expiry linked to the task.","tags":["Integrations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["taskGid"],"properties":{"taskGid":{"type":"string","description":"Numeric Asana task gid."}}},"example":{"taskGid":"1200000000000777"}}}},"responses":{"200":{"description":"Expiry created from the task (200, not 201).","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"expiryId":{"type":"string"}}},"example":{"success":true,"expiryId":"exp_4Tq9sLm2"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/asana/deleteWebhook":{"post":{"operationId":"integrationsAsanaDeleteWebhook_post","summary":"Delete the Asana webhook (POST alias)","description":"Requires: bearer token; role editor or admin.\n400 when no webhook is registered.","tags":["Integrations"],"responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true,"message":"Webhook deleted"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"delete":{"operationId":"integrationsAsanaDeleteWebhook","summary":"Delete the Asana webhook","description":"Requires: bearer token; role editor or admin.\n400 when no webhook is registered.","tags":["Integrations"],"responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true,"message":"Webhook deleted"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/asana/orgSettings":{"get":{"operationId":"integrationsAsanaOrgSettings","summary":"Get Asana sync settings","description":"Requires: bearer token; any role.\nReturns `{connected: false}` when Asana was never connected.","tags":["Integrations"],"responses":{"200":{"description":"Settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AsanaOrgSettings"},"example":{"connected":true,"default_workspace_gid":"1200000000000001","default_project_gid":"1200000000000042","sync_on_create":true,"sync_on_update":true,"sync_from_asana":true,"enabled":true}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"integrationsAsanaOrgSettings_post","summary":"Get Asana sync settings (POST alias)","description":"Requires: bearer token; any role.\nReturns `{connected: false}` when Asana was never connected.","tags":["Integrations"],"responses":{"200":{"description":"Settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AsanaOrgSettings"},"example":{"connected":true,"default_workspace_gid":"1200000000000001","default_project_gid":"1200000000000042","sync_on_create":true,"sync_on_update":true,"sync_from_asana":true,"enabled":true}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/asana/projects":{"get":{"operationId":"integrationsAsanaProjects","summary":"List Asana projects in a workspace","description":"Requires: bearer token; any role.","tags":["Integrations"],"parameters":[{"name":"workspace_gid","in":"query","required":true,"description":"Workspace gid.","schema":{"type":"string"},"example":"1200000000000001"}],"responses":{"200":{"description":"Projects (`gid`, `name`, `color`, `archived`).","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"projects":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"example":{"success":true,"projects":[{"gid":"1200000000000042","name":"Licenses & Renewals","color":"dark-green","archived":false}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"integrationsAsanaProjects_post","summary":"List Asana projects in a workspace (POST alias)","description":"Requires: bearer token; any role.","tags":["Integrations"],"parameters":[{"name":"workspace_gid","in":"query","required":true,"description":"Workspace gid.","schema":{"type":"string"},"example":"1200000000000001"}],"responses":{"200":{"description":"Projects (`gid`, `name`, `color`, `archived`).","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"projects":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"example":{"success":true,"projects":[{"gid":"1200000000000042","name":"Licenses & Renewals","color":"dark-green","archived":false}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/asana/registerWebhook":{"post":{"operationId":"integrationsAsanaRegisterWebhook","summary":"Register the Asana real-time webhook","description":"Requires: bearer token; role editor or admin.\nReplaces any existing webhook for the organization. `resource_gid` is a project gid (preferred) or workspace gid.","tags":["Integrations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["resource_gid"],"properties":{"resource_gid":{"type":"string"}}},"example":{"resource_gid":"1200000000000042"}}}},"responses":{"200":{"description":"Webhook registered; it becomes active after Asana's handshake.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"webhook_gid":{"type":"string"},"target":{"type":"string","format":"uri"}}},"example":{"success":true,"webhook_gid":"1200000000009001","target":"https://us-central1-expiryedge.cloudfunctions.net/api/integrations/asana/webhook?orgId=org_northwind"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/asana/saveOrgSettings":{"post":{"operationId":"integrationsAsanaSaveOrgSettings","summary":"Save Asana sync settings","description":"Requires: bearer token; role editor or admin.\nReplaces all settings; omitted booleans become false (except `sync_from_asana`, default true).","tags":["Integrations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"default_workspace_gid":{"type":["string","null"]},"default_project_gid":{"type":["string","null"]},"sync_on_create":{"type":"boolean"},"sync_on_update":{"type":"boolean"},"sync_from_asana":{"type":"boolean","default":true}}},"example":{"default_workspace_gid":"1200000000000001","default_project_gid":"1200000000000042","sync_on_create":true,"sync_on_update":true,"sync_from_asana":true}}}},"responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/asana/syncExpiry":{"post":{"operationId":"integrationsAsanaSyncExpiry","summary":"Push an expiry to Asana as a task","description":"Requires: bearer token; role editor or admin.\nCreates the task, or updates the linked task (recreating it if it was deleted in Asana). 404 if the expiry is not in your organization.","tags":["Integrations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expiryId","workspaceGid"],"properties":{"expiryId":{"type":"string"},"workspaceGid":{"type":"string"},"projectGid":{"type":"string"},"assigneeGid":{"type":"string"}}},"example":{"expiryId":"exp_4Tq9sLm2","workspaceGid":"1200000000000001","projectGid":"1200000000000042"}}}},"responses":{"200":{"description":"Task created or updated and linked to the expiry.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"task":{"$ref":"#/components/schemas/IntegrationTaskLink"}}},"example":{"success":true,"task":{"gid":"1200000000000777","name":"Dental board license","url":"https://app.asana.com/0/1200000000000042/1200000000000777"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/asana/syncFromAsana":{"post":{"operationId":"integrationsAsanaSyncFromAsana","summary":"Pull tasks from Asana now","description":"Requires: bearer token; role editor or admin.","tags":["Integrations"],"responses":{"200":{"description":"Sync finished. Only tasks with a due date are imported; safe to repeat.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntegrationSyncRunResult"},"example":{"success":true,"created":3,"updated":5,"skipped":1,"errors":0}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/asana/tasks":{"get":{"operationId":"integrationsAsanaTasks","summary":"List Asana tasks","description":"Requires: bearer token; any role.\nPass `project_gid`, or `workspace_gid` (optionally with `assignee`, default `me`).","tags":["Integrations"],"parameters":[{"name":"project_gid","in":"query","required":false,"description":"List tasks in this project.","schema":{"type":"string"},"example":"1200000000000042"},{"name":"workspace_gid","in":"query","required":false,"description":"List tasks in this workspace for `assignee`.","schema":{"type":"string"}},{"name":"assignee","in":"query","required":false,"description":"Asana user gid or `me`; used with `workspace_gid`.","schema":{"type":"string","default":"me"}}],"responses":{"200":{"description":"Tasks (`gid`, `name`, `due_on`, `notes`, `assignee`, `completed`).","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"tasks":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"example":{"success":true,"tasks":[{"gid":"1200000000000777","name":"Renew dental board license","due_on":"2026-10-15","notes":"","assignee":null,"completed":false}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"integrationsAsanaTasks_post","summary":"List Asana tasks (POST alias)","description":"Requires: bearer token; any role.\nPass `project_gid`, or `workspace_gid` (optionally with `assignee`, default `me`).","tags":["Integrations"],"parameters":[{"name":"project_gid","in":"query","required":false,"description":"List tasks in this project.","schema":{"type":"string"},"example":"1200000000000042"},{"name":"workspace_gid","in":"query","required":false,"description":"List tasks in this workspace for `assignee`.","schema":{"type":"string"}},{"name":"assignee","in":"query","required":false,"description":"Asana user gid or `me`; used with `workspace_gid`.","schema":{"type":"string","default":"me"}}],"responses":{"200":{"description":"Tasks (`gid`, `name`, `due_on`, `notes`, `assignee`, `completed`).","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"tasks":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"example":{"success":true,"tasks":[{"gid":"1200000000000777","name":"Renew dental board license","due_on":"2026-10-15","notes":"","assignee":null,"completed":false}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/asana/toggleIntegration":{"put":{"operationId":"integrationsAsanaToggleIntegration","summary":"Enable or disable the Asana integration","description":"Requires: bearer token; role editor or admin.","tags":["Integrations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean"}}},"example":{"enabled":true}}}},"responses":{"200":{"description":"New state.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"enabled":{"type":"boolean"}}},"example":{"success":true,"enabled":true}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"integrationsAsanaToggleIntegration_post","summary":"Enable or disable the Asana integration (POST alias)","description":"Requires: bearer token; role editor or admin.","tags":["Integrations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean"}}},"example":{"enabled":true}}}},"responses":{"200":{"description":"New state.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"enabled":{"type":"boolean"}}},"example":{"success":true,"enabled":true}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/asana/unsyncExpiry":{"post":{"operationId":"integrationsAsanaUnsyncExpiry_post","summary":"Unlink an expiry from its Asana task (POST alias)","description":"Requires: bearer token; role editor or admin.\nRemoves the link only; the Asana task is not touched.","tags":["Integrations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expiryId"],"properties":{"expiryId":{"type":"string"}}},"example":{"expiryId":"exp_4Tq9sLm2"}}}},"responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true,"message":"Asana sync removed"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"delete":{"operationId":"integrationsAsanaUnsyncExpiry","summary":"Unlink an expiry from its Asana task","description":"Requires: bearer token; role editor or admin.\nRemoves the link only; the Asana task is not touched.","tags":["Integrations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expiryId"],"properties":{"expiryId":{"type":"string"}}},"example":{"expiryId":"exp_4Tq9sLm2"}}}},"responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true,"message":"Asana sync removed"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/asana/workspaces":{"get":{"operationId":"integrationsAsanaWorkspaces","summary":"List Asana workspaces","description":"Requires: bearer token; any role.","tags":["Integrations"],"responses":{"200":{"description":"Workspaces visible to the connected Asana account.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"workspaces":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"example":{"success":true,"workspaces":[{"gid":"1200000000000001","name":"Northwind Dental","is_organization":true}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"integrationsAsanaWorkspaces_post","summary":"List Asana workspaces (POST alias)","description":"Requires: bearer token; any role.","tags":["Integrations"],"responses":{"200":{"description":"Workspaces visible to the connected Asana account.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"workspaces":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"example":{"success":true,"workspaces":[{"gid":"1200000000000001","name":"Northwind Dental","is_organization":true}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/clickup/createExpiry":{"post":{"operationId":"integrationsClickupCreateExpiry","summary":"Create an expiry from a ClickUp task","description":"Requires: bearer token; role editor or admin.\nCopies the task name, description and due date into a new expiry linked to the task.","tags":["Integrations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["taskId"],"properties":{"taskId":{"type":"string"}}},"example":{"taskId":"86abc123"}}}},"responses":{"200":{"description":"Expiry created from the task (200, not 201).","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"expiryId":{"type":"string"}}},"example":{"success":true,"expiryId":"exp_4Tq9sLm2"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/clickup/deleteWebhook":{"post":{"operationId":"integrationsClickupDeleteWebhook_post","summary":"Delete the ClickUp webhook (POST alias)","description":"Requires: bearer token; role editor or admin.\n400 when no webhook is registered.","tags":["Integrations"],"responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true,"message":"Webhook deleted"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"delete":{"operationId":"integrationsClickupDeleteWebhook","summary":"Delete the ClickUp webhook","description":"Requires: bearer token; role editor or admin.\n400 when no webhook is registered.","tags":["Integrations"],"responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true,"message":"Webhook deleted"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/clickup/folders":{"get":{"operationId":"integrationsClickupFolders","summary":"List ClickUp folders","description":"Requires: bearer token; any role.","tags":["Integrations"],"parameters":[{"name":"space_id","in":"query","required":true,"description":"ClickUp space id.","schema":{"type":"string"},"example":"90120001"}],"responses":{"200":{"description":"Non-archived folders.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"folders":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"example":{"success":true,"folders":[{"id":"90120101","name":"Licenses"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"integrationsClickupFolders_post","summary":"List ClickUp folders (POST alias)","description":"Requires: bearer token; any role.","tags":["Integrations"],"parameters":[{"name":"space_id","in":"query","required":true,"description":"ClickUp space id.","schema":{"type":"string"},"example":"90120001"}],"responses":{"200":{"description":"Non-archived folders.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"folders":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"example":{"success":true,"folders":[{"id":"90120101","name":"Licenses"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/clickup/lists":{"get":{"operationId":"integrationsClickupLists","summary":"List ClickUp lists","description":"Requires: bearer token; any role.\nPass `folder_id`, or `space_id` for folderless lists.","tags":["Integrations"],"parameters":[{"name":"folder_id","in":"query","required":false,"description":"ClickUp folder id.","schema":{"type":"string"},"example":"90120101"},{"name":"space_id","in":"query","required":false,"description":"ClickUp space id (folderless lists).","schema":{"type":"string"}}],"responses":{"200":{"description":"Non-archived lists.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"lists":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"example":{"success":true,"lists":[{"id":"901201010","name":"Bar renewals"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"integrationsClickupLists_post","summary":"List ClickUp lists (POST alias)","description":"Requires: bearer token; any role.\nPass `folder_id`, or `space_id` for folderless lists.","tags":["Integrations"],"parameters":[{"name":"folder_id","in":"query","required":false,"description":"ClickUp folder id.","schema":{"type":"string"},"example":"90120101"},{"name":"space_id","in":"query","required":false,"description":"ClickUp space id (folderless lists).","schema":{"type":"string"}}],"responses":{"200":{"description":"Non-archived lists.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"lists":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"example":{"success":true,"lists":[{"id":"901201010","name":"Bar renewals"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/clickup/orgSettings":{"get":{"operationId":"integrationsClickupOrgSettings","summary":"Get ClickUp sync settings","description":"Requires: bearer token; any role.\nReturns `{connected: false}` when ClickUp was never connected.","tags":["Integrations"],"responses":{"200":{"description":"Settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClickupOrgSettings"},"example":{"connected":true,"default_team_id":"9012345","default_space_id":"90120001","default_folder_id":"90120101","default_list_id":"901201010","sync_on_create":true,"sync_on_update":false,"sync_from_clickup":true,"enabled":true}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"integrationsClickupOrgSettings_post","summary":"Get ClickUp sync settings (POST alias)","description":"Requires: bearer token; any role.\nReturns `{connected: false}` when ClickUp was never connected.","tags":["Integrations"],"responses":{"200":{"description":"Settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClickupOrgSettings"},"example":{"connected":true,"default_team_id":"9012345","default_space_id":"90120001","default_folder_id":"90120101","default_list_id":"901201010","sync_on_create":true,"sync_on_update":false,"sync_from_clickup":true,"enabled":true}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/clickup/registerWebhook":{"post":{"operationId":"integrationsClickupRegisterWebhook","summary":"Register the ClickUp real-time webhook","description":"Requires: bearer token; role editor or admin.\nReplaces any existing webhook for the organization. `list_id` optionally scopes it to one list.","tags":["Integrations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["team_id"],"properties":{"team_id":{"type":"string"},"list_id":{"type":"string"}}},"example":{"team_id":"9012345","list_id":"901201010"}}}},"responses":{"200":{"description":"Webhook registered.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"webhook_id":{"type":"string"},"target":{"type":"string","format":"uri"}}},"example":{"success":true,"webhook_id":"4b67ac88-e506-4a29-9d42-26e504e3435e","target":"https://us-central1-expiryedge.cloudfunctions.net/api/integrations/clickup/webhook?orgId=org_northwind"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/clickup/saveOrgSettings":{"post":{"operationId":"integrationsClickupSaveOrgSettings","summary":"Save ClickUp sync settings","description":"Requires: bearer token; role editor or admin.\nReplaces all settings; omitted booleans become false (except `sync_from_clickup`, default true).","tags":["Integrations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"default_team_id":{"type":["string","null"]},"default_space_id":{"type":["string","null"]},"default_folder_id":{"type":["string","null"]},"default_list_id":{"type":["string","null"]},"sync_on_create":{"type":"boolean"},"sync_on_update":{"type":"boolean"},"sync_from_clickup":{"type":"boolean","default":true}}},"example":{"default_team_id":"9012345","default_list_id":"901201010","sync_on_create":true,"sync_on_update":false,"sync_from_clickup":true}}}},"responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/clickup/spaces":{"get":{"operationId":"integrationsClickupSpaces","summary":"List ClickUp spaces","description":"Requires: bearer token; any role.","tags":["Integrations"],"parameters":[{"name":"team_id","in":"query","required":true,"description":"ClickUp team id.","schema":{"type":"string"},"example":"9012345"}],"responses":{"200":{"description":"Non-archived spaces.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"spaces":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"example":{"success":true,"spaces":[{"id":"90120001","name":"Compliance"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"integrationsClickupSpaces_post","summary":"List ClickUp spaces (POST alias)","description":"Requires: bearer token; any role.","tags":["Integrations"],"parameters":[{"name":"team_id","in":"query","required":true,"description":"ClickUp team id.","schema":{"type":"string"},"example":"9012345"}],"responses":{"200":{"description":"Non-archived spaces.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"spaces":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"example":{"success":true,"spaces":[{"id":"90120001","name":"Compliance"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/clickup/syncExpiry":{"post":{"operationId":"integrationsClickupSyncExpiry","summary":"Push an expiry to ClickUp as a task","description":"Requires: bearer token; role editor or admin.\nCreates the task, or updates the linked task (recreating it if it was deleted). 404 if the expiry is not in your organization.","tags":["Integrations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expiryId","listId"],"properties":{"expiryId":{"type":"string"},"listId":{"type":"string"},"assigneeId":{"type":["string","integer"],"description":"ClickUp user id."}}},"example":{"expiryId":"exp_4Tq9sLm2","listId":"901201010"}}}},"responses":{"200":{"description":"Task created or updated and linked to the expiry.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"task":{"$ref":"#/components/schemas/IntegrationTaskLink"}}},"example":{"success":true,"task":{"id":"86abc123","name":"Bar membership","url":"https://app.clickup.com/t/86abc123"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/clickup/syncFromClickup":{"post":{"operationId":"integrationsClickupSyncFromClickup","summary":"Pull tasks from ClickUp now","description":"Requires: bearer token; role editor or admin.","tags":["Integrations"],"responses":{"200":{"description":"Sync finished. Only tasks with a due date are imported; safe to repeat.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntegrationSyncRunResult"},"example":{"success":true,"created":3,"updated":5,"skipped":1,"errors":0}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/clickup/tasks":{"get":{"operationId":"integrationsClickupTasks","summary":"List ClickUp tasks in a list","description":"Requires: bearer token; any role.\nIncludes closed tasks.","tags":["Integrations"],"parameters":[{"name":"list_id","in":"query","required":true,"description":"ClickUp list id.","schema":{"type":"string"},"example":"901201010"}],"responses":{"200":{"description":"Tasks as returned by ClickUp.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"tasks":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"example":{"success":true,"tasks":[{"id":"86abc123","name":"Renew bar membership","due_date":"1791936000000","url":"https://app.clickup.com/t/86abc123"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"integrationsClickupTasks_post","summary":"List ClickUp tasks in a list (POST alias)","description":"Requires: bearer token; any role.\nIncludes closed tasks.","tags":["Integrations"],"parameters":[{"name":"list_id","in":"query","required":true,"description":"ClickUp list id.","schema":{"type":"string"},"example":"901201010"}],"responses":{"200":{"description":"Tasks as returned by ClickUp.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"tasks":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"example":{"success":true,"tasks":[{"id":"86abc123","name":"Renew bar membership","due_date":"1791936000000","url":"https://app.clickup.com/t/86abc123"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/clickup/teams":{"get":{"operationId":"integrationsClickupTeams","summary":"List ClickUp workspaces (teams)","description":"Requires: bearer token; any role.","tags":["Integrations"],"responses":{"200":{"description":"ClickUp teams as returned by ClickUp.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"teams":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"example":{"success":true,"teams":[{"id":"9012345","name":"Contoso Legal"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"integrationsClickupTeams_post","summary":"List ClickUp workspaces (teams) (POST alias)","description":"Requires: bearer token; any role.","tags":["Integrations"],"responses":{"200":{"description":"ClickUp teams as returned by ClickUp.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"teams":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"example":{"success":true,"teams":[{"id":"9012345","name":"Contoso Legal"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/clickup/toggleIntegration":{"put":{"operationId":"integrationsClickupToggleIntegration","summary":"Enable or disable the ClickUp integration","description":"Requires: bearer token; role editor or admin.","tags":["Integrations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean"}}},"example":{"enabled":true}}}},"responses":{"200":{"description":"New state.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"enabled":{"type":"boolean"}}},"example":{"success":true,"enabled":true}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"post":{"operationId":"integrationsClickupToggleIntegration_post","summary":"Enable or disable the ClickUp integration (POST alias)","description":"Requires: bearer token; role editor or admin.","tags":["Integrations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean"}}},"example":{"enabled":true}}}},"responses":{"200":{"description":"New state.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"enabled":{"type":"boolean"}}},"example":{"success":true,"enabled":true}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/integrations/clickup/unsyncExpiry":{"post":{"operationId":"integrationsClickupUnsyncExpiry_post","summary":"Unlink an expiry from its ClickUp task (POST alias)","description":"Requires: bearer token; role editor or admin.\nRemoves the link only; the ClickUp task is not touched.","tags":["Integrations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expiryId"],"properties":{"expiryId":{"type":"string"}}},"example":{"expiryId":"exp_4Tq9sLm2"}}}},"responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true,"message":"ClickUp sync removed"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true},"delete":{"operationId":"integrationsClickupUnsyncExpiry","summary":"Unlink an expiry from its ClickUp task","description":"Requires: bearer token; role editor or admin.\nRemoves the link only; the ClickUp task is not touched.","tags":["Integrations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expiryId"],"properties":{"expiryId":{"type":"string"}}},"example":{"expiryId":"exp_4Tq9sLm2"}}}},"responses":{"200":{"description":"Done.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"},"example":{"success":true,"message":"ClickUp sync removed"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"description":"The user has no organization, the provider is not connected, validation failed, or the provider API call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"workspace_gid is required"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"},"x-accepts-any-method":true}},"/rotateWebhookSecret":{"post":{"operationId":"rotateWebhookSecret","summary":"Replace a webhook's signing secret","description":"Requires: bearer token; role admin.\nThe old secret stops working immediately, including for queued retries. The new secret is only shown in this response.\n","tags":["Integrations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionIdRequest"},"example":{"id":"wh_3kQ9xLm2"}}}},"responses":{"200":{"description":"The new secret.","content":{"application/json":{"schema":{"type":"object","required":["id","secret"],"properties":{"id":{"type":"string"},"secret":{"type":"string"}}},"example":{"id":"wh_3kQ9xLm2","secret":"whsec_9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f7a31"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/sendTestWebhook":{"post":{"operationId":"sendTestWebhook","summary":"Send one test event to a webhook now","description":"Requires: bearer token; role admin.\nSends a `webhook.test` event once (no retries, does not affect the failure count). Works on switched-off subscriptions too.\nAn unreachable or refused endpoint is reported with `delivered: false`, not an error status.\n","tags":["Integrations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionIdRequest"},"example":{"id":"wh_3kQ9xLm2"}}}},"responses":{"200":{"description":"The result of the test send.","content":{"application/json":{"schema":{"type":"object","required":["delivered"],"properties":{"delivered":{"type":"boolean"},"status_code":{"type":["integer","null"]},"duration_ms":{"type":"integer"},"error":{"type":["string","null"],"description":"Why it failed, e.g. `HTTP 500`, `Timed out after 10s`, `Redirects are not followed`, `Refused: Host resolves to a private or reserved address`."},"delivery_id":{"type":"string"},"event_id":{"type":"string"}}},"example":{"delivered":true,"status_code":200,"duration_ms":184,"error":null,"delivery_id":"dlv_5c2e9a0b1d4f6a8c3e7b9d1f","event_id":"evt_8c1f0a2b3c4d5e6f7a8b9c0d"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/updateWebhookSubscription":{"post":{"operationId":"updateWebhookSubscription","summary":"Change a webhook's URL, events, description or on/off state","description":"Requires: bearer token; role admin.\nSend `id` plus at least one field. `active: true` re-enables a switched-off subscription and resets its failure count.\nAlso accepts PATCH.\n","tags":["Integrations"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"string"},"url":{"type":"string","format":"uri"},"events":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/WebhookEventType"}},"description":{"type":"string","maxLength":200},"active":{"type":"boolean"}}},"example":{"id":"wh_3kQ9xLm2","events":["expiry.created","expiry.completed"],"active":true}}}},"responses":{"200":{"description":"The updated subscription (no secret).","content":{"application/json":{"schema":{"type":"object","required":["subscription"],"properties":{"subscription":{"$ref":"#/components/schemas/WebhookSubscription"}}},"example":{"subscription":{"id":"wh_3kQ9xLm2","url":"https://hooks.northwind-dental.com/expiryedge","events":["expiry.created","expiry.completed"],"description":"CRM sync","active":true,"disabled_reason":null,"disabled_at":null,"consecutive_failures":0,"last_delivery_at":null,"last_delivery_status":null,"secret_hint":"whsec_...9f2c","created_by":"u_71bXq","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-28T09:00:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/completeExpiryAttachmentUpload":{"post":{"operationId":"completeExpiryAttachmentUpload","summary":"Finish an upload and attach the file to the expiry","description":"Requires: bearer token; role editor or admin; must be the user who started the upload.\nStep 3 of 3. Checks the uploaded file matches what you declared, adds it to the end of the expiry's\nfile list (it appears in the app straight away) and counts it towards your storage. Single use.\n","tags":["Files & Documents"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["upload_id"],"properties":{"upload_id":{"type":"string","description":"upload_id from getExpiryAttachmentUploadUrl."}}},"example":{"upload_id":"pUp7x1"}}}},"responses":{"201":{"description":"File attached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpiryAttachmentUploadResult"},"example":{"attachment":{"index":1,"name":"Gas Safety Certificate 2026.pdf","url":"https://firebasestorage.googleapis.com/v0/b/stylingsphere.appspot.com/o/user_files%2Forg_northwind%2Fu_71bXq%2Fexpiry_exp_4Tq9sLm2%2F1790517900000_Gas_Safety_Certificate_2026.pdf?alt=media&token=9c2e4d1a-7b3f-4e6a-8d5c-1f0a2b3c4d5e"},"attachments":[{"index":0,"name":"Gas Safety Certificate 2025.pdf","url":"https://firebasestorage.googleapis.com/v0/b/stylingsphere.appspot.com/o/user_files%2Forg_northwind%2Fu_71bXq%2Fexpiry_exp_4Tq9sLm2%2FGas_Safety_Certificate_2025.pdf?alt=media&token=5b1f0c7e-2a41-4d8e-9f3a-0c6d2e7b9a11"},{"index":1,"name":"Gas Safety Certificate 2026.pdf","url":"https://firebasestorage.googleapis.com/v0/b/stylingsphere.appspot.com/o/user_files%2Forg_northwind%2Fu_71bXq%2Fexpiry_exp_4Tq9sLm2%2F1790517900000_Gas_Safety_Certificate_2026.pdf?alt=media&token=9c2e4d1a-7b3f-4e6a-8d5c-1f0a2b3c4d5e"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Upload not found (wrong id, other user or organization), or the expiry was deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Upload not found","code":"NOT_FOUND"}}}},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"409":{"description":"Already completed (`CONFLICT`), or the expiry reached 50 attachments meanwhile (`TOO_MANY_ATTACHMENTS`).; or a request with the same Idempotency-Key is still being processed (code IDEMPOTENCY_IN_PROGRESS).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"The upload link expired (15 minutes). Start a new upload.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"This upload link has expired - start a new upload","code":"UPLOAD_EXPIRED"}}}},"422":{"description":"No file was uploaded (`UPLOAD_MISSING`), or it does not match the declared size or type (`UPLOAD_MISMATCH`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No file was uploaded - PUT the file to upload_url first","code":"UPLOAD_MISSING"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/completeFolderFileUpload":{"post":{"operationId":"completeFolderFileUpload","summary":"Finish a file upload and add it to the folder (step 3 of 3)","description":"Requires: bearer token; role editor or admin, and the same user who started the upload.\nChecks the uploaded file matches what was declared, counts it toward storage, and creates the file\nrecord exactly as the web app does. Each `upload_id` can be completed once.\n","tags":["Files & Documents"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["upload_id"],"properties":{"upload_id":{"type":"string","description":"The `upload_id` from getFolderFileUploadUrl."},"uploaded_by_name":{"type":"string","maxLength":200,"description":"Name shown as \"Owner\" in the app. Defaults to your profile name."}}},"example":{"upload_id":"pUp7x1"}}}},"responses":{"201":{"description":"File added to the folder.","content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"$ref":"#/components/schemas/FolderFile"}}},"example":{"file":{"id":"ff_7Qm2xKp","name":"Liability Insurance Certificate 2026.pdf","size":184022,"type":"application/pdf","url":"https://firebasestorage.googleapis.com/v0/b/expiryedge.appspot.com/o/organizations%2Forg_northwind%2Ffolder_files%2Ffld_Ins2026%2F1790518300000_Liability_Insurance_Certificate_2026.pdf?alt=media&token=3f0c8a52-6d1e-4b8e-9a51-0c2f7d9e1b44","storage_path":"organizations/org_northwind/folder_files/fld_Ins2026/1790518300000_Liability_Insurance_Certificate_2026.pdf","folder_id":"fld_Ins2026","uploaded_at":"2026-09-27T14:05:00.000Z","uploaded_by_name":"Dana Whitfield"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Upload not found (wrong id, another user or organization) or the target folder was deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Upload not found","code":"NOT_FOUND"}}}},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"409":{"description":"This upload was already completed.; or a request with the same Idempotency-Key is still being processed (code IDEMPOTENCY_IN_PROGRESS).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"`UPLOAD_EXPIRED` - the 15-minute upload link expired; start a new upload.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"This upload link has expired - start a new upload","code":"UPLOAD_EXPIRED"}}}},"422":{"description":"`UPLOAD_MISSING` (nothing was PUT yet) or `UPLOAD_MISMATCH` (size or type differs from what was declared; the file is discarded).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No file was uploaded - PUT the file to upload_url first","code":"UPLOAD_MISSING"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/completeKnowledgeDocAttachmentUpload":{"post":{"operationId":"completeKnowledgeDocAttachmentUpload","summary":"Finish attaching a file to a Docs page","description":"Requires: bearer token; role editor or admin (the same user who started the upload).\nStep 3 of 3. Checks the uploaded file and saves it as an attachment of the page. If the page was deleted\nin the meantime the file is removed and 404 is returned.\n","tags":["Files & Documents"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["upload_id"],"properties":{"upload_id":{"type":"string"}}},"example":{"upload_id":"pUp7x1"}}}},"responses":{"201":{"description":"Attachment saved.","content":{"application/json":{"schema":{"type":"object","required":["attachment"],"properties":{"attachment":{"$ref":"#/components/schemas/KnowledgeDocAttachment"}}},"example":{"attachment":{"id":"att_3Fq8","doc_id":"kd_7Hn2qLx","name":"food-hygiene-renewal-checklist.pdf","size":184022,"type":"application/pdf","url":"https://firebasestorage.googleapis.com/v0/b/expiryedge.appspot.com/o/organizations%2Forg_northwind%2Fdocs%2Fkd_7Hn2qLx%2Fattachments%2F1790000000000_food-hygiene-renewal-checklist.pdf?alt=media&token=2b9c6f0e-7a1d-4c4e-9d3b-5f1a8e2c7b10","storage_path":"organizations/org_northwind/docs/kd_7Hn2qLx/attachments/1790000000000_food-hygiene-renewal-checklist.pdf","uploaded_at":"2026-09-27T14:20:00.000Z","uploaded_by":"u_71bXq","uploaded_by_name":"Priya Shah"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"409":{"description":"This upload was already completed.; or a request with the same Idempotency-Key is still being processed (code IDEMPOTENCY_IN_PROGRESS).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"The upload link expired (15 minutes). Start a new upload.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"This upload link has expired - start a new upload","code":"UPLOAD_EXPIRED"}}}},"422":{"description":"No file was uploaded (`UPLOAD_MISSING`), or it does not match the declared size or type (`UPLOAD_MISMATCH`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No file was uploaded - PUT the file to upload_url first","code":"UPLOAD_MISSING"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/createKnowledgeDoc":{"post":{"operationId":"createKnowledgeDoc","summary":"Create a Docs page (as a draft)","description":"Requires: bearer token; role editor or admin.\nNew pages always start as `draft`; publish with updateKnowledgeDoc `status: published`.\n`content` is HTML like the web editor produces; unsafe markup (scripts, event handlers, iframes, non-https images) is removed.\n","tags":["Files & Documents"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeDocInput"},"example":{"title":"How we renew our food hygiene certificate","content":"<h2>Steps</h2><ol><li><p>Book the inspection 8 weeks before expiry</p></li><li><p>Upload the new certificate</p></li></ol>","emoji":"🧾","tags":["food-safety","procedures"],"parent_id":"kd_1Proc"}}}},"responses":{"201":{"description":"Page created.","content":{"application/json":{"schema":{"type":"object","required":["doc"],"properties":{"doc":{"$ref":"#/components/schemas/KnowledgeDoc"}}},"example":{"doc":{"id":"kd_7Hn2qLx","title":"How we renew our food hygiene certificate","content":"<h2>Steps</h2><ol><li><p>Book the inspection 8 weeks before expiry</p></li><li><p>Upload the new certificate</p></li></ol>","emoji":"🧾","tags":["food-safety","procedures"],"parent_id":"kd_1Proc","status":"draft","organization_id":"org_northwind","created_by":"u_71bXq","created_by_name":"Priya Shah","created_by_email":"priya@northwinddental.com","created_at":"2026-09-27T14:05:00.000Z","updated_by":"u_71bXq","updated_by_name":"Priya Shah","updated_at":"2026-09-27T14:05:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"409":{"$ref":"#/components/responses/IdempotencyInProgress"},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/deleteExpiryAttachment":{"post":{"operationId":"deleteExpiryAttachment","summary":"Remove a file from an expiry","description":"Requires: bearer token; role editor or admin.\nIdentify the file by `url` (recommended - safe even if the list changed since you read it) or by\n`index`. Removes it from the expiry, deletes the stored file (only if it is in your organization's\nstorage) and frees its space in your storage quota.\n","tags":["Files & Documents"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpiryAttachmentDeleteRequest"},"example":{"expiryId":"exp_4Tq9sLm2","url":"https://firebasestorage.googleapis.com/v0/b/stylingsphere.appspot.com/o/user_files%2Forg_northwind%2Fu_71bXq%2Fexpiry_exp_4Tq9sLm2%2FGas_Safety_Certificate_2025.pdf?alt=media&token=5b1f0c7e-2a41-4d8e-9f3a-0c6d2e7b9a11"}}}},"responses":{"200":{"$ref":"#/components/responses/ExpiryAttachmentDeleted"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Expiry not in your organization, or no attachment with that url / index.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Attachment not found","code":"NOT_FOUND"}}}},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"delete":{"operationId":"deleteExpiryAttachment_delete","summary":"Remove a file from an expiry (DELETE form)","description":"Requires: bearer token; role editor or admin.\nSame as the POST form. Send the fields as a JSON body or as query parameters.\n","tags":["Files & Documents"],"parameters":[{"name":"expiryId","in":"query","required":false,"schema":{"type":"string"},"example":"exp_4Tq9sLm2"},{"name":"url","in":"query","required":false,"schema":{"type":"string"}},{"name":"index","in":"query","required":false,"schema":{"type":"integer","minimum":0}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpiryAttachmentDeleteRequest"},"example":{"expiryId":"exp_4Tq9sLm2","index":0}}}},"responses":{"200":{"$ref":"#/components/responses/ExpiryAttachmentDeleted"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/deleteFolderFile":{"post":{"operationId":"deleteFolderFile","summary":"Delete a file from Documents","description":"Requires: bearer token; role editor or admin (the web app lets any member delete; the API is stricter).\nDeletes the file record and the stored file, and frees the space in your storage quota. Cannot be undone.\n","tags":["Files & Documents"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["fileId"],"properties":{"fileId":{"type":"string"}}},"example":{"fileId":"ff_7Qm2xKp"}}}},"responses":{"200":{"description":"File deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FolderFileDeleted"},"example":{"deleted":true,"id":"ff_7Qm2xKp"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"delete":{"operationId":"deleteFolderFile_delete","summary":"Delete a file from Documents (DELETE form)","description":"Requires: bearer token; role editor or admin.\nSame as POST /deleteFolderFile; pass `fileId` in the JSON body or as a query parameter.\n","tags":["Files & Documents"],"parameters":[{"name":"fileId","in":"query","required":false,"schema":{"type":"string"},"example":"ff_7Qm2xKp"}],"responses":{"200":{"description":"File deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FolderFileDeleted"},"example":{"deleted":true,"id":"ff_7Qm2xKp"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/deleteKnowledgeDoc":{"post":{"operationId":"deleteKnowledgeDoc","summary":"Delete a Docs page and its attachments","description":"Requires: bearer token; role admin, or an editor who created the page (with `cascade`, every sub-page too).\nDeletes the page's attachments (records and stored files) and then the page. A page with sub-pages returns 409\n`HAS_CHILDREN` unless `cascade: true`, which deletes the sub-pages too (deepest first).\n","tags":["Files & Documents"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeDocDeleteRequest"},"example":{"id":"kd_7Hn2qLx"}}}},"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeDocDeleteResult"},"example":{"deleted":true,"id":"kd_7Hn2qLx","deleted_ids":["kd_7Hn2qLx"]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"409":{"description":"The page has sub-pages. Delete or move them first, or pass `cascade` true.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"This document has sub-pages. Delete or move them first, or pass cascade: true to delete them too.","code":"HAS_CHILDREN","details":{"child_count":2}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"delete":{"operationId":"deleteKnowledgeDoc_delete","summary":"Delete a Docs page and its attachments (DELETE form)","description":"Requires: bearer token; role admin, or an editor who created the page (with `cascade`, every sub-page too).\nSame as the POST form; `id` and `cascade` may be sent in a JSON body or as query parameters.\n","tags":["Files & Documents"],"parameters":[{"name":"id","in":"query","required":false,"schema":{"type":"string"},"example":"kd_7Hn2qLx"},{"name":"cascade","in":"query","required":false,"schema":{"type":"boolean"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeDocDeleteRequest"},"example":{"id":"kd_1Proc","cascade":true}}}},"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeDocDeleteResult"},"example":{"deleted":true,"id":"kd_1Proc","deleted_ids":["kd_7Hn2qLx","kd_1Proc"]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"409":{"description":"The page has sub-pages. Delete or move them first, or pass `cascade` true.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"This document has sub-pages. Delete or move them first, or pass cascade: true to delete them too.","code":"HAS_CHILDREN","details":{"child_count":2}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/deleteKnowledgeDocAttachment":{"post":{"operationId":"deleteKnowledgeDocAttachment","summary":"Remove a file attachment from a Docs page","description":"Requires: bearer token; role editor or admin.\nDeletes the attachment record and the stored file, and frees the storage it used.\n","tags":["Files & Documents"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeDocAttachmentDeleteRequest"},"example":{"id":"kd_7Hn2qLx","attachmentId":"att_3Fq8"}}}},"responses":{"200":{"description":"Deleted. `id` is the attachment id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeDocAttachmentDeleteResult"},"example":{"deleted":true,"id":"att_3Fq8"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"delete":{"operationId":"deleteKnowledgeDocAttachment_delete","summary":"Remove a file attachment from a Docs page (DELETE form)","description":"Requires: bearer token; role editor or admin.\nSame as the POST form; `id` and `attachmentId` may be sent in a JSON body or as query parameters.\n","tags":["Files & Documents"],"parameters":[{"name":"id","in":"query","required":false,"schema":{"type":"string"},"example":"kd_7Hn2qLx"},{"name":"attachmentId","in":"query","required":false,"schema":{"type":"string"},"example":"att_3Fq8"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeDocAttachmentDeleteRequest"},"example":{"id":"kd_7Hn2qLx","attachmentId":"att_3Fq8"}}}},"responses":{"200":{"description":"Deleted. `id` is the attachment id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeDocAttachmentDeleteResult"},"example":{"deleted":true,"id":"att_3Fq8"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/getExpiryAttachments":{"get":{"operationId":"getExpiryAttachments","summary":"List the files attached to an expiry","description":"Requires: bearer token; any member of an organization.\nReturns the expiry's files in the order the app shows them. `index` is the file's position in the\nlist; `url` is the download URL. Old records without stored names get a name derived from the URL.\n","tags":["Files & Documents"],"parameters":[{"name":"expiryId","in":"query","required":true,"schema":{"type":"string"},"example":"exp_4Tq9sLm2"},{"name":"limit","in":"query","required":false,"description":"Max entries returned, 1-500 (default 100). An expiry holds at most 50 files added through the API.","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}}],"responses":{"200":{"description":"The expiry's attachments.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ExpiryAttachment"}}}},"example":{"data":[{"index":0,"name":"Gas Safety Certificate 2025.pdf","url":"https://firebasestorage.googleapis.com/v0/b/stylingsphere.appspot.com/o/user_files%2Forg_northwind%2Fu_71bXq%2Fexpiry_exp_4Tq9sLm2%2FGas_Safety_Certificate_2025.pdf?alt=media&token=5b1f0c7e-2a41-4d8e-9f3a-0c6d2e7b9a11"},{"index":1,"name":"Gas Safety Certificate 2026.pdf","url":"https://firebasestorage.googleapis.com/v0/b/stylingsphere.appspot.com/o/user_files%2Forg_northwind%2Fu_71bXq%2Fexpiry_exp_4Tq9sLm2%2F1790517900000_Gas_Safety_Certificate_2026.pdf?alt=media&token=9c2e4d1a-7b3f-4e6a-8d5c-1f0a2b3c4d5e"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/getExpiryAttachmentUploadUrl":{"post":{"operationId":"getExpiryAttachmentUploadUrl","summary":"Start uploading a file to an expiry","description":"Requires: bearer token; role editor or admin.\nStep 1 of 3. Returns a one-time upload link valid for 15 minutes. PUT the file bytes to `upload_url`\nwith exactly the returned `headers`, then call completeExpiryAttachmentUpload. Max 50 MB per file,\nmax 50 files per expiry, and the file must fit in your plan's storage quota.\n","tags":["Files & Documents"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/UploadTicketRequest"},{"type":"object","required":["expiryId"],"properties":{"expiryId":{"type":"string","description":"The expiry to attach the file to."}}}]},"example":{"expiryId":"exp_4Tq9sLm2","file_name":"Gas Safety Certificate 2026.pdf","content_type":"application/pdf","size":184022}}}},"responses":{"201":{"description":"Upload link created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadTicket"},"example":{"upload_id":"pUp7x1","upload_url":"https://storage.googleapis.com/stylingsphere.appspot.com/user_files/org_northwind/u_71bXq/expiry_exp_4Tq9sLm2/1790517900000_Gas_Safety_Certificate_2026.pdf?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Signature=abc123","method":"PUT","headers":{"Content-Type":"application/pdf","x-goog-content-length-range":"0,184022"},"expires_at":"2026-09-27T14:20:00.000Z","max_size":184022}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"400":{"description":"Validation failed, or the file type is not allowed (`UNSUPPORTED_FILE_TYPE`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"content_type \"text/html\" is not allowed","code":"UNSUPPORTED_FILE_TYPE"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Viewer role, no organization, or the plan's storage limit would be exceeded (`STORAGE_LIMIT_REACHED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Your plan's storage limit would be exceeded","code":"STORAGE_LIMIT_REACHED","details":{"current_usage":1073000000,"limit":1073741824,"file_size":184022}}}}},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"409":{"description":"The expiry already has 50 attachments.; or a request with the same Idempotency-Key is still being processed (code IDEMPOTENCY_IN_PROGRESS).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"The file is larger than 50 MB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Files can be at most 52428800 bytes (50 MB)","code":"FILE_TOO_LARGE"}}}},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/getFolderFiles":{"get":{"operationId":"getFolderFiles","summary":"List the files in a Documents folder","description":"Requires: bearer token; any member of an organization.\nReturns the files directly inside one folder (not subfolders), newest first. Omit `folderId`\n(or pass `root`) for files at the top level. Up to `limit` files (default 100, max 500).\n","tags":["Files & Documents"],"parameters":[{"name":"folderId","in":"query","required":false,"description":"Folder id from the Folders API; omit, empty or `root` for the top level.","schema":{"type":"string"},"example":"fld_Ins2026"},{"name":"limit","in":"query","required":false,"description":"Maximum files to return, 1-500 (values above 500 are clamped; default 100).","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}}],"responses":{"200":{"description":"Files in the folder, newest first.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/FolderFile"}}}},"example":{"data":[{"id":"ff_7Qm2xKp","name":"Liability Insurance Certificate 2026.pdf","size":184022,"type":"application/pdf","url":"https://firebasestorage.googleapis.com/v0/b/expiryedge.appspot.com/o/organizations%2Forg_northwind%2Ffolder_files%2Ffld_Ins2026%2F1790518300000_Liability_Insurance_Certificate_2026.pdf?alt=media&token=3f0c8a52-6d1e-4b8e-9a51-0c2f7d9e1b44","storage_path":"organizations/org_northwind/folder_files/fld_Ins2026/1790518300000_Liability_Insurance_Certificate_2026.pdf","folder_id":"fld_Ins2026","uploaded_at":"2026-09-27T14:05:00.000Z","uploaded_by_name":"Dana Whitfield"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/getFolderFileUploadUrl":{"post":{"operationId":"getFolderFileUploadUrl","summary":"Start a file upload into a Documents folder (step 1 of 3)","description":"Requires: bearer token; role editor or admin.\nValidates the file and returns a one-time signed URL. PUT the bytes to `upload_url` with the returned\nheaders within 15 minutes, then call completeFolderFileUpload. Max 50 MB; must fit in your storage quota.\n","tags":["Files & Documents"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/UploadTicketRequest"},{"type":"object","properties":{"folderId":{"type":["string","null"],"description":"Target folder id; omit, null or `root` for the top level."}}}]},"example":{"folderId":"fld_Ins2026","file_name":"Liability Insurance Certificate 2026.pdf","content_type":"application/pdf","size":184022}}}},"responses":{"201":{"description":"Upload link created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadTicket"},"example":{"upload_id":"pUp7x1","upload_url":"https://storage.googleapis.com/expiryedge.appspot.com/organizations/org_northwind/folder_files/fld_Ins2026/1790518300000_Liability_Insurance_Certificate_2026.pdf?X-Goog-Signature=abc123","method":"PUT","headers":{"Content-Type":"application/pdf","x-goog-content-length-range":"0,184022"},"expires_at":"2026-09-27T14:20:00.000Z","max_size":184022}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"400":{"description":"Invalid request, or `UNSUPPORTED_FILE_TYPE` for a content type that is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"content_type \"text/html\" is not allowed","code":"UNSUPPORTED_FILE_TYPE"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Viewer role, no organization, or `STORAGE_LIMIT_REACHED` (the file would exceed your plan storage).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Your plan's storage limit would be exceeded","code":"STORAGE_LIMIT_REACHED","details":{"current_usage":1073000000,"limit":1073741824,"file_size":184022}}}}},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"409":{"$ref":"#/components/responses/IdempotencyInProgress"},"413":{"description":"`FILE_TOO_LARGE` - the file is over 50 MB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Files can be at most 52428800 bytes (50 MB)","code":"FILE_TOO_LARGE"}}}},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/getKnowledgeDoc":{"get":{"operationId":"getKnowledgeDoc","summary":"Get one Docs page with its breadcrumb trail","description":"Requires: bearer token; any member of an organization.\n`breadcrumbs` lists the page's ancestors from the top level down (the page itself is not included),\nat most 10 levels.\n","tags":["Files & Documents"],"parameters":[{"name":"id","in":"query","required":true,"schema":{"type":"string"},"example":"kd_7Hn2qLx"}],"responses":{"200":{"description":"The page and its ancestors.","content":{"application/json":{"schema":{"type":"object","required":["doc","breadcrumbs"],"properties":{"doc":{"$ref":"#/components/schemas/KnowledgeDoc"},"breadcrumbs":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeDocBreadcrumb"}}}},"example":{"doc":{"id":"kd_7Hn2qLx","title":"How we renew our food hygiene certificate","content":"<h2>Steps</h2><ol><li><p>Book the inspection 8 weeks before expiry</p></li></ol>","emoji":"🧾","tags":["food-safety"],"parent_id":"kd_1Proc","status":"published","organization_id":"org_northwind","created_by":"u_71bXq","created_by_name":"Priya Shah","created_by_email":"priya@northwinddental.com","created_at":"2026-09-27T14:05:00.000Z","updated_by":"u_71bXq","updated_by_name":"Priya Shah","updated_at":"2026-09-27T14:12:00.000Z"},"breadcrumbs":[{"id":"kd_1Proc","title":"Procedures","emoji":"📚"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/getKnowledgeDocAttachments":{"get":{"operationId":"getKnowledgeDocAttachments","summary":"List a Docs page's file attachments","description":"Requires: bearer token; any member of an organization.\nReturns all attachments of the page, newest first (unbounded; pages rarely have more than a few dozen).\n","tags":["Files & Documents"],"parameters":[{"name":"id","in":"query","required":true,"description":"Page id.","schema":{"type":"string"},"example":"kd_7Hn2qLx"}],"responses":{"200":{"description":"Attachments, newest first.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeDocAttachment"}}}},"example":{"data":[{"id":"att_3Fq8","doc_id":"kd_7Hn2qLx","name":"food-hygiene-renewal-checklist.pdf","size":184022,"type":"application/pdf","url":"https://firebasestorage.googleapis.com/v0/b/expiryedge.appspot.com/o/organizations%2Forg_northwind%2Fdocs%2Fkd_7Hn2qLx%2Fattachments%2F1790000000000_food-hygiene-renewal-checklist.pdf?alt=media&token=2b9c6f0e-7a1d-4c4e-9d3b-5f1a8e2c7b10","storage_path":"organizations/org_northwind/docs/kd_7Hn2qLx/attachments/1790000000000_food-hygiene-renewal-checklist.pdf","uploaded_at":"2026-09-27T14:20:00.000Z","uploaded_by":"u_71bXq","uploaded_by_name":"Priya Shah"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/getKnowledgeDocAttachmentUploadUrl":{"post":{"operationId":"getKnowledgeDocAttachmentUploadUrl","summary":"Start attaching a file to a Docs page (get an upload link)","description":"Requires: bearer token; role editor or admin.\nStep 1 of 3. PUT the file bytes to `upload_url` with the returned `headers` within 15 minutes,\nthen call completeKnowledgeDocAttachmentUpload with `upload_id`.\n","tags":["Files & Documents"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/UploadTicketRequest"},{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"Page id."}}}]},"example":{"id":"kd_7Hn2qLx","file_name":"food-hygiene-renewal-checklist.pdf","content_type":"application/pdf","size":184022}}}},"responses":{"201":{"description":"Upload link issued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadTicket"},"example":{"upload_id":"pUp7x1","upload_url":"https://storage.googleapis.com/expiryedge.appspot.com/organizations/org_northwind/docs/kd_7Hn2qLx/attachments/1790000000000_food-hygiene-renewal-checklist.pdf?X-Goog-Signature=...","method":"PUT","headers":{"Content-Type":"application/pdf","x-goog-content-length-range":"0,184022"},"expires_at":"2026-09-27T14:35:00.000Z","max_size":184022}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}}},"400":{"description":"Invalid request (`VALIDATION_FAILED`) or a file type that is not allowed (`UNSUPPORTED_FILE_TYPE`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"content_type \"text/html\" is not allowed","code":"UNSUPPORTED_FILE_TYPE"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Viewer role, no organization, or the plan storage limit would be exceeded (`STORAGE_LIMIT_REACHED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Your plan's storage limit would be exceeded","code":"STORAGE_LIMIT_REACHED"}}}},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"409":{"$ref":"#/components/responses/IdempotencyInProgress"},"413":{"description":"`FILE_TOO_LARGE` - the file is over 50 MB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Files can be at most 52428800 bytes (50 MB)","code":"FILE_TOO_LARGE"}}}},"422":{"$ref":"#/components/responses/IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/getKnowledgeDocs":{"get":{"operationId":"getKnowledgeDocs","summary":"List Docs pages","description":"Requires: bearer token; any member of an organization.\nLists pages in the Docs (knowledge base) section, most recently updated first.\nReturns up to `limit` pages (default 100, max 500; larger values are clamped). No cursor.\n","tags":["Files & Documents"],"parameters":[{"name":"status","in":"query","required":false,"description":"Only drafts or only published pages.","schema":{"type":"string","enum":["draft","published"]}},{"name":"parentId","in":"query","required":false,"description":"Only direct sub-pages of this page. Use `root` for top-level pages only.","schema":{"type":"string"},"example":"root"},{"name":"limit","in":"query","required":false,"description":"Max pages to return, 1-500 (default 100).","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}}],"responses":{"200":{"description":"Pages, newest updated first.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeDoc"}}}},"example":{"data":[{"id":"kd_7Hn2qLx","title":"How we renew our food hygiene certificate","content":"<h2>Steps</h2><ol><li><p>Book the inspection 8 weeks before expiry</p></li></ol>","emoji":"🧾","tags":["food-safety","procedures"],"parent_id":null,"status":"published","organization_id":"org_northwind","created_by":"u_71bXq","created_by_name":"Priya Shah","created_by_email":"priya@northwinddental.com","created_at":"2026-09-27T14:05:00.000Z","updated_by":"u_71bXq","updated_by_name":"Priya Shah","updated_at":"2026-09-27T14:12:00.000Z"}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/moveFolderFile":{"post":{"operationId":"moveFolderFile","summary":"Move a file to another Documents folder","description":"Requires: bearer token; role editor or admin.\nOnly `folder_id` changes (same as dragging in the app); `storage_path` and `url` stay the same.\n`folderId` is required: pass null or `root` to move the file to the top level.\n","tags":["Files & Documents"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["fileId","folderId"],"properties":{"fileId":{"type":"string"},"folderId":{"type":["string","null"],"description":"Target folder id, or null / `root` for the top level."}}},"example":{"fileId":"ff_7Qm2xKp","folderId":"fld_Archive2025"}}}},"responses":{"200":{"description":"File moved.","content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"$ref":"#/components/schemas/FolderFile"}}},"example":{"file":{"id":"ff_7Qm2xKp","name":"Liability Insurance Certificate 2026.pdf","size":184022,"type":"application/pdf","url":"https://firebasestorage.googleapis.com/v0/b/expiryedge.appspot.com/o/organizations%2Forg_northwind%2Ffolder_files%2Ffld_Ins2026%2F1790518300000_Liability_Insurance_Certificate_2026.pdf?alt=media&token=3f0c8a52-6d1e-4b8e-9a51-0c2f7d9e1b44","storage_path":"organizations/org_northwind/folder_files/fld_Ins2026/1790518300000_Liability_Insurance_Certificate_2026.pdf","folder_id":"fld_Archive2025","uploaded_at":"2026-09-27T14:05:00.000Z","uploaded_by_name":"Dana Whitfield"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/renameFolderFile":{"post":{"operationId":"renameFolderFile","summary":"Rename a file in Documents","description":"Requires: bearer token; role editor or admin.\nChanges the display name only; the stored file and its download URL do not change.\n","tags":["Files & Documents"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["fileId","name"],"properties":{"fileId":{"type":"string"},"name":{"type":"string","minLength":1,"maxLength":255,"description":"New name; leading and trailing spaces are removed."}}},"example":{"fileId":"ff_7Qm2xKp","name":"Northwind Dental - Liability Certificate 2026.pdf"}}}},"responses":{"200":{"description":"File renamed.","content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"$ref":"#/components/schemas/FolderFile"}}},"example":{"file":{"id":"ff_7Qm2xKp","name":"Northwind Dental - Liability Certificate 2026.pdf","size":184022,"type":"application/pdf","url":"https://firebasestorage.googleapis.com/v0/b/expiryedge.appspot.com/o/organizations%2Forg_northwind%2Ffolder_files%2Ffld_Ins2026%2F1790518300000_Liability_Insurance_Certificate_2026.pdf?alt=media&token=3f0c8a52-6d1e-4b8e-9a51-0c2f7d9e1b44","storage_path":"organizations/org_northwind/folder_files/fld_Ins2026/1790518300000_Liability_Insurance_Certificate_2026.pdf","folder_id":"fld_Ins2026","uploaded_at":"2026-09-27T14:05:00.000Z","uploaded_by_name":"Dana Whitfield"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/updateKnowledgeDoc":{"post":{"operationId":"updateKnowledgeDoc","summary":"Edit, move, publish or unpublish a Docs page","description":"Requires: bearer token; role editor or admin.\nSend only the fields you want to change. `parent_id` must be a page in your organization and cannot be the page\nitself or one of its sub-pages; `null` moves it to the top level. Other fields in the body are ignored.\n","tags":["Files & Documents"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/KnowledgeDocInput"},{"type":"object","required":["id"],"properties":{"id":{"type":"string"},"status":{"type":"string","enum":["draft","published"]}}}]},"example":{"id":"kd_7Hn2qLx","status":"published"}}}},"responses":{"200":{"description":"The updated page.","content":{"application/json":{"schema":{"type":"object","required":["doc"],"properties":{"doc":{"$ref":"#/components/schemas/KnowledgeDoc"}}},"example":{"doc":{"id":"kd_7Hn2qLx","title":"How we renew our food hygiene certificate","content":"<h2>Steps</h2><ol><li><p>Book the inspection 8 weeks before expiry</p></li></ol>","emoji":"🧾","tags":["food-safety","procedures"],"parent_id":"kd_1Proc","status":"published","organization_id":"org_northwind","created_by":"u_71bXq","created_by_name":"Priya Shah","created_by_email":"priya@northwinddental.com","created_at":"2026-09-27T14:05:00.000Z","updated_by":"u_71bXq","updated_by_name":"Priya Shah","updated_at":"2026-09-27T14:12:00.000Z"}}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/v2/collection-requests":{"servers":[{"url":"https://api.expiryedge.com","description":"Production"}],"get":{"operationId":"v2ListCollectionRequests","summary":"List Document Collection requests","description":"Requires: bearer token; any member of an organization.\nNewest first by default, cursor-paginated. Combine two or more filters only with `sort=id`.\nThe link token hash, password hash and the recipient's unsubmitted draft are never returned.\n","tags":["v2 (preview)"],"parameters":[{"name":"status","in":"query","required":false,"description":"Stored status (`pending`, `viewed`, `completed`, `cancelled`, `expired`).","schema":{"type":"string"}},{"name":"template_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"contact_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"expiry_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","enum":["-created_at","id"],"default":"-created_at"}},{"$ref":"#/components/parameters/V2Cursor"},{"$ref":"#/components/parameters/V2Limit"}],"responses":{"200":{"description":"A page of requests.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2CollectionRequestPage"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"post":{"operationId":"v2CreateCollectionRequests","summary":"Send a Document Collection request","description":"Requires: bearer token; role editor or admin.\nCreates one request per recipient (`contact_ids` and/or every member of `group_id`; contacts without an\nemail are skipped; max 200) and emails each a fill link. Counts toward the monthly request limit\n(403 `PLAN_LIMIT_REACHED`). Returns 201 `{ data }`; `Location` is the request when one was created, else\n`/v2/collection-requests`.\n","tags":["v2 (preview)"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2CollectionRequestInput"},"example":{"template_id":"tpl_W9x2","contact_ids":["c_8Hk2pQ"],"expiry_id":"exp_4Tq9sLm2","name":"W-9 for 2026","due_date":"2026-10-31"}}}},"responses":{"201":{"description":"Sent.","headers":{"Location":{"$ref":"#/components/headers/V2Location"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/V2CollectionRequest"}}}}}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2ForbiddenOrPlanLimit"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"409":{"$ref":"#/components/responses/V2Conflict"},"422":{"$ref":"#/components/responses/V2IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/v2/collection-requests/{id}":{"servers":[{"url":"https://api.expiryedge.com","description":"Production"}],"parameters":[{"$ref":"#/components/parameters/V2PathId"}],"get":{"operationId":"v2GetCollectionRequest","summary":"Get a Document Collection request","description":"Requires: bearer token; any member of the request's organization.","tags":["v2 (preview)"],"responses":{"200":{"description":"The request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2CollectionRequest"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/v2/collection-requests/{id}/cancel":{"servers":[{"url":"https://api.expiryedge.com","description":"Production"}],"parameters":[{"$ref":"#/components/parameters/V2PathId"}],"post":{"operationId":"v2CancelCollectionRequest","summary":"Cancel a Document Collection request","description":"Requires: bearer token; role editor or admin.\nThe link stops working and pending reminders stop. A completed request is 409 `INVALID_STATE`.\n","tags":["v2 (preview)"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"The cancelled request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2CollectionRequest"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"409":{"$ref":"#/components/responses/V2Conflict"},"422":{"$ref":"#/components/responses/V2IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/v2/collection-requests/{id}/resend":{"servers":[{"url":"https://api.expiryedge.com","description":"Production"}],"parameters":[{"$ref":"#/components/parameters/V2PathId"}],"post":{"operationId":"v2ResendCollectionRequest","summary":"Resend a Document Collection request","description":"Requires: bearer token; role editor or admin.\nIssues a NEW fill link (the previous one stops working) and emails it unless `skip_email` is true.\nPaused, completed and cancelled requests are 409 `INVALID_STATE`. An Idempotency-Key replay returns\n`link: null` (links are never stored).\n","tags":["v2 (preview)"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"skip_email":{"type":"boolean","default":false}}},"example":{"skip_email":true}}}},"responses":{"200":{"description":"The request, plus the new link.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V2CollectionRequest"},{"type":"object","properties":{"link":{"type":["string","null"],"description":"The new fill link; null on an idempotent replay."},"email_sent":{"type":"boolean"}}}]}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"409":{"$ref":"#/components/responses/V2Conflict"},"422":{"$ref":"#/components/responses/V2IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/v2/collection-submissions":{"servers":[{"url":"https://api.expiryedge.com","description":"Production"}],"get":{"operationId":"v2ListCollectionSubmissions","summary":"List Document Collection submissions","description":"Requires: bearer token; any member of an organization.\nOldest first by `submitted_at`, cursor-paginated - poll with `submitted_since` and keep the cursor.\nFiles are metadata only; download them with v1 getCollectionSubmissionFile.\n","tags":["v2 (preview)"],"parameters":[{"name":"submitted_since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"example":"2026-09-01T00:00:00Z"},{"name":"review_status","in":"query","required":false,"description":"Not combinable with `request_id`.","schema":{"type":"string","enum":["pending","approved","changes_requested","rejected"]}},{"name":"request_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","enum":["submitted_at"]}},{"$ref":"#/components/parameters/V2Cursor"},{"$ref":"#/components/parameters/V2Limit"}],"responses":{"200":{"description":"A page of submissions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2SubmissionPage"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/v2/collection-templates":{"servers":[{"url":"https://api.expiryedge.com","description":"Production"}],"get":{"operationId":"v2ListCollectionTemplates","summary":"List Document Collection templates","description":"Requires: bearer token; any member of an organization. Ordered by id, cursor-paginated.","tags":["v2 (preview)"],"parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["active","archived","request_draft","request_sent","all"],"default":"active"}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","enum":["id"]}},{"$ref":"#/components/parameters/V2Cursor"},{"$ref":"#/components/parameters/V2Limit"}],"responses":{"200":{"description":"A page of templates.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2CollectionTemplatePage"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/v2/collection-templates/{id}":{"servers":[{"url":"https://api.expiryedge.com","description":"Production"}],"parameters":[{"$ref":"#/components/parameters/V2PathId"}],"get":{"operationId":"v2GetCollectionTemplate","summary":"Get a Document Collection template","description":"Requires: bearer token; any member of the template's organization. The whole `pages` tree as stored.","tags":["v2 (preview)"],"responses":{"200":{"description":"The template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2CollectionTemplate"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/v2/comments/{id}":{"servers":[{"url":"https://api.expiryedge.com","description":"Production"}],"parameters":[{"$ref":"#/components/parameters/V2PathId"}],"get":{"operationId":"v2GetComment","summary":"Get a comment","description":"Requires: bearer token; any member of the organization of the comment's expiry.","tags":["v2 (preview)"],"responses":{"200":{"description":"The comment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2Comment"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"delete":{"operationId":"v2DeleteComment","summary":"Delete a comment","description":"Requires: bearer token; the comment's author or an org admin.","tags":["v2 (preview)"],"responses":{"204":{"description":"Deleted.","headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"patch":{"operationId":"v2UpdateComment","summary":"Edit your comment","description":"Requires: bearer token; the comment's author (403 otherwise). Sets `edited: true`.","tags":["v2 (preview)"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2CommentInput"}}}},"responses":{"200":{"description":"The edited comment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2Comment"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/v2/compliance-clients":{"servers":[{"url":"https://api.expiryedge.com","description":"Production"}],"get":{"operationId":"v2ListComplianceClients","summary":"List compliance clients","description":"Requires: bearer token; any member of an organization. Ordered by id, cursor-paginated; archived and active clients unless `archived` is given.","tags":["v2 (preview)"],"parameters":[{"name":"archived","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["Active","Inactive"]}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","enum":["id"]}},{"$ref":"#/components/parameters/V2Cursor"},{"$ref":"#/components/parameters/V2Limit"}],"responses":{"200":{"description":"A page of clients.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2ComplianceClientPage"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"post":{"operationId":"v2CreateComplianceClient","summary":"Create a compliance client","description":"Requires: bearer token; role editor or admin. Field names as stored; unknown fields are rejected (400).","tags":["v2 (preview)"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V2ComplianceClientInput"},{"required":["name"]}]},"example":{"name":"Northwind Dental","primary_contact_name":"Priya Shah","email":"office@northwinddental.example","industry":"Dental","custom_fields":{"tax_id":"12-3456789"}}}}},"responses":{"201":{"description":"Created.","headers":{"Location":{"$ref":"#/components/headers/V2Location"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2ComplianceClient"}}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"409":{"$ref":"#/components/responses/V2Conflict"},"422":{"$ref":"#/components/responses/V2IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/v2/compliance-clients/{id}":{"servers":[{"url":"https://api.expiryedge.com","description":"Production"}],"parameters":[{"$ref":"#/components/parameters/V2PathId"}],"get":{"operationId":"v2GetComplianceClient","summary":"Get a compliance client","description":"Requires: bearer token; any member of the client's organization.","tags":["v2 (preview)"],"responses":{"200":{"description":"The client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2ComplianceClient"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"delete":{"operationId":"v2DeleteComplianceClient","summary":"Delete a compliance client","description":"Requires: bearer token; role editor or admin. A client with projects is 409 `CLIENT_HAS_PROJECTS`.","tags":["v2 (preview)"],"responses":{"204":{"description":"Deleted.","headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"409":{"$ref":"#/components/responses/V2Conflict"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"patch":{"operationId":"v2UpdateComplianceClient","summary":"Update a compliance client","description":"Requires: bearer token; role editor or admin. Only fields present change; `is_archived` archives or restores the client.","tags":["v2 (preview)"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V2ComplianceClientInput"},{"type":"object","properties":{"is_archived":{"type":"boolean"}}}]},"example":{"industry":"Dental","is_archived":true}}}},"responses":{"200":{"description":"The client after the change.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2ComplianceClient"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/v2/contact-groups":{"servers":[{"url":"https://api.expiryedge.com","description":"Production"}],"get":{"operationId":"v2ListContactGroups","summary":"List contact groups","description":"Requires: bearer token; any member of an organization.\nStored groups ordered by id, cursor-paginated. Members are the contacts whose `contact_group` equals the\ngroup `name` (`GET /v2/contacts` and filter).\n","tags":["v2 (preview)"],"parameters":[{"name":"sort","in":"query","required":false,"schema":{"type":"string","enum":["id"]}},{"$ref":"#/components/parameters/V2Cursor"},{"$ref":"#/components/parameters/V2Limit"}],"responses":{"200":{"description":"A page of contact groups.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2ContactGroupPage"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"post":{"operationId":"v2CreateContactGroup","summary":"Create a contact group","description":"Requires: bearer token; role editor or admin. Names are unique per organization, ignoring case (409 `GROUP_EXISTS`).","tags":["v2 (preview)"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V2ContactGroupInput"},{"required":["name"]}]},"example":{"name":"Licensing Boards","color":"#1976d2"}}}},"responses":{"201":{"description":"Created.","headers":{"Location":{"$ref":"#/components/headers/V2Location"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2ContactGroup"}}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"409":{"$ref":"#/components/responses/V2Conflict"},"422":{"$ref":"#/components/responses/V2IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/v2/contact-groups/{id}":{"servers":[{"url":"https://api.expiryedge.com","description":"Production"}],"parameters":[{"$ref":"#/components/parameters/V2PathId"}],"get":{"operationId":"v2GetContactGroup","summary":"Get a contact group","description":"Requires: bearer token; any member of the group's organization.","tags":["v2 (preview)"],"responses":{"200":{"description":"The group.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2ContactGroup"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"delete":{"operationId":"v2DeleteContactGroup","summary":"Delete a contact group","description":"Requires: bearer token; role editor or admin. Member contacts are kept and become ungrouped.","tags":["v2 (preview)"],"responses":{"204":{"description":"Deleted.","headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"patch":{"operationId":"v2UpdateContactGroup","summary":"Update a contact group","description":"Requires: bearer token; role editor or admin. A rename also renames the group on every member contact.","tags":["v2 (preview)"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2ContactGroupInput"},"example":{"name":"State Boards"}}}},"responses":{"200":{"description":"The group after the change.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2ContactGroup"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"409":{"$ref":"#/components/responses/V2Conflict"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/v2/contacts":{"servers":[{"url":"https://api.expiryedge.com","description":"Production"}],"get":{"operationId":"v2ListContacts","summary":"List contacts (cursor-paginated)","description":"Requires: bearer token; any member of an organization.\nReturns `{ data, next_cursor, has_more }`, up to 100 per page (default 50), ordered by id unless `sort`\nis given. `status` is not supported on contacts (400).\n","tags":["v2 (preview)"],"parameters":[{"name":"type","in":"query","required":false,"description":"Exact `contact_type`.","schema":{"type":"string"}},{"$ref":"#/components/parameters/V2UpdatedSince"},{"name":"sort","in":"query","required":false,"description":"`id` (default), `updated_at`, `-updated_at`. With `updated_since`: `updated_at` or `-updated_at`.","schema":{"type":"string","enum":["id","updated_at","-updated_at"]}},{"$ref":"#/components/parameters/V2Cursor"},{"$ref":"#/components/parameters/V2Limit"}],"responses":{"200":{"description":"A page of contacts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2ContactPage"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"post":{"operationId":"v2CreateContact","summary":"Create a contact","description":"Requires: bearer token; role editor or admin.\nField names as stored (snake_case, plus `sendNotifications`); unknown fields are rejected (400).\nEmail must be unique in the organization (409 `EMAIL_EXISTS`). Counts toward the plan's contact limit.\n","tags":["v2 (preview)"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V2ContactInput"},{"required":["first_name"]}]},"example":{"first_name":"Priya","last_name":"Shah","email":"priya.shah@northwinddental.example","sms_phone":"+15550123456","job_title":"Practice Manager","contact_group":"Licensing","timezone":"America/New_York"}}}},"responses":{"201":{"description":"Created.","headers":{"Location":{"$ref":"#/components/headers/V2Location"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2Contact"}}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2ForbiddenOrPlanLimit"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"409":{"$ref":"#/components/responses/V2Conflict"},"422":{"$ref":"#/components/responses/V2IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/v2/contacts/{id}":{"servers":[{"url":"https://api.expiryedge.com","description":"Production"}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"example":"c_8Hk2pQ"}],"get":{"operationId":"v2GetContact","summary":"Get a contact","description":"Requires: bearer token; any member of the contact's organization.","tags":["v2 (preview)"],"responses":{"200":{"description":"The contact.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2Contact"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"delete":{"operationId":"v2DeleteContact","summary":"Delete a contact","description":"Requires: bearer token; role editor or admin.","tags":["v2 (preview)"],"responses":{"204":{"description":"Deleted.","headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"patch":{"operationId":"v2UpdateContact","summary":"Update a contact","description":"Requires: bearer token; role editor or admin.\nOnly fields present change; unknown fields are rejected (400). Returns the updated contact.\n","tags":["v2 (preview)"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2ContactInput"},"example":{"job_title":"Office Manager","sms_opt_in":true}}}},"responses":{"200":{"description":"The updated contact.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2Contact"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/v2/expiries":{"servers":[{"url":"https://api.expiryedge.com","description":"Production"}],"get":{"operationId":"v2ListExpiries","summary":"List expiries (cursor-paginated)","description":"Requires: bearer token; any member of an organization.\nReturns `{ data, next_cursor, has_more }`, up to 100 per page (default 50). Pass `next_cursor` back as\n`cursor` with the same `sort`. Records missing the sort field are not returned. `share_password` is never returned.\n","tags":["v2 (preview)"],"parameters":[{"name":"status","in":"query","required":false,"description":"`open` = not done and not archived; `done` = is_done; `archived` = is_archive. Default `all`.","schema":{"type":"string","enum":["open","done","archived","all"],"default":"all"}},{"name":"type","in":"query","required":false,"description":"Exact expiry `type`.","schema":{"type":"string"},"example":"License"},{"$ref":"#/components/parameters/V2UpdatedSince"},{"name":"sort","in":"query","required":false,"description":"`expiry_date` (default), `-expiry_date`, `updated_at`, `-updated_at` (`-` = descending).\nWith `updated_since` the sort must be `updated_at` or `-updated_at` (default `updated_at`).\n","schema":{"type":"string","enum":["expiry_date","-expiry_date","updated_at","-updated_at"]}},{"$ref":"#/components/parameters/V2Cursor"},{"$ref":"#/components/parameters/V2Limit"}],"responses":{"200":{"description":"A page of expiries.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2ExpiryPage"},"example":{"data":[{"id":"exp_4Tq9sLm2","name":"Northwind Dental - State Dental License","type":"License","expiry_date":"2026-10-15","state":"todo","is_done":false,"is_archive":false,"contacts":["c_8Hk2pQ"],"organization_id":"org_northwind","user_id":"u_71bXq","has_share_password":false,"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T15:10:00.000Z"}],"next_cursor":"WyJleHBpcnlfZGF0ZSIsIjIwMjYtMTAtMTUiLCJleHBfNFRxOXNMbTIiXQ","has_more":true}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"post":{"operationId":"v2CreateExpiry","summary":"Create an expiry","description":"Requires: bearer token; role editor or admin.\nSame rules as v1 createExpiry (plan limit, referenced contacts/users/folders must be in your organization).\nReturns 201 with the created expiry and a `Location` header.\n","tags":["v2 (preview)"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2ExpiryCreate"},"example":{"name":"Northwind Dental - State Dental License","type":"License","expiry_date":"2026-10-15","priority":"High","contacts":["c_8Hk2pQ"]}}}},"responses":{"201":{"description":"Created.","headers":{"Location":{"$ref":"#/components/headers/V2Location"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2Expiry"}}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2ForbiddenOrPlanLimit"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"409":{"$ref":"#/components/responses/V2Conflict"},"422":{"$ref":"#/components/responses/V2IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/v2/expiries/{id}":{"servers":[{"url":"https://api.expiryedge.com","description":"Production"}],"parameters":[{"$ref":"#/components/parameters/V2ExpiryId"}],"get":{"operationId":"v2GetExpiry","summary":"Get an expiry","description":"Requires: bearer token; any member of the expiry's organization.\nFields as stored: `contacts` is an array of contact ids. `share_password` is never returned.\n","tags":["v2 (preview)"],"responses":{"200":{"description":"The expiry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2Expiry"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"delete":{"operationId":"v2DeleteExpiry","summary":"Delete an expiry","description":"Requires: bearer token; role editor or admin.\nPermanently deletes the expiry and its pending reminders, notifications and triggers.\n","tags":["v2 (preview)"],"responses":{"204":{"description":"Deleted.","headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"patch":{"operationId":"v2UpdateExpiry","summary":"Update an expiry","description":"Requires: bearer token; role editor or admin.\nOnly fields present change. Server-owned fields (`id`, `organization_id`, `user_id`, `share_token`,\n`created_at`, recurrence and escalation bookkeeping) are ignored. Returns the updated expiry.\n","tags":["v2 (preview)"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpiryInput"},"example":{"expiry_date":"2027-10-15","notes":"Renewed early."}}}},"responses":{"200":{"description":"The updated expiry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2Expiry"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/v2/expiries/{id}/archive":{"servers":[{"url":"https://api.expiryedge.com","description":"Production"}],"parameters":[{"$ref":"#/components/parameters/V2ExpiryId"}],"post":{"operationId":"v2ArchiveExpiry","summary":"Archive an expiry","description":"Requires: bearer token; role editor or admin.\nSets `is_archive: true` and `state: onhold`, and pauses workflow and document-collection triggers.\n","tags":["v2 (preview)"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"The archived expiry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2Expiry"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"409":{"$ref":"#/components/responses/V2Conflict"},"422":{"$ref":"#/components/responses/V2IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/v2/expiries/{id}/attachments":{"servers":[{"url":"https://api.expiryedge.com","description":"Production"}],"parameters":[{"$ref":"#/components/parameters/V2ExpiryId"}],"get":{"operationId":"v2ListExpiryAttachments","summary":"List an expiry's attachments","description":"Requires: bearer token; any member of the expiry's organization.\nFiles in the order shown in the app. An expiry holds at most 50 files, so this is always one page\n(`next_cursor: null`, `has_more: false`).\n","tags":["v2 (preview)"],"responses":{"200":{"description":"The attachments.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2AttachmentPage"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"post":{"operationId":"v2StartExpiryAttachmentUpload","summary":"Start an attachment upload","description":"Requires: bearer token; role editor or admin.\nReturns a one-time signed URL. PUT the bytes to `upload_url` with exactly the returned `headers`, then call\n`POST /v2/expiries/{id}/attachments/complete` with the `upload_id` within 15 minutes.\nMax 50 MB per file and 50 files per expiry (409 `TOO_MANY_ATTACHMENTS`).\n","tags":["v2 (preview)"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["file_name","content_type","size"],"properties":{"file_name":{"type":"string","maxLength":255},"content_type":{"type":"string"},"size":{"type":"integer","minimum":1,"maximum":52428800}}},"example":{"file_name":"Dental License 2026.pdf","content_type":"application/pdf","size":482113}}}},"responses":{"201":{"description":"Upload ticket.","headers":{"Location":{"$ref":"#/components/headers/V2Location"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadTicket"}}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"409":{"$ref":"#/components/responses/V2Conflict"},"413":{"description":"`FILE_TOO_LARGE` (over 50 MB).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2Error"}}}},"422":{"$ref":"#/components/responses/V2IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"delete":{"operationId":"v2DeleteExpiryAttachment","summary":"Remove an attachment","description":"Requires: bearer token; role editor or admin.\nIdentify the file by `url` (recommended) or `index`. The stored file is deleted and its size given back to\nthe storage quota.\n","tags":["v2 (preview)"],"parameters":[{"name":"url","in":"query","required":false,"description":"The attachment's `url` from the list.","schema":{"type":"string"}},{"name":"index","in":"query","required":false,"description":"The attachment's `index`; used only when `url` is not given.","schema":{"type":"integer","minimum":0}}],"responses":{"204":{"description":"Removed.","headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/v2/expiries/{id}/attachments/complete":{"servers":[{"url":"https://api.expiryedge.com","description":"Production"}],"parameters":[{"$ref":"#/components/parameters/V2ExpiryId"}],"post":{"operationId":"v2CompleteExpiryAttachmentUpload","summary":"Finish an attachment upload","description":"Requires: bearer token; role editor or admin, and the user who started the upload.\nVerifies the uploaded file and adds it to the expiry. The upload must have been started for this expiry\n(404 otherwise). Returns 201 with the new attachment and `Location: /v2/expiries/{id}/attachments`.\n","tags":["v2 (preview)"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["upload_id"],"properties":{"upload_id":{"type":"string"}}},"example":{"upload_id":"up_5Nq2rT"}}}},"responses":{"201":{"description":"Attached.","headers":{"Location":{"$ref":"#/components/headers/V2Location"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpiryAttachment"}}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"409":{"$ref":"#/components/responses/V2Conflict"},"410":{"description":"`UPLOAD_EXPIRED` - start a new upload.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2Error"}}}},"422":{"description":"`UPLOAD_MISSING` (nothing was PUT), `UPLOAD_MISMATCH` (size or type differs) or `IDEMPOTENCY_KEY_REUSED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/v2/expiries/{id}/comments":{"servers":[{"url":"https://api.expiryedge.com","description":"Production"}],"parameters":[{"$ref":"#/components/parameters/V2ExpiryId"}],"get":{"operationId":"v2ListExpiryComments","summary":"List an expiry's comments","description":"Requires: bearer token; any member of the expiry's organization.\nNewest first, cursor-paginated. Fields as stored (the web app's camelCase: `expiryId`, `userId`, `timestamp`).\n","tags":["v2 (preview)"],"parameters":[{"name":"sort","in":"query","required":false,"description":"Only `-timestamp` (the default, newest first).","schema":{"type":"string","enum":["-timestamp"]}},{"$ref":"#/components/parameters/V2Cursor"},{"$ref":"#/components/parameters/V2Limit"}],"responses":{"200":{"description":"A page of comments.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2CommentPage"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"post":{"operationId":"v2CreateExpiryComment","summary":"Comment on an expiry","description":"Requires: bearer token; any member of the expiry's organization.\nPlain text (max 5000 characters); stored HTML-escaped with newlines as `<br>`, like the app.\nReturns 201 with the comment and `Location: /v2/comments/{commentId}`.\n","tags":["v2 (preview)"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2CommentInput"},"example":{"text":"Board confirmed the renewal fee."}}}},"responses":{"201":{"description":"Created.","headers":{"Location":{"$ref":"#/components/headers/V2Location"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2Comment"}}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"409":{"$ref":"#/components/responses/V2Conflict"},"422":{"$ref":"#/components/responses/V2IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/v2/expiries/{id}/complete":{"servers":[{"url":"https://api.expiryedge.com","description":"Production"}],"parameters":[{"$ref":"#/components/parameters/V2ExpiryId"}],"post":{"operationId":"v2CompleteExpiry","summary":"Mark an expiry done","description":"Requires: bearer token; role editor or admin.\nEvery required checklist item must be listed in `checklist_completed` (400 with `details.missing_required`\notherwise). A recurring expiry may create its next occurrence; its URL is in\n`Link: </v2/expiries/{id}>; rel=\"next-occurrence\"`.\n","tags":["v2 (preview)"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"closing_notes":{"type":"string"},"checklist_completed":{"type":"array","items":{"type":"string"}},"checklist_item_comments":{"type":"object","additionalProperties":{"type":"string"}}}},"example":{"closing_notes":"Renewed for 2 years.","checklist_completed":["upload-certificate"]}}}},"responses":{"200":{"description":"The completed expiry.","headers":{"Link":{"description":"Present when a recurring expiry created its next occurrence.","schema":{"type":"string"}},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2Expiry"}}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"409":{"$ref":"#/components/responses/V2Conflict"},"422":{"$ref":"#/components/responses/V2IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/v2/expiries/{id}/reminders":{"servers":[{"url":"https://api.expiryedge.com","description":"Production"}],"parameters":[{"$ref":"#/components/parameters/V2ExpiryId"}],"get":{"operationId":"v2ListExpiryReminders","summary":"List an expiry's reminders","description":"Requires: bearer token; any member of the expiry's organization.\nReminders as stored in `expiry_reminders`, soonest `scheduled_at` first, cursor-paginated.\n","tags":["v2 (preview)"],"parameters":[{"name":"sort","in":"query","required":false,"description":"Only `scheduled_at` (the default).","schema":{"type":"string","enum":["scheduled_at"]}},{"$ref":"#/components/parameters/V2Cursor"},{"$ref":"#/components/parameters/V2Limit"}],"responses":{"200":{"description":"A page of reminders.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2ReminderPage"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"post":{"operationId":"v2CreateExpiryReminders","summary":"Add reminders to an expiry","description":"Requires: bearer token; role editor or admin.\nAdds 1-20 reminders, scheduled from the expiry's stored `expiry_date` (an expiry without one is 400).\n`time` is in `timezone` (default: the organization's timezone, else UTC). Returns 201 with the created\nreminders as `{ data }` and `Location: /v2/expiries/{id}/reminders`.\n","tags":["v2 (preview)"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2ReminderSetInput"},"example":{"reminders":[{"amount":30,"time_unit":"day","period":"before","time":"09:00"},{"amount":7,"time_unit":"day","period":"before","time":"09:00","sms_enabled":false}]}}}},"responses":{"201":{"description":"Created.","headers":{"Location":{"$ref":"#/components/headers/V2Location"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2ReminderList"}}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"409":{"$ref":"#/components/responses/V2Conflict"},"422":{"$ref":"#/components/responses/V2IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"delete":{"operationId":"v2DeleteExpiryReminders","summary":"Delete all of an expiry's reminders","description":"Requires: bearer token; role editor or admin.","tags":["v2 (preview)"],"responses":{"204":{"description":"Deleted.","headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"patch":{"operationId":"v2ReplaceExpiryReminders","summary":"Replace an expiry's reminders","description":"Requires: bearer token; role editor or admin.\nReplaces the whole set with 1-20 reminders: an item with `id` updates that reminder, an item without one\nreuses a reminder with the same amount/time_unit/period or is created, and reminders not listed are deleted.\nA reminder moved into the future is re-armed (`status: pending`). Returns the full set. Use DELETE to remove all.\n","tags":["v2 (preview)"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2ReminderSetInput"},"example":{"reminders":[{"id":"rem_7Jd2","amount":14,"time_unit":"day","period":"before"},{"amount":1,"time_unit":"day","period":"after"}]}}}},"responses":{"200":{"description":"The reminder set after the change.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2ReminderPage"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/v2/folders":{"servers":[{"url":"https://api.expiryedge.com","description":"Production"}],"get":{"operationId":"v2ListFolders","summary":"List folders (flat, cursor-paginated)","description":"Requires: bearer token; any member of an organization.\nFolders that are not deleted, ordered by id, as stored (`parent_folder_id`, `folder_path`) - not the\nnested tree v1 getAllFolders returns.\n","tags":["v2 (preview)"],"parameters":[{"name":"parent_folder_id","in":"query","required":false,"description":"Only children of this folder; `root` = top-level folders.","schema":{"type":"string"}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","enum":["id"]}},{"$ref":"#/components/parameters/V2Cursor"},{"$ref":"#/components/parameters/V2Limit"}],"responses":{"200":{"description":"A page of folders.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2FolderPage"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"post":{"operationId":"v2CreateFolder","summary":"Create a folder","description":"Requires: bearer token; role editor or admin.\nNames are unique per parent (409 `CONFLICT`). `parent_folder_id` must be a folder in your organization (400).\n","tags":["v2 (preview)"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V2FolderInput"},{"required":["name"]}]},"example":{"name":"Dental","parent_folder_id":"fld_2Lq8"}}}},"responses":{"201":{"description":"Created.","headers":{"Location":{"$ref":"#/components/headers/V2Location"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2Folder"}}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"409":{"$ref":"#/components/responses/V2Conflict"},"422":{"$ref":"#/components/responses/V2IdempotencyKeyReused"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}},"/v2/folders/{id}":{"servers":[{"url":"https://api.expiryedge.com","description":"Production"}],"parameters":[{"$ref":"#/components/parameters/V2PathId"}],"get":{"operationId":"v2GetFolder","summary":"Get a folder","description":"Requires: bearer token; any member of the folder's organization. Deleted folders are 404.","tags":["v2 (preview)"],"responses":{"200":{"description":"The folder.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2Folder"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"delete":{"operationId":"v2DeleteFolder","summary":"Delete a folder","description":"Requires: bearer token; role editor or admin.\nA folder with subfolders or active expiries is 409 `FOLDER_NOT_EMPTY` (`details`: children_count,\nexpiries_count) unless `force=true`, which also deletes its subfolders and moves its expiries out of the\nfolder (expiries are not deleted).\n","tags":["v2 (preview)"],"parameters":[{"name":"force","in":"query","required":false,"schema":{"type":"string","enum":["true","false"],"default":"false"}}],"responses":{"204":{"description":"Deleted.","headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"409":{"$ref":"#/components/responses/V2Conflict"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}},"patch":{"operationId":"v2UpdateFolder","summary":"Rename or move a folder","description":"Requires: bearer token; role editor or admin.\n`parent_folder_id: null` moves it to the top level; a move that would create a cycle is 400.\n`folder_path` of the folder and its subfolders is updated.\n","tags":["v2 (preview)"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2FolderInput"},"example":{"name":"Permits"}}}},"responses":{"200":{"description":"The folder after the change.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2Folder"}}},"headers":{"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}}},"400":{"$ref":"#/components/responses/V2BadRequest"},"401":{"$ref":"#/components/responses/V2Unauthorized"},"403":{"$ref":"#/components/responses/V2Forbidden"},"404":{"$ref":"#/components/responses/V2NotFound"},"405":{"$ref":"#/components/responses/V2MethodNotAllowed"},"409":{"$ref":"#/components/responses/V2Conflict"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-rate-limit":{"limit":60,"window":"1m","scope":"per IP, per endpoint, per server instance"}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"Firebase ID token or session JWT","description":"One of: an organization API key (ee_live_..., recommended for integrations), a session JWT from POST /login (30 days), or a Firebase ID token (1 hour). A Firebase ID token (1 hour; refresh with the Firebase SDK) or the `accessToken` returned by\n`POST /login` (HS256 session JWT, 30 days). `POST /revokeAllSessions` invalidates session JWTs.\n"},"apiKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Organization API key (ee_live_...). Can also be sent as Authorization: Bearer ee_live_.... Create keys in Settings > Organization > API keys (admins). See docs/api/guides/api-keys.md."}},"parameters":{"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-generated key (1-255 characters of letters, digits, `- _ : .`). A retry with the\nsame key and body within 24 hours replays the original 2xx response (with `Idempotent-Replayed: true`)\ninstead of performing the action again.\n","schema":{"type":"string","pattern":"^[A-Za-z0-9_\\-:.]{1,255}$"},"example":"zap-7f3c2a91-create-expiry"},"ExpiryIdQuery":{"name":"id","in":"query","required":true,"description":"Expiry id.","schema":{"type":"string"},"example":"exp_4Tq9sLm2"},"ShareTokenQuery":{"name":"share_token","in":"query","required":false,"description":"The expiry's share token (the last segment of the share URL). Required here or in the body.","schema":{"type":"string"},"example":"5c0e7a52-3f7e-4b3e-9a2e-1d2f0b6c9e11"},"NotificationIdQuery":{"name":"id","in":"query","required":true,"description":"Notification id.","schema":{"type":"string"},"example":"ntf_3Jd8wQ"},"ReEngagementUid":{"name":"uid","in":"query","required":true,"schema":{"type":"string"},"example":"u_71bXq"},"ReEngagementToken":{"name":"token","in":"query","required":true,"description":"24-character token from the unsubscribe link.","schema":{"type":"string","minLength":24,"maxLength":24},"example":"9f2c1e7a4b8d03f6a1c5e2b7"},"TeamIdQuery":{"name":"id","in":"query","required":true,"description":"Team id.","schema":{"type":"string"},"example":"tm_3Fh8qLx"},"AvailabilityTeamIdQuery":{"name":"team_id","in":"query","required":false,"description":"Restrict to members of this team.","schema":{"type":"string"},"example":"tm_3Fh8qLx"},"V2ExpiryId":{"name":"id","in":"path","required":true,"schema":{"type":"string"},"example":"exp_4Tq9sLm2"},"V2PathId":{"name":"id","in":"path","required":true,"schema":{"type":"string"}},"V2Cursor":{"name":"cursor","in":"query","required":false,"description":"Opaque `next_cursor` from the previous page. Only valid with the same `sort`.","schema":{"type":"string"}},"V2Limit":{"name":"limit","in":"query","required":false,"description":"Page size, 1-100 (values above 100 are clamped; default 50).","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},"V2UpdatedSince":{"name":"updated_since","in":"query","required":false,"description":"ISO 8601 timestamp; only records with `updated_at` at or after it.","schema":{"type":"string","format":"date-time"},"example":"2026-09-01T00:00:00Z"}},"headers":{"XRateLimitRemaining":{"description":"Requests left in the current window for this endpoint (per IP, per instance).","schema":{"type":"integer"},"example":57},"RetryAfter":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"},"example":42},"IdempotentReplayed":{"description":"Present with value `true` when the response is a replay of an earlier request with the same Idempotency-Key.","schema":{"type":"string","enum":["true"]}},"XApiVersion":{"description":"Set to `1` when the request used the `/v1/` prefix.","schema":{"type":"string","enum":["1"]}},"V2Location":{"description":"URL of the created resource, e.g. `/v2/expiries/exp_4Tq9sLm2`.","schema":{"type":"string"}}},"responses":{"BadRequest":{"description":"The request is missing required fields or contains invalid values.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"expiryId is required","code":"VALIDATION_FAILED"}}}},"Unauthorized":{"description":"Missing, expired or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Your session has expired. Please sign in again.","code":"SESSION_EXPIRED","requestId":"req_8f2d1c"}}}},"Forbidden":{"description":"Authenticated, but your role or organization does not allow this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"You don't have permission to do that.","code":"FORBIDDEN"}}}},"PlanLimitExceeded":{"description":"The organization's plan limit for this resource has been reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanLimitError"},"example":{"error":"You have reached the maximum number of expiries for your plan.","current":100,"limit":100,"remaining":0}}}},"NotFound":{"description":"The record does not exist or is not in your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Expiry not found","code":"NOT_FOUND"}}}},"MethodNotAllowed":{"description":"The endpoint does not accept this HTTP method.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Method not allowed"}}}},"Conflict":{"description":"The request conflicts with existing data.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"That conflicts with existing data. Please refresh and try again.","code":"CONFLICT"}}}},"IdempotencyInProgress":{"description":"A request with the same Idempotency-Key is still being processed. Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"A request with this Idempotency-Key is still being processed","code":"IDEMPOTENCY_IN_PROGRESS"}}}},"IdempotencyKeyReused":{"description":"The Idempotency-Key was already used with a different request body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"This Idempotency-Key was already used with a different request body","code":"IDEMPOTENCY_KEY_REUSED"}}}},"RateLimited":{"description":"Too many requests for this endpoint from your IP. Wait `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitError"},"example":{"error":"Rate limit exceeded","message":"Too many requests. Please try again in 42 seconds.","retryAfter":42}}}},"InternalError":{"description":"Unexpected server error. Retry later; quote `requestId` to support.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Something went wrong on our end. Please try again in a moment.","code":"INTERNAL_ERROR","requestId":"req_8f2d1c"}}}},"PaginatedExpiriesOk":{"description":"One page of expiries. `data` and `expiries` hold the same array.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedExpiriesResponse"},"example":{"data":[{"id":"exp_4Tq9sLm2","name":"Northwind Dental - State Dental License","type":"License","expiry_date":"2026-10-15","priority":"High","state":"todo","is_done":false,"is_archive":false,"has_share_password":false,"assigned_to":"u_71bXq","assigned_to_user_full_name":"Priya Shah","assigned_to_user_email":"priya@northwinddental.com","assigned_by_user_full_name":null,"assigned_by_user_email":null}],"expiries":[{"id":"exp_4Tq9sLm2","name":"Northwind Dental - State Dental License","type":"License","expiry_date":"2026-10-15"}],"pagination":{"total":27,"totalPages":2,"currentPage":1,"page":1,"limit":25,"optimized":true}}}}},"ExpiryDetailOk":{"description":"The expiry with resolved contacts and assignee names.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpiryDetail"},"example":{"id":"exp_4Tq9sLm2","name":"Northwind Dental - State Dental License","type":"License","expiry_date":"2026-10-15","start_date":"2025-10-15","priority":"High","state":"todo","team_id":"0","is_done":false,"is_archive":false,"is_public":false,"has_share_password":false,"organization_id":"org_northwind","user_id":"u_71bXq","assigned_to":"u_71bXq","assigned_to_user_full_name":"Priya Shah","assigned_to_user_email":"priya@northwinddental.com","assigned_by_user_full_name":null,"assigned_by_user_email":null,"contacts":[{"id":"c_8Hk2pQ","firstName":"Priya","lastName":"Shah","email":"priya@northwinddental.com","smsPhone":"+15125550143","email_opt_in":true,"sms_opt_in":false,"whatsapp_opt_in":false}],"escalation_contacts":[],"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}}}},"ExpiryUpdatedOk":{"description":"Updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Expiry updated successfully"}}}},"ExpiryDeletedOk":{"description":"Deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"},"example":{"message":"Expiry deleted successfully"}}}},"BulkImportExpiriesError":{"description":"No rows, unknown template, bad token (401) or a failed commit (500, nothing written). `errors` repeats the message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkImportExpiriesFailure"},"example":{"error":"No records to import","successCount":0,"errors":["No valid records found in the request"]}}}},"EmailTemplatePlanRequired":{"description":"The organization's plan does not include custom email templates.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Custom email templates are available on the Pro Essentials plan or higher.","code":"PLAN_UPGRADE_REQUIRED"}}}},"SharePasswordRequired":{"description":"The share link is password-protected and the password is missing or wrong.","content":{"application/json":{"schema":{"type":"object","required":["error","requiresPassword"],"properties":{"error":{"type":"string"},"requiresPassword":{"type":"boolean","const":true}}},"example":{"error":"Password required","requiresPassword":true}}}},"SharedExpiryOk":{"description":"Public display fields of the shared expiry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SharedExpiryView"},"example":{"id":"exp_4Tq9sLm2","name":"Northwind Dental - State Dental License","type":"License","priority":"High","state":"todo","expiry_date":"2026-10-15","start_date":"2025-10-15","notes":"Renew with the State Board of Dentistry at least 30 days ahead.","is_done":false,"is_archive":false,"is_public":true,"has_workflow":true,"workflow_attachment_ids":["wf_0"],"assigned_to_user_full_name":"Priya Shah","assigned_to_user_email":"priya@northwinddental.com","assigned_by_user_full_name":null,"assigned_by_user_email":null,"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}}}},"ExpiryTypesWithStatsOk":{"description":"Expiry types with counts.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ExpiryTypeWithStats"}},"example":[{"id":"et_5Gm1rT","name":"Business License","expiry_count":12,"created_at":"2026-03-02T10:15:00.000Z","email_template_id":"tmpl_7Qw2","email_template_name":"Friendly license reminder","email_template_missing":false,"email_template_active":true},{"id":"et_2Kd9wB","name":"Insurance","expiry_count":7,"created_at":"2026-03-02T10:16:00.000Z","email_template_id":null,"email_template_name":null,"email_template_missing":false,"email_template_active":false}]}}},"ExpiryShareUrlOk":{"description":"Sharing enabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpiryShareUrl"},"example":{"share_url":"https://app.expiryedge.com/share/5c0e7a52-3f7e-4b3e-9a2e-1d2f0b6c9e11","share_token":"5c0e7a52-3f7e-4b3e-9a2e-1d2f0b6c9e11"}}}},"ExpiryAttachmentDeleted":{"description":"File removed. Returns the remaining attachments.","content":{"application/json":{"schema":{"type":"object","required":["deleted","url","attachments"],"properties":{"deleted":{"type":"boolean","const":true},"url":{"type":"string","description":"Download URL of the removed file."},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/ExpiryAttachment"}}}},"example":{"deleted":true,"url":"https://firebasestorage.googleapis.com/v0/b/stylingsphere.appspot.com/o/user_files%2Forg_northwind%2Fu_71bXq%2Fexpiry_exp_4Tq9sLm2%2FGas_Safety_Certificate_2025.pdf?alt=media&token=5b1f0c7e-2a41-4d8e-9f3a-0c6d2e7b9a11","attachments":[{"index":0,"name":"Gas Safety Certificate 2026.pdf","url":"https://firebasestorage.googleapis.com/v0/b/stylingsphere.appspot.com/o/user_files%2Forg_northwind%2Fu_71bXq%2Fexpiry_exp_4Tq9sLm2%2F1790517900000_Gas_Safety_Certificate_2026.pdf?alt=media&token=9c2e4d1a-7b3f-4e6a-8d5c-1f0a2b3c4d5e"}]}}}},"ReEngagementPlainTextError":{"description":"Invalid link or server error (plain text body).","content":{"text/plain":{"schema":{"type":"string"},"example":"Invalid or expired unsubscribe link."}}},"TeamMembersOk":{"description":"Team members.","content":{"application/json":{"schema":{"type":"object","required":["members"],"properties":{"members":{"type":"array","items":{"$ref":"#/components/schemas/TeamMember"}}}},"example":{"members":[{"membership_id":"membership_u_71bXq_tm_3Fh8qLx","user_id":"u_71bXq","team_id":"tm_3Fh8qLx","role":"admin","email":"dana@northwinddental.com","firstName":"Dana","lastName":"Brooks","displayName":"Dana Brooks"}]}}}},"AccountDetailsOk":{"description":"Account details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountDetails"},"example":{"name":"Dana Brooks","organization_name":"Northwind Dental","subscription_id":"prod_SK2RjJ132uucmf","subscription_plan":"prod_SK2RjJ132uucmf","plan_name":"ProEssentials","subscription_status":"active","cancel_at_period_end":false,"last_payment_date":"2026-09-01T00:00:00.000Z","last_payment_amount":49,"billing_period":"month","next_billing_date":"2026-10-01T00:00:00.000Z","order_date":"2026-03-01T10:00:00.000Z","plan_expiry_date":null,"account_created_at":"2026-03-01T09:55:00.000Z","notification_credit":{"organization_id":"org_northwind","sms_balance":120,"email_balance":4800}}}}},"PersonalDataExportOk":{"description":"JSON export, sent as an attachment named `expiryedge-my-data.json`.","headers":{"Content-Disposition":{"schema":{"type":"string"},"example":"attachment; filename=\"expiryedge-my-data.json\""}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PersonalDataExport"},"example":{"exported_at":"2026-09-27T14:05:00.000Z","account":{"email":"dana@northwinddental.com","firstName":"Dana","lastName":"Brooks","organization_id":"org_northwind","role":"admin"},"organization":{"name":"Northwind Dental","id_note":"Full organization record available to the organization owner only."},"user_settings":null,"user_onboarding":null,"notification_preferences":{"push_enabled":true,"push_scope":"assigned"},"note":"This export covers your account profile and settings."}}}},"WebhookSettingsOk":{"description":"Webhook URLs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSettings"},"example":{"teams_webhook":"https://northwind.webhook.office.com/webhookb2/7c1e2a90","slack_webhook":null}}}},"ReminderSettingsOk":{"description":"Default reminder rows.","content":{"application/json":{"schema":{"type":"object","required":["reminders"],"properties":{"reminders":{"type":"array","items":{"$ref":"#/components/schemas/ReminderSetting"}}}},"example":{"reminders":[{"time_unit":"day","amount":30,"period":"before","time":"09:00","timezone":"America/Chicago"},{"time_unit":"day","amount":7,"period":"before","time":"09:00","timezone":"America/Chicago"}]}}}},"EscalationSettingsOk":{"description":"Default escalation days.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EscalationSettings"},"example":{"default_escalation_notification_days":[7]}}}},"OrganizationSettingsOk":{"description":"Organization settings.","content":{"application/json":{"schema":{"type":"object","required":["organization"],"properties":{"organization":{"$ref":"#/components/schemas/OrganizationSettings"}}},"example":{"organization":{"name":"Northwind Dental","industry":"Healthcare","timezone":"America/Chicago","logo_url":"https://storage.googleapis.com/stylingsphere.appspot.com/organizations/org_northwind/logo/1790517900000_logo.png","website":"https://northwinddental.com","phone":"+1 312 555 0142","address":"200 W Madison St, Chicago, IL","email_sender_name":"Northwind Dental","email_sender_prefix":"reminders","email_sender_identity_enabled":true,"email_reminder_mode":"INDIVIDUAL","digest_send_time":"09:00","base_currency":"USD","billing_email":"billing@northwinddental.com","tax_id":{"type":"us_ein","value":"12-3456789"},"setup_checklist_acks":{"reminder_sequence":true},"expiry_form_sections":null,"expiry_form_section_order":null}}}}},"AvailabilityListOk":{"description":"Leave records.","content":{"application/json":{"schema":{"type":"object","required":["availability"],"properties":{"availability":{"type":"array","items":{"$ref":"#/components/schemas/AvailabilityRecord"}}}},"example":{"availability":[{"id":"av_6Jc3nTy","user_id":"u_4Np8cZe","organization_id":"org_northwind","start_date":"2026-10-15","end_date":"2026-10-22","reason":"Annual leave","created_by":"u_4Np8cZe","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}]}}}},"AvailabilitySuccessOk":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean"}}},"example":{"success":true}}}},"BackupRulesOk":{"description":"Backup rules with display names, plus the organization's users.","content":{"application/json":{"schema":{"type":"object","required":["rules","users"],"properties":{"rules":{"type":"array","items":{"$ref":"#/components/schemas/BackupRule"}},"users":{"type":"array","description":"Every user in the organization (`id` plus profile fields).","items":{"type":"object","additionalProperties":true,"properties":{"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"role":{"type":"string"}}}}}},"example":{"rules":[{"id":"br_8Vt1xKd","organization_id":"org_northwind","primary_user_id":"u_4Np8cZe","primary_user_name":"Sam Ortiz","backup_type":"general","general_backup_user_id":"u_71bXq","general_backup_user_name":"Dana Brooks","fallback_user_id":null,"fallback_user_name":"","type_backups":[],"updated_by":"u_71bXq"}],"users":[{"id":"u_71bXq","email":"dana@northwinddental.com","firstName":"Dana","lastName":"Brooks","role":"admin"}]}}}},"ReassignmentLogsOk":{"description":"Reassignment log entries.","content":{"application/json":{"schema":{"type":"object","required":["logs"],"properties":{"logs":{"type":"array","items":{"$ref":"#/components/schemas/ReassignmentLog"}}}},"example":{"logs":[{"id":"rl_1Hs5bNw","expiry_id":"exp_4Tq9sLm2","expiry_name":"Dental license renewal","expiry_date":"2026-10-20","from_user_id":"u_4Np8cZe","from_user_name":"Sam Ortiz","to_user_id":"u_71bXq","to_user_name":"Dana Brooks","reason":"Auto-reassigned: Sam Ortiz on leave 2026-10-15 - 2026-10-22","triggered_by":"auto","organization_id":"org_northwind","timestamp":"2026-10-14T07:00:00.000Z"}]}}}},"LegacyMessageBadRequest":{"description":"Missing or invalid fields (legacy `{message}` body).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LegacyMessageError"},"example":{"message":"start_date and end_date are required"}}}},"LegacyMessageForbidden":{"description":"Not allowed (legacy `{message}` body).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LegacyMessageError"},"example":{"message":"Not authorized"}}}},"LegacyMessageNotFound":{"description":"Record not found or not in your organization (legacy `{message}` body).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LegacyMessageError"},"example":{"message":"Record not found"}}}},"LegacyMessageServerError":{"description":"Server error, including a missing or invalid token (legacy `{message}` body).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LegacyMessageError"},"example":{"message":"Unauthorized - No token provided"}}}},"V2BadRequest":{"description":"Invalid parameters or body (`VALIDATION_FAILED`; `details` names the fields).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2Error"},"example":{"error":{"code":"VALIDATION_FAILED","message":"Some fields are missing or invalid.","details":{"fields":{"expiry_date":"required, YYYY-MM-DD"}},"request_id":"9f2c4e1a7b3d"}}}}},"V2Unauthorized":{"description":"Missing, invalid or expired token (`UNAUTHORIZED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2Error"}}}},"V2Forbidden":{"description":"Your role can't do this, or your account has no organization (`FORBIDDEN`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2Error"}}}},"V2ForbiddenOrPlanLimit":{"description":"`FORBIDDEN` (role / no organization) or `PLAN_LIMIT_REACHED` (`details`: current, limit, remaining).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2Error"},"example":{"error":{"code":"PLAN_LIMIT_REACHED","message":"You have reached your plan's expiry limit.","details":{"current":100,"limit":100,"remaining":0},"request_id":"1c8d0b6e2f4a"}}}}},"V2NotFound":{"description":"No such record in your organization (records in other organizations are also 404).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2Error"}}}},"V2MethodNotAllowed":{"description":"Wrong HTTP method for this path; the `Allow` header lists the valid ones.","headers":{"Allow":{"schema":{"type":"string"},"example":"GET, POST"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2Error"}}}},"V2Conflict":{"description":"State or uniqueness conflict: `EMAIL_EXISTS` (contacts), `GROUP_EXISTS` (contact groups), `CONFLICT` (folder\nname taken), `FOLDER_NOT_EMPTY`, `CLIENT_HAS_PROJECTS`, `TOO_MANY_ATTACHMENTS`, `INVALID_STATE` (collection\nrequest cancel/resend), or `IDEMPOTENCY_IN_PROGRESS` (same Idempotency-Key still running; see Retry-After).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2Error"}}}},"V2IdempotencyKeyReused":{"description":"The Idempotency-Key was already used with a different body (`IDEMPOTENCY_KEY_REUSED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V2Error"}}}}},"schemas":{"Error":{"type":"object","description":"Standard error envelope. `code` and `requestId` are present on handlers that use the shared error\nhelper; older handlers return only `error` (and sometimes `message` or `details`).\n","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable message, safe to show to end users."},"code":{"type":"string","description":"Stable machine-readable code. Treat unknown codes by HTTP status. Known codes: `ALREADY_EXISTS`, `ASANA_SYNC_FAILED`, `BAD_REQUEST`, `CONFLICT`, `CREATE_FAILED`, `DELETE_FAILED`, `EMAIL_SEND_FAILED`, `FETCH_FAILED`, `FILE_NOT_FOUND`, `FILE_TOO_LARGE`, `FILE_UPLOAD_FAILED`, `FORBIDDEN`, `IDEMPOTENCY_IN_PROGRESS`, `IDEMPOTENCY_KEY_REUSED`, `INTEGRATION_AUTH_FAILED`, `INTEGRATION_UNAVAILABLE`, `INTERNAL_ERROR`, `INVALID_SIGNATURE`, `INVALID_WEBHOOK_URL`, `LIMIT_REACHED`, `METHOD_NOT_ALLOWED`, `NETWORK_ERROR`, `NOT_FOUND`, `PAYMENT_FAILED`, `PLAN_UPGRADE_REQUIRED`, `RATE_LIMITED`, `SESSION_EXPIRED`, `SUBSCRIPTION_ERROR`, `TIMEOUT`, `TOKEN_INVALID`, `TOKEN_MISSING`, `TOO_MANY_ROWS`, `TRIAL_UPGRADE_REQUIRED`, `UNAUTHORIZED`, `UPDATE_FAILED`, `VALIDATION_ERROR`, `VALIDATION_FAILED`.","examples":["VALIDATION_FAILED"]},"requestId":{"type":"string","description":"Correlation id for support; present on errors produced by the shared error helper."},"message":{"type":"string","description":"Extra detail returned by some older handlers."},"details":{"description":"Field-level validation details (object keyed by field, or an array of messages).","oneOf":[{"type":"object","additionalProperties":true},{"type":"array","items":{}}]}}},"LegacyMessageError":{"type":"object","description":"Error shape used by `/login` and a few legacy handlers.","required":["message"],"properties":{"message":{"type":"string"}},"example":{"message":"Invalid email or password"}},"RateLimitError":{"type":"object","required":["error","retryAfter"],"properties":{"error":{"type":"string","const":"Rate limit exceeded"},"message":{"type":"string"},"retryAfter":{"type":"integer","description":"Seconds until the window resets."}}},"PlanLimitError":{"type":"object","description":"Returned with 403 when a plan limit blocks a create.","required":["error"],"properties":{"error":{"type":"string"},"current":{"type":"integer"},"limit":{"type":"integer"},"remaining":{"type":"integer"}}},"MessageResponse":{"type":"object","required":["message"],"properties":{"message":{"type":"string"}},"example":{"message":"Expiry updated successfully"}},"SuccessResponse":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"}},"example":{"success":true,"message":"Done"}},"IsoDate":{"type":"string","description":"Calendar date `YYYY-MM-DD` in the organization's timezone. The web app may also send\n`YYYY-MM-DDTHH:mm` when a specific time is chosen (paired with `expiry_timezone` / `start_timezone`).\n","examples":["2026-10-15"]},"Timestamp":{"type":"string","format":"date-time","description":"UTC timestamp, ISO 8601. Every v1 response uses this form, never a raw Firestore Timestamp (`{_seconds, _nanoseconds}`).","examples":["2026-09-27T14:05:00.000Z"]},"Page":{"type":"object","description":"Cursor page envelope used by the change feeds. Pass `next_cursor` as `cursor` to get the next page.","required":["data","next_cursor","has_more"],"properties":{"data":{"type":"array","items":{}},"next_cursor":{"type":["string","null"],"description":"Opaque cursor. Store it and send it as `cursor` on the next poll, even when `has_more` is false."},"has_more":{"type":"boolean"}}},"ExpiryInput":{"type":"object","description":"Writable expiry fields. The handler accepts additional custom keys (they are stored as-is after\nHTML sanitization). Server-owned fields (`id`, `organization_id`, `user_id`, `created_at`, `share_token`) are always set by the server; updateExpiry also ignores recurrence and escalation bookkeeping fields. Referenced `contacts`, `escalation_contacts`,\n`directory_entry_ids`, `assigned_to` and `folder_id` must belong to your organization (400 otherwise).\n","additionalProperties":true,"properties":{"name":{"type":"string","description":"Display name."},"type":{"type":"string","description":"Expiry type name (see getExpiryTypesWithStats)."},"expiry_date":{"$ref":"#/components/schemas/IsoDate"},"expiry_timezone":{"type":"string","description":"IANA timezone for `expiry_date` when it carries a time."},"start_date":{"oneOf":[{"$ref":"#/components/schemas/IsoDate"},{"type":"null"}]},"start_timezone":{"type":"string"},"priority":{"type":"string","enum":["Low","Medium","High"]},"state":{"type":"string","description":"Workflow state; defaults to `todo`.","enum":["todo","inprogress","onhold","inreview","completed"]},"notes":{"type":"string"},"url":{"type":"string"},"document_type":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"team_id":{"type":"string","description":"Team id, or `'0'` for no team."},"folder_id":{"type":["string","null"]},"assigned_to":{"type":["string","null"],"description":"User id of the assignee (must be in your organization)."},"assigned_by":{"type":"string"},"contacts":{"type":"array","description":"Contact ids that receive reminders.","items":{"type":"string"}},"directory_entry_ids":{"type":"array","items":{"type":"string"}},"is_public":{"type":"boolean","description":"Enables the public share link (viewExpiry)."},"share_password":{"type":"string","writeOnly":true,"description":"Optional password for the share link. Never returned."},"value":{"type":["number","null"]},"penalty_amount":{"type":["number","null"]},"currency":{"type":["string","null"],"description":"ISO 4217 code, uppercase."},"custom_columns":{"type":"object","additionalProperties":true},"checklist_items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"required":{"type":"boolean"}}}},"template_id":{"type":["string","null"]},"template_name":{"type":["string","null"]},"template_fields":{"type":["array","null"],"items":{"type":"object","additionalProperties":true}},"template_data":{"type":["object","null"],"additionalProperties":true},"fileUrls":{"type":"array","items":{"type":"string"}},"fileNames":{"type":"array","items":{"type":"string"}},"is_recurring":{"type":"boolean"},"recurrence_type":{"type":["string","null"],"enum":["day","week","month","year",null]},"recurrence_interval":{"type":["integer","null"]},"recurrence_count":{"type":["integer","null"]},"recurrence_end_date":{"type":["string","null"]},"recurrence_mode":{"type":"string","enum":["renew_as_copy","renew_same"],"description":"Defaults to `renew_as_copy`."},"auto_renew":{"type":"boolean"},"auto_renew_vendor":{"type":["string","null"]},"escalation_enabled":{"type":"boolean","description":"Always false on create unless explicitly true."},"escalation_contacts":{"type":"array","items":{"type":"string"}},"escalation_notification_days":{"type":"array","description":"Days before expiry at which escalation fires (0-365).","items":{"type":"integer"}}},"example":{"name":"Northwind Dental - State Dental License","type":"License","expiry_date":"2026-10-15","start_date":"2025-10-15","priority":"High","state":"todo","notes":"Renew with the State Board of Dentistry at least 30 days ahead.","url":"https://dentalboard.example.gov/renewals","team_id":"0","contacts":["c_8Hk2pQ"],"tags":["licensing"]}},"Expiry":{"description":"An expiry record as stored. Contains every field from `ExpiryInput` plus server-owned fields.","allOf":[{"$ref":"#/components/schemas/ExpiryInput"},{"type":"object","properties":{"id":{"type":"string","readOnly":true},"organization_id":{"type":"string","readOnly":true},"user_id":{"type":"string","readOnly":true,"description":"Creator's user id."},"share_token":{"type":"string","readOnly":true},"is_done":{"type":"boolean"},"completed_at":{"type":["string","null"],"format":"date-time"},"is_archive":{"type":"boolean","description":"Archived flag (the canonical field name)."},"occurrence_number":{"type":"integer","readOnly":true},"is_auto_created":{"type":"boolean","readOnly":true},"recurrence_created_from_id":{"type":["string","null"],"readOnly":true},"renewal_history":{"type":"array","readOnly":true,"items":{"type":"object","additionalProperties":true}},"escalation_notification_sent_days":{"type":"array","readOnly":true,"items":{"type":"integer"}},"escalation_all_sent":{"type":"boolean","readOnly":true},"created_at":{"$ref":"#/components/schemas/Timestamp","readOnly":true},"updated_at":{"$ref":"#/components/schemas/Timestamp","readOnly":true}}}],"example":{"id":"exp_4Tq9sLm2","name":"Northwind Dental - State Dental License","type":"License","expiry_date":"2026-10-15","start_date":"2025-10-15","priority":"High","state":"todo","notes":"Renew with the State Board of Dentistry at least 30 days ahead.","team_id":"0","contacts":["c_8Hk2pQ"],"assigned_to":"u_71bXq","is_done":false,"is_archive":false,"is_public":false,"is_recurring":false,"organization_id":"org_northwind","user_id":"u_71bXq","share_token":"5c0e7a52-3f7e-4b3e-9a2e-1d2f0b6c9e11","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}},"Comment":{"type":"object","description":"A comment on an expiry. `text` is HTML-escaped with newlines stored as `<br>`.","properties":{"id":{"type":"string","readOnly":true},"expiryId":{"type":"string"},"text":{"type":"string"},"userId":{"type":"string","readOnly":true},"username":{"type":"string","readOnly":true},"edited":{"type":"boolean","readOnly":true},"timestamp":{"type":["string","null"],"format":"date-time","readOnly":true}},"example":{"id":"cmt_2Lx8","expiryId":"exp_4Tq9sLm2","text":"Renewal form submitted to the board.<br>Awaiting receipt.","userId":"u_71bXq","username":"Priya Shah","edited":false,"timestamp":"2026-09-27T14:05:00.000Z"}},"CollectionSubmissionFile":{"type":"object","description":"File metadata only. Download files with getCollectionSubmissionFile.","properties":{"field_id":{"type":["string","null"]},"name":{"type":["string","null"]},"content_type":{"type":["string","null"]},"size":{"type":["integer","null"]}}},"CollectionSubmission":{"type":"object","description":"A recipient's submission for a Document Collection request, as returned by listCollectionSubmissions.","properties":{"id":{"type":"string","readOnly":true},"request_id":{"type":"string"},"template_id":{"type":"string"},"submitted_at":{"$ref":"#/components/schemas/Timestamp"},"review_status":{"type":"string","enum":["pending","approved","changes_requested","rejected"]},"revision":{"type":["integer","null"]},"responses":{"type":"object","description":"Field id -> answer.","additionalProperties":true},"files":{"type":"array","items":{"$ref":"#/components/schemas/CollectionSubmissionFile"}}},"example":{"id":"sub_Qm3r","request_id":"req_9WkT","template_id":"tpl_W9onboard","submitted_at":"2026-09-26T16:42:10.000Z","review_status":"pending","revision":1,"responses":{"fld_company_name":"Northwind Dental LLC"},"files":[{"field_id":"fld_insurance","name":"liability-certificate-2026.pdf","content_type":"application/pdf","size":482113}]}},"UploadTicketRequest":{"type":"object","required":["file_name","content_type","size"],"description":"Describe the file you are about to upload. Allowed types: images (png, jpeg, gif, webp, bmp, heic, tiff), audio, video, PDF, Word, Excel, PowerPoint, txt, csv, json, zip, rar, 7z, gzip. HTML, SVG and XML are not accepted. Max 50 MB, and the upload must fit in your plan storage quota.","properties":{"file_name":{"type":"string","maxLength":255,"examples":["fire-safety-certificate-2026.pdf"]},"content_type":{"type":"string","examples":["application/pdf"]},"size":{"type":"integer","minimum":1,"maximum":52428800,"description":"Exact file size in bytes.","examples":[184022]}}},"UploadTicket":{"type":"object","description":"A one-time upload link. PUT the file bytes to upload_url with exactly these headers within 15 minutes, then call the matching complete endpoint with upload_id.","required":["upload_id","upload_url","method","headers","expires_at","max_size"],"properties":{"upload_id":{"type":"string","examples":["pUp7x1"]},"upload_url":{"type":"string","format":"uri","description":"Signed Google Cloud Storage URL."},"method":{"type":"string","const":"PUT"},"headers":{"type":"object","additionalProperties":{"type":"string"},"examples":[{"Content-Type":"application/pdf","x-goog-content-length-range":"0,184022"}]},"expires_at":{"type":"string","format":"date-time"},"max_size":{"type":"integer"}}},"ApiKeyInput":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"Label shown in Settings (HTML is stripped)."},"role":{"type":"string","enum":["editor","viewer"],"default":"editor","description":"What the key may do. Keys can never be admin."},"expires_at":{"type":["string","null"],"format":"date-time","description":"Optional future time after which the key stops working."}}},"ApiKey":{"type":"object","description":"An organization API key without the secret. Only the first 12 characters (`prefix`) are ever shown again.","properties":{"id":{"type":"string","readOnly":true},"name":{"type":"string"},"prefix":{"type":"string","readOnly":true,"description":"First 12 characters of the key, e.g. `ee_live_4fQ9`."},"role":{"type":"string","enum":["editor","viewer"]},"status":{"type":"string","enum":["active","expired","revoked"],"readOnly":true},"created_by":{"type":"string","readOnly":true},"created_at":{"type":"string","format":"date-time","readOnly":true},"last_used_at":{"type":["string","null"],"format":"date-time","readOnly":true},"expires_at":{"type":["string","null"],"format":"date-time"},"revoked_at":{"type":["string","null"],"format":"date-time","readOnly":true}}},"ApiKeyCreated":{"type":"object","properties":{"key":{"type":["string","null"],"readOnly":true,"description":"The full key (`ee_live_` + 43 characters). Shown only once; null on an Idempotency-Key replay."},"api_key":{"$ref":"#/components/schemas/ApiKey"}}},"ReferralCodePublic":{"type":"object","description":"What an anonymous caller (the signup referral banner) sees for a referral code.","required":["code_info"],"properties":{"code_info":{"type":"object","required":["referral_code","active"],"properties":{"referral_code":{"type":"string"},"active":{"type":"boolean"},"user_discount_percentage":{"type":["number","null"]},"user_discount_duration":{"type":["integer","null"],"description":"Billing cycles the discount lasts."},"user_free_trial_days":{"type":["integer","null"]}}}},"example":{"code_info":{"referral_code":"CONTOSO25","active":true,"user_discount_percentage":25,"user_discount_duration":3,"user_free_trial_days":14}}},"User":{"type":"object","description":"A user profile (`users/{uid}`). `getMe` returns the full profile minus internal fields; `getUsers` and `login`\nreturn a subset. Older documents may carry `createdAt`/`updatedAt` instead of `created_at`/`updated_at`.\n","additionalProperties":true,"properties":{"id":{"type":"string","readOnly":true},"email":{"type":"string","format":"email","readOnly":true},"firstName":{"type":"string"},"lastName":{"type":"string"},"displayName":{"type":"string"},"photoURL":{"type":["string","null"]},"avatar":{"type":["string","null"]},"phone":{"type":["string","null"]},"address":{"description":"Free-form address (string or object)."},"bio":{"type":["string","null"]},"timezone":{"type":["string","null"],"description":"IANA timezone."},"locale":{"type":["string","null"]},"role":{"type":"string","enum":["admin","editor","viewer","member","user"],"readOnly":true,"description":"`member` is an alias of editor, `user` of viewer."},"user_type":{"type":"string","enum":["organization","individual"],"readOnly":true},"organization_id":{"type":["string","null"],"readOnly":true},"status":{"type":"string","readOnly":true,"description":"`active` unless set otherwise (for example `migrated`)."},"email_verified":{"type":"boolean","readOnly":true,"description":"Absent on legacy accounts (treat as verified)."},"dashboardColumnPrefs":{"type":"object","additionalProperties":true},"notificationPrefs":{"type":"object","additionalProperties":true},"uiPrefs":{"type":"object","additionalProperties":true},"created_at":{"$ref":"#/components/schemas/Timestamp","readOnly":true},"createdAt":{"$ref":"#/components/schemas/Timestamp","readOnly":true},"updated_at":{"$ref":"#/components/schemas/Timestamp","readOnly":true}},"example":{"id":"u_71bXq","email":"priya.shah@northwinddental.com","firstName":"Priya","lastName":"Shah","displayName":"Priya Shah","role":"admin","user_type":"organization","organization_id":"org_northwind","status":"active","timezone":"America/Chicago","created_at":"2026-09-27T14:05:00.000Z"}},"LoginResponse":{"type":"object","required":["accessToken","user","teams"],"properties":{"accessToken":{"type":"string","readOnly":true,"description":"HS256 session JWT, valid 30 days. Send as `Authorization: Bearer <accessToken>`."},"user":{"$ref":"#/components/schemas/User"},"teams":{"type":"array","description":"Teams the user belongs to.","items":{"$ref":"#/components/schemas/LoginTeamMembership"}}}},"Reminder":{"type":"object","description":"A scheduled reminder for one expiry (`expiry_reminders`). `scheduled_at` is the UTC send time computed from\n`expiry_date`, `amount`/`time_unit`/`period` and the local `time` in `timezone`. Delivery bookkeeping fields\n(`email_sent`, `*_sent_count`, `skip_reason`...) may also be present.\n","additionalProperties":true,"properties":{"id":{"type":"string","readOnly":true},"expiry_id":{"type":"string","readOnly":true},"organization_id":{"type":"string","readOnly":true},"user_id":{"type":"string","readOnly":true},"amount":{"type":"integer","description":"How many `time_unit`s before/after the expiry date."},"time_unit":{"type":"string","enum":["day","week","month","year"],"description":"A month counts as 30 days and a year as 365."},"period":{"type":"string","enum":["before","after"]},"time":{"type":"string","description":"Local send time `HH:mm`.","examples":["09:00"]},"timezone":{"type":"string","description":"IANA timezone for `time`."},"reminder_date":{"$ref":"#/components/schemas/Timestamp","readOnly":true,"description":"Reminder day (midnight UTC)."},"scheduled_at":{"$ref":"#/components/schemas/Timestamp","readOnly":true},"email_enabled":{"type":"boolean"},"sms_enabled":{"type":"boolean"},"whatsapp_enabled":{"type":"boolean"},"status":{"type":"string","readOnly":true,"description":"Typically `pending`, `sent`, `failed` or `cancelled`.","examples":["pending"]},"sent_at":{"type":["string","null"],"format":"date-time","readOnly":true},"error_message":{"type":["string","null"],"readOnly":true},"created_at":{"$ref":"#/components/schemas/Timestamp","readOnly":true},"updated_at":{"$ref":"#/components/schemas/Timestamp","readOnly":true}}},"ReminderInput":{"type":"object","description":"One reminder definition. Channels default to enabled unless explicitly `false`.","required":["amount","time_unit","period"],"properties":{"amount":{"type":"integer","minimum":0,"maximum":3650},"time_unit":{"type":"string","enum":["day","week","month","year"]},"period":{"type":"string","enum":["before","after"]},"time":{"type":"string","description":"Local send time `HH:mm`; defaults to `09:00`.","pattern":"^([01]?\\d|2[0-3]):[0-5]\\d$"},"timezone":{"type":"string","description":"IANA timezone for this reminder (overrides the request-level timezone where supported)."},"email":{"type":"boolean","default":true},"sms":{"type":"boolean","default":true,"description":"SMS uses the organization's SMS credits."},"whatsapp":{"type":"boolean","default":true},"firestore_id":{"type":"string","description":"updateExpiryReminders only - id of the existing reminder to update in place."}},"example":{"amount":30,"time_unit":"day","period":"before","time":"09:00","email":true,"sms":false,"whatsapp":false}},"BulkOperationResult":{"type":"object","description":"Common envelope of the bulk expiry endpoints. Each endpoint adds its own count and item fields. Ids that are\nmissing, outside your organization or otherwise not processed are listed in `errors`; the rest are applied.\n","required":["success"],"properties":{"success":{"type":"boolean"},"errors":{"type":"array","description":"Omitted when every id was processed.","items":{"$ref":"#/components/schemas/BulkItemError"}}}},"BulkItemError":{"type":"object","required":["id","error"],"properties":{"id":{"type":"string"},"error":{"type":"string","examples":["Expiry not found"]},"missing_required":{"type":"array","description":"bulkMarkExpiriesDone only - required checklist item ids not completed.","items":{"type":"string"}}}},"BulkAffectedExpiry":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}},"BulkExpiryIdsInput":{"type":"object","required":["expiryIds"],"properties":{"expiryIds":{"type":"array","minItems":1,"items":{"type":"string"}}}},"LoginRequest":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"},"password":{"type":"string","writeOnly":true,"description":"Account password. For `login_type: google`, may carry the Firebase ID token instead of `idToken`."},"login_type":{"type":"string","enum":["google"],"description":"Set to `google` to sign in with a Firebase ID token from Google sign-in."},"idToken":{"type":"string","writeOnly":true,"description":"Firebase ID token (Google sign-in). Its email must match `email`."},"user_data":{"type":"object","description":"Optional Google profile hints used when a new account is auto-registered.","properties":{"displayName":{"type":"string"},"photoURL":{"type":"string"}}}}},"LoginTeamMembership":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"role":{"type":"string","description":"Team membership role (`admin` or `member`)."}}},"RegisterRequest":{"type":"object","required":["email","firstName","lastName"],"properties":{"email":{"type":"string","format":"email","description":"Business email (free email providers are rejected unless `googleUid` is sent)."},"password":{"type":"string","writeOnly":true,"description":"Required for email signups; must meet the password policy."},"firstName":{"type":"string"},"lastName":{"type":"string"},"displayName":{"type":"string"},"is_organization":{"type":"boolean"},"organization_name":{"type":"string","description":"Used when `is_organization` is true; otherwise the workspace is named after the user."},"photoURL":{"type":"string"},"timezone":{"type":"string","description":"IANA timezone; defaults to `America/New_York`."},"referral_code":{"type":"string"},"promo_code":{"type":"string","description":"Ignored when a valid `referral_code` is applied. Preview with checkPromoCode."},"appsumo_link_token":{"type":"string","description":"Links an AppSumo license (preview with checkAppSumoLicense)."},"recaptchaToken":{"type":"string","writeOnly":true},"googleUid":{"type":"string","description":"Firebase uid of an existing Google-authenticated account (send its ID token as the bearer token)."}}},"RegisterResponse":{"type":"object","required":["message","user"],"properties":{"message":{"type":"string"},"user":{"$ref":"#/components/schemas/User"}}},"CompleteInvitationRequest":{"type":"object","required":["invitationToken","email"],"properties":{"invitationToken":{"type":"string","writeOnly":true,"description":"Invitation token from the invite link."},"email":{"type":"string","format":"email","description":"Must match the invited email."},"firstName":{"type":"string"},"lastName":{"type":"string"},"password":{"type":"string","writeOnly":true,"description":"Required unless `userId` is sent; must meet the password policy."},"userId":{"type":"string","description":"Legacy - uid of an Auth account created client-side (requires its ID token as the bearer token)."}}},"CompleteInvitationResult":{"type":"object","required":["success","userId","organizationId"],"properties":{"success":{"type":"boolean"},"userId":{"type":"string"},"organizationId":{"type":"string"},"organizationName":{"type":"string"},"alreadyCompleted":{"type":"boolean","description":"Present (true) when the user had already joined."}}},"EmailVerificationStatus":{"type":"object","properties":{"email_verified":{"type":"boolean","description":"Omitted for legacy accounts (treat as verified)."},"email":{"type":"string","format":"email"}}},"UserUpdateInput":{"type":"object","description":"Only these fields are written; others are ignored.","properties":{"firstName":{"type":"string"},"lastName":{"type":"string"},"displayName":{"type":"string"},"phone":{"type":"string"},"address":{"description":"String or object."},"avatar":{"type":"string"},"bio":{"type":"string"},"timezone":{"type":"string"},"locale":{"type":"string"},"dashboardColumnPrefs":{"type":"object","additionalProperties":true},"notificationPrefs":{"type":"object","additionalProperties":true},"uiPrefs":{"type":"object","additionalProperties":true}}},"UserList":{"type":"object","required":["users"],"properties":{"users":{"type":"array","items":{"$ref":"#/components/schemas/User"}}}},"MarkExpiryDoneInput":{"type":"object","properties":{"id":{"type":"string","description":"Expiry id (alternative to query `id`)."},"expiryId":{"type":"string","description":"Expiry id (alias of `id`)."},"closingNotes":{"type":"string"},"checklistCompleted":{"type":"array","description":"Ids of completed checklist items; must include every required item.","items":{"type":"string"}},"checklistCommentsById":{"type":"object","description":"Checklist item id -> comment.","additionalProperties":{"type":"string"}}}},"MarkExpiryDoneResult":{"type":"object","required":["message","next_expiry"],"properties":{"message":{"type":"string"},"next_expiry":{"description":"The next occurrence created for a recurring expiry, else null.","oneOf":[{"type":"null"},{"type":"object","properties":{"next_expiry_id":{"type":"string"},"next_expiry_date":{"$ref":"#/components/schemas/IsoDate"},"mode":{"type":"string","enum":["renew_as_copy","renew_same"]},"occurrence_number":{"type":"integer"}}}]}}},"ChecklistRequiredError":{"type":"object","required":["error","missing_required"],"properties":{"error":{"type":"string"},"missing_required":{"type":"array","items":{"type":"string"}}}},"ReminderList":{"type":"object","required":["reminders"],"properties":{"reminders":{"type":"array","items":{"$ref":"#/components/schemas/Reminder"}}}},"UpcomingNotificationRecipient":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":"string"},"channels":{"type":"array","items":{"type":"string","enum":["email","sms","whatsapp"]}}}},"UpcomingNotification":{"type":"object","description":"The next scheduled reminder for one expiry.","properties":{"id":{"type":"string","description":"Expiry id."},"expiryName":{"type":"string"},"expiryType":{"type":"string"},"expiryDate":{"$ref":"#/components/schemas/IsoDate"},"nextNotification":{"$ref":"#/components/schemas/Timestamp"},"nextNotificationOrgTz":{"type":"string","description":"The send time expressed as organization-local wall time (serialized with a Z suffix)."},"reminderTime":{"type":"string","description":"Local send time `HH:mm`."},"timezone":{"type":"string","description":"Organization timezone."},"daysUntilExpiry":{"type":"integer"},"recipients":{"type":"array","items":{"$ref":"#/components/schemas/UpcomingNotificationRecipient"}},"notificationChannels":{"type":"array","items":{"type":"string","enum":["email","sms","whatsapp"]}},"priority":{"type":"string","enum":["critical","high","medium"]}}},"EarlyBirdStatus":{"type":"object","required":["eligible"],"properties":{"eligible":{"type":"boolean"},"reason":{"type":"string","description":"Why the organization is not eligible.","enum":["promo_active","referral_active","not_on_trial","no_created_at","window_expired"]},"discount_pct":{"type":"integer","examples":[30]},"discount_period":{"type":"string","enum":["yearly"]},"expires_at":{"$ref":"#/components/schemas/Timestamp"}}},"PromoStatus":{"type":"object","required":["eligible"],"properties":{"eligible":{"type":"boolean"},"reason":{"type":"string","enum":["no_pending_promo","window_expired"]},"code":{"type":"string"},"discount_percentage":{"type":"number"},"expires_at":{"$ref":"#/components/schemas/Timestamp"}}},"PromoCodeCheck":{"type":"object","required":["valid"],"properties":{"valid":{"type":"boolean"},"reason":{"type":"string","enum":["missing_code","invalid_or_expired","error"]},"code":{"type":"string"},"discount_percentage":{"type":"number"},"expiry_window_hours":{"type":"number","description":"Hours after signup during which the discount can be redeemed."}}},"AppSumoLicenseCheck":{"type":"object","required":["valid"],"properties":{"valid":{"type":"boolean"},"reason":{"type":"string","enum":["missing_link_token","not_found","already_linked","expired","deactivated","error"]},"tier":{"type":"integer"},"plan_key":{"type":"string"},"plan_name":{"type":"string"},"limits":{"type":"object","additionalProperties":{"type":"number"},"properties":{"expiries":{"type":"number"},"categories":{"type":"number"},"users":{"type":"number"},"teams":{"type":"number"},"contacts":{"type":"number"},"workflows":{"type":"number"},"workflow_runs":{"type":"number"},"collection_templates":{"type":"number"},"collection_requests":{"type":"number"}}}}},"ReferralCode":{"type":"object","description":"A referral code document (commercial terms are set by ExpiryEdge).","additionalProperties":true,"properties":{"referral_code":{"type":"string","readOnly":true},"referee_name":{"type":"string"},"referee_email":{"type":["string","null"]},"referee_user_id":{"type":["string","null"],"readOnly":true},"active":{"type":"boolean"},"current_uses":{"type":"integer","readOnly":true},"max_uses":{"type":["integer","null"]},"total_signups":{"type":"integer","readOnly":true},"user_discount_percentage":{"type":"number"},"user_discount_period":{"type":["string","null"]},"user_discount_duration":{"type":["integer","null"]},"user_free_trial_days":{"type":["integer","null"]},"referrer_commission_percentage":{"type":"number"},"referrer_commission_period":{"type":["string","null"]},"expires_at":{"type":["string","null"]},"created_at":{"$ref":"#/components/schemas/Timestamp","readOnly":true},"updated_at":{"$ref":"#/components/schemas/Timestamp","readOnly":true}}},"ReferralCodeDetails":{"type":"object","required":["code_info","organizations","total_organizations"],"properties":{"code_info":{"$ref":"#/components/schemas/ReferralCode"},"organizations":{"type":"array","items":{"type":"object","properties":{"organization_id":{"type":"string"},"organization_name":{"type":"string"},"created_at":{"type":"string"},"referred_at":{"type":["string","null"]},"subscription_plan":{"type":"string"},"subscription_status":{"type":"string"}}}},"total_organizations":{"type":"integer"}}},"ReferralRevenuePeriod":{"type":"object","properties":{"year":{"type":"integer"},"month":{"type":"integer","description":"1-12 (omitted in `revenueByYear`)."},"revenue":{"type":"number"},"commission":{"type":"number"},"payments":{"type":"integer"},"label":{"type":"string","description":"For example `Sep 2026` (`revenueByPeriod` only)."}}},"MyReferralSummary":{"type":"object","required":["hasReferralCode"],"properties":{"hasReferralCode":{"type":"boolean"},"referralCode":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/ReferralCode"}]},"referredOrgs":{"type":"array","items":{"type":"object","additionalProperties":true,"properties":{"organization_id":{"type":"string"},"organization_name":{"type":"string"},"organization_domain":{"type":"string"},"signed_up_by":{"type":["object","null"],"properties":{"uid":{"type":"string"},"name":{"type":["string","null"]},"email":{"type":["string","null"]},"created_at":{"type":["string","null"]}}},"subscription_plan":{"type":"string"},"subscription_status":{"type":"string"},"referred_at":{"type":"string"},"discount_percentage":{"type":"number"},"discount_cycles_remaining":{"type":["integer","null"]},"referrer_commission_percentage":{"type":"number"},"last_payment_amount":{"type":"number"},"last_payment_date":{"type":["string","null"],"format":"date-time","description":"ISO 8601 date-time string (UTC)."},"events":{"type":"array","items":{"type":"object","additionalProperties":true}},"org_revenue":{"type":"number"},"org_commission":{"type":"number"}}}},"commissions":{"type":"array","description":"Commission log entries, newest first (max 200).","items":{"type":"object","additionalProperties":true}},"events":{"type":"array","description":"Referral events (payments, refunds, cancellations), newest first.","items":{"type":"object","additionalProperties":true}},"revenueByPeriod":{"type":"array","items":{"$ref":"#/components/schemas/ReferralRevenuePeriod"}},"revenueByYear":{"type":"array","items":{"$ref":"#/components/schemas/ReferralRevenuePeriod"}},"stats":{"type":"object","properties":{"totalReferrals":{"type":"integer"},"activeSubscribers":{"type":"integer"},"totalRevenueGenerated":{"type":"number"},"totalRefunded":{"type":"number"},"netRevenue":{"type":"number"},"totalCommissionEarned":{"type":"number"},"pendingCommission":{"type":"number"},"conversionRate":{"type":"number","description":"Percent of referred organizations with an active subscription."},"commissionPercentage":{"type":"number"}}}}},"BillingFirestoreTimestamp":{"type":"string","format":"date-time","description":"ISO 8601 date-time string (UTC). Formerly a raw Firestore Timestamp."},"Subscription":{"type":"object","description":"A plan purchase record (an \"order\"), written by the Stripe webhook each time a plan subscription is created.\nTimestamps are ISO 8601 date-time strings (UTC).","additionalProperties":true,"properties":{"id":{"type":"string","readOnly":true},"user_id":{"type":"string","readOnly":true,"description":"User who bought the plan."},"organization_id":{"type":"string","readOnly":true},"plan_type":{"type":"string","readOnly":true,"description":"Stripe product id of the plan."},"subscription_period":{"type":"integer","readOnly":true,"description":"Billing interval count."},"period_unit":{"type":"string","enum":["day","week","month","year"],"readOnly":true},"subscription_id":{"type":"string","readOnly":true,"description":"Stripe subscription id (`sub_...`)."},"currency":{"type":"string","readOnly":true,"description":"Lowercase ISO currency code."},"total_amount":{"type":"number","readOnly":true,"description":"Amount in major currency units (e.g. dollars)."},"net_amount":{"type":"number","readOnly":true},"status":{"type":"string","readOnly":true,"examples":["completed"]},"order_date":{"allOf":[{"$ref":"#/components/schemas/BillingFirestoreTimestamp"}],"readOnly":true},"createdAt":{"allOf":[{"$ref":"#/components/schemas/BillingFirestoreTimestamp"}],"readOnly":true}},"example":{"id":"ord_3Jk8Wq","user_id":"u_71bXq","organization_id":"org_northwind","plan_type":"prod_SJbWzGAm137eGN","subscription_period":1,"period_unit":"month","subscription_id":"sub_1QfT8sLm2Northwind","currency":"usd","total_amount":29,"net_amount":29,"status":"completed","order_date":"2026-09-27T14:05:00.000Z","createdAt":"2026-09-27T14:05:00.000Z"}},"PricingPlan":{"type":"object","description":"A document from the `subscription_plans` collection. Fields beyond `id` and `plan_type` are whatever the plan document stores.","additionalProperties":true,"properties":{"id":{"type":"string","readOnly":true},"plan_type":{"type":"string","description":"Billing cadence the plan belongs to.","examples":["monthly","yearly"]},"name":{"type":"string"},"price":{"type":"number","description":"Price in major currency units."},"currency":{"type":"string"},"subscription_plan_id":{"type":"string","description":"Stripe product id."},"features":{"type":"array","items":{"type":"string"}}},"example":{"id":"plan_personal_lite_monthly","plan_type":"monthly","name":"Personal Lite","price":29,"currency":"USD","subscription_plan_id":"prod_SJbWzGAm137eGN","features":["basic_tracking","email_notifications","recurring_expiries"]}},"CreateSubscriptionInput":{"type":"object","required":["subscription_plan_id"],"properties":{"subscription_plan_id":{"type":"string","description":"Stripe product id of a self-serve plan. Unknown products are rejected."},"frequency":{"type":"string","description":"`Month` or `Year` (`Yearly`/`Annual` also accepted). Default `Month`.","default":"Month"},"price":{"type":"number","description":"Amount the client displayed, in USD. Must match a server-side price for the plan and interval, otherwise 400 INVALID_PRICE."},"apply_referral_discount":{"type":"boolean","description":"When `price` is sent, apply the org referral discount on top of it."},"subscription_period":{"type":"integer","description":"Metadata only."},"billing_cycles":{"type":["integer","string"],"description":"Metadata only."},"period_unit":{"type":"string","description":"Metadata only (max 20 chars)."},"customer_email":{"type":"string","format":"email","description":"Used only when the token carries no email."},"withdrawal_consent":{"type":"boolean","description":"EU withdrawal-right consent, recorded on the Stripe session."},"withdrawal_consent_at":{"type":["string","null"],"format":"date-time"},"user_id":{"type":"string","deprecated":true,"description":"Ignored; the caller is always the purchaser."},"currency":{"type":"string","deprecated":true,"description":"Ignored; plans are always charged in USD."}}},"CheckoutSessionResult":{"type":"object","required":["status","sessionId","url"],"properties":{"status":{"type":"string","const":"success"},"sessionId":{"type":"string","description":"Stripe Checkout Session id."},"url":{"type":"string","format":"uri","description":"Redirect the browser here to pay."},"message":{"type":"string"}}},"SubscriptionUpgradeResult":{"type":"object","required":["status","type","subscription_id"],"properties":{"status":{"type":"string","const":"success"},"type":{"type":"string","const":"upgrade"},"subscription_id":{"type":"string"},"message":{"type":"string"}}},"BillingStatusError":{"type":"object","description":"Error shape used by the subscription handlers for their own validation errors.","required":["status","message"],"properties":{"status":{"type":"string","const":"error"},"code":{"type":"string","examples":["INVALID_PLAN","INVALID_PRICE"]},"message":{"type":"string"}}},"CancelSubscriptionInput":{"type":"object","properties":{"reason":{"type":"string","maxLength":500},"feedback":{"type":"string","maxLength":5000}}},"CancelSubscriptionResult":{"type":"object","required":["status","message","cancel_at"],"properties":{"status":{"type":"string","const":"success"},"message":{"type":"string"},"cancel_at":{"$ref":"#/components/schemas/Timestamp"}}},"BillingInvoice":{"type":"object","description":"A Stripe invoice (`type: invoice`) or a refund on a charge (`type: refund`). Dates are Unix epoch milliseconds.","required":["id","type","date","amount","currency","status"],"properties":{"id":{"type":"string"},"type":{"type":"string","enum":["invoice","refund"]},"number":{"type":"string"},"date":{"type":"integer","description":"Epoch milliseconds."},"periodStart":{"type":"integer","description":"Epoch milliseconds (invoices only)."},"periodEnd":{"type":"integer","description":"Epoch milliseconds (invoices only)."},"amount":{"type":"number","description":"Amount paid in major units; negative for refunds."},"amountDue":{"type":"number","description":"Invoices only."},"currency":{"type":"string"},"status":{"type":"string","description":"Stripe invoice or refund status."},"reason":{"type":["string","null"],"description":"Refunds only."},"chargeId":{"type":"string","description":"Refunds only."},"description":{"type":"string"},"pdfUrl":{"type":["string","null"],"format":"uri"},"hostedUrl":{"type":["string","null"],"format":"uri"}}},"BillingPaymentMethod":{"type":"object","required":["id","brand","last4","default"],"properties":{"id":{"type":"string"},"brand":{"type":"string"},"last4":{"type":"string"},"exp_month":{"type":["integer","null"]},"exp_year":{"type":["integer","null"]},"funding":{"type":["string","null"],"examples":["credit"]},"country":{"type":["string","null"]},"wallet":{"type":["string","null"],"examples":["apple_pay"]},"default":{"type":"boolean"}}},"BillingPrice":{"type":"object","properties":{"unit_amount":{"type":"integer","description":"Smallest currency unit (cents)."},"currency":{"type":"string"},"display":{"type":"string","description":"Major units, two decimals."}}},"AddonKey":{"type":"string","enum":["expiries","teams","categories","email_templates","custom_field_templates","workflows","workflow_runs","collection_templates","collection_requests"]},"AddonItemState":{"type":"object","properties":{"label":{"type":"string"},"unit_size":{"type":"integer","description":"Capacity granted per purchased unit."},"quantity":{"type":"integer","description":"Units purchased (0 unless the add-ons subscription is active)."},"base_limit":{"type":["number","null"],"description":"Plan limit without add-ons."},"limit":{"type":["number","null"],"description":"Effective limit including add-ons."},"usage":{"type":["number","null"]},"prices":{"type":"object","properties":{"month":{"$ref":"#/components/schemas/BillingPrice"},"year":{"$ref":"#/components/schemas/BillingPrice"}}}}},"AddonsState":{"type":"object","properties":{"period":{"type":"string","enum":["month","year"]},"status":{"type":"string","description":"Stripe status of the add-ons subscription, or `none`."},"cancel_at_period_end":{"type":"boolean"},"pending":{"type":["object","null"],"additionalProperties":{"type":"integer"},"description":"Quantities awaiting Checkout completion."},"has_subscription":{"type":"boolean"},"items":{"type":"object","description":"Keyed by add-on key.","additionalProperties":{"$ref":"#/components/schemas/AddonItemState"}},"sms_credits":{"type":"object","properties":{"block_size":{"type":"integer","description":"Credits per block."},"balance":{"type":"integer","description":"Rollover add-on credits."},"total_purchased":{"type":"integer"},"plan_balance":{"type":"integer","description":"Plan credits (refresh monthly)."},"price":{"oneOf":[{"$ref":"#/components/schemas/BillingPrice"},{"type":"null"}]}}}}},"AddonQuantities":{"type":"object","description":"Map of add-on key to number of units (0-100).","propertyNames":{"$ref":"#/components/schemas/AddonKey"},"additionalProperties":{"type":"integer","minimum":0,"maximum":100}},"StripeCheckoutRedirect":{"type":"object","required":["url","sessionId"],"properties":{"url":{"type":"string","format":"uri"},"sessionId":{"type":"string"}}},"AdditionalLicensesState":{"type":"object","properties":{"planName":{"type":"string"},"isTrial":{"type":"boolean"},"baseBillingPeriod":{"type":"string","enum":["month","year"]},"allowedPeriods":{"type":"array","items":{"type":"string","enum":["month","year"]},"description":"Empty on Trial; yearly plans can only buy yearly seats."},"prices":{"type":"object","properties":{"month":{"$ref":"#/components/schemas/BillingPrice"},"year":{"$ref":"#/components/schemas/BillingPrice"}}},"current":{"type":"object","properties":{"count":{"type":"integer"},"period":{"type":"string","enum":["month","year"]},"status":{"type":"string"},"stripe_subscription_id":{"type":["string","null"]},"last_invoice_at":{"type":["string","null"]},"next_invoice_at":{"oneOf":[{"$ref":"#/components/schemas/Timestamp"},{"type":"null"}]},"cancel_at_period_end":{"type":"boolean"}}},"seats":{"type":"object","properties":{"active_users":{"type":"integer"},"base_user_limit":{"type":"integer"},"effective_limit":{"type":"integer"},"min_allowed_extra":{"type":"integer","description":"Lowest additional-seat count allowed without removing members."}}}}},"BillingCancelResult":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"status":{"type":"string","description":"Stripe subscription status."},"cancel_at_period_end":{"type":"boolean","const":true}}},"BillingConflictError":{"type":"object","description":"Error with a stable `code` and extra context fields.","required":["error"],"additionalProperties":true,"properties":{"error":{"type":"string"},"code":{"type":"string"}}},"IntegrationProviderId":{"type":"string","enum":["asana","clickup"]},"IntegrationSummary":{"type":"object","properties":{"id":{"$ref":"#/components/schemas/IntegrationProviderId"},"name":{"type":"string"},"connected":{"type":"boolean"},"enabled":{"type":"boolean"}}},"IntegrationStatus":{"type":"object","description":"Connection status. When connected, provider fields are added: Asana `asana_user_name`, `asana_user_email`, `workspaces`; ClickUp `clickup_user_id`, `clickup_user_name`, `clickup_user_email`, `teams`. `connected_at` is an ISO 8601 date-time string (UTC).","required":["connected"],"additionalProperties":true,"properties":{"connected":{"type":"boolean"},"enabled":{"type":"boolean"},"connected_at":{"$ref":"#/components/schemas/BillingFirestoreTimestamp"}}},"IntegrationUnknownProviderError":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"available":{"type":"array","items":{"type":"string"}}}},"IntegrationTaskLink":{"type":"object","properties":{"gid":{"type":"string","description":"Asana task gid."},"id":{"type":"string","description":"ClickUp task id."},"name":{"type":"string"},"url":{"type":"string","format":"uri"}}},"IntegrationSyncRunResult":{"type":"object","properties":{"success":{"type":"boolean","const":true},"created":{"type":"integer"},"updated":{"type":"integer"},"skipped":{"type":"integer"},"errors":{"type":"integer"}}},"AsanaOrgSettings":{"type":"object","properties":{"connected":{"type":"boolean"},"default_workspace_gid":{"type":["string","null"]},"default_project_gid":{"type":["string","null"]},"sync_on_create":{"type":"boolean"},"sync_on_update":{"type":"boolean"},"sync_from_asana":{"type":"boolean"},"enabled":{"type":"boolean"}}},"ClickupOrgSettings":{"type":"object","properties":{"connected":{"type":"boolean"},"default_team_id":{"type":["string","null"]},"default_space_id":{"type":["string","null"]},"default_folder_id":{"type":["string","null"]},"default_list_id":{"type":["string","null"]},"sync_on_create":{"type":"boolean"},"sync_on_update":{"type":"boolean"},"sync_from_clickup":{"type":"boolean"},"enabled":{"type":"boolean"}}},"ComplianceClientInput":{"type":"object","description":"Writable client fields (camelCase). Unknown keys are ignored. String fields are trimmed.","properties":{"name":{"type":"string","description":"Required on create; cannot be blank on update."},"primaryContactName":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"},"website":{"type":"string"},"address":{"type":"string"},"industry":{"type":"string"},"status":{"type":"string","enum":["Active","Inactive"],"description":"Any other value is stored as `Active`."},"notes":{"type":"string"},"customFields":{"type":"object","description":"Free-form key/value pairs. Keys are trimmed; values are stored as strings.","additionalProperties":{"type":"string"}}},"example":{"name":"Northwind Dental","primaryContactName":"Priya Shah","email":"priya@northwinddental.example","phone":"+1 555 0142","website":"https://northwinddental.example","address":"12 Harbor Way, Portland, OR","industry":"Healthcare","status":"Active","notes":"Annual license audit in October.","customFields":{"account_manager":"Dana Lee"}}},"ComplianceClient":{"description":"A client in the Compliance Documents module.","allOf":[{"$ref":"#/components/schemas/ComplianceClientInput"},{"type":"object","properties":{"id":{"type":"string","readOnly":true},"isArchived":{"type":"boolean","readOnly":true},"archivedAt":{"oneOf":[{"$ref":"#/components/schemas/Timestamp"},{"type":"null"}],"readOnly":true},"organization_id":{"type":"string","readOnly":true},"user_id":{"type":"string","readOnly":true,"description":"Creator user id."},"createdAt":{"allOf":[{"$ref":"#/components/schemas/Timestamp"}],"readOnly":true},"updatedAt":{"allOf":[{"$ref":"#/components/schemas/Timestamp"}],"readOnly":true}}}],"example":{"id":"cc_7Rt2vQ","name":"Northwind Dental","primaryContactName":"Priya Shah","email":"priya@northwinddental.example","phone":"+1 555 0142","website":"https://northwinddental.example","address":"12 Harbor Way, Portland, OR","industry":"Healthcare","status":"Active","notes":"Annual license audit in October.","customFields":{"account_manager":"Dana Lee"},"isArchived":false,"archivedAt":null,"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}},"ComplianceProjectInput":{"type":"object","description":"Writable project fields (camelCase). Unknown keys are ignored.","properties":{"name":{"type":"string","description":"Required on create; cannot be blank on update."},"clientId":{"type":"string","description":"Compliance client id in your organization. Required on create."},"status":{"type":"string","enum":["Not Started","In Progress","On Hold","Completed","Cancelled"],"description":"Any other value is stored as `Not Started`."},"startDate":{"type":"string","description":"Stored as sent (not validated); the web app sends `YYYY-MM-DD`. Empty string when unset."},"dueDate":{"type":"string","description":"Stored as sent (not validated); the web app sends `YYYY-MM-DD`. Empty string when unset."},"description":{"type":"string"},"owner":{"type":"string","description":"Free-text owner name."},"customFields":{"type":"object","description":"Free-form key/value pairs. Keys are trimmed; values are stored as strings.","additionalProperties":{"type":"string"}}},"example":{"name":"Northwind Dental - 2026 License Renewals","clientId":"cc_7Rt2vQ","status":"In Progress","startDate":"2026-09-01","dueDate":"2026-10-15","description":"Collect and renew all state dental licenses.","owner":"Priya Shah","customFields":{"board":"State Board of Dentistry"}}},"ComplianceProject":{"description":"A project belonging to a compliance client.","allOf":[{"$ref":"#/components/schemas/ComplianceProjectInput"},{"type":"object","properties":{"id":{"type":"string","readOnly":true},"isArchived":{"type":"boolean","readOnly":true},"archivedAt":{"oneOf":[{"$ref":"#/components/schemas/Timestamp"},{"type":"null"}],"readOnly":true},"organization_id":{"type":"string","readOnly":true},"user_id":{"type":"string","readOnly":true},"createdAt":{"allOf":[{"$ref":"#/components/schemas/Timestamp"}],"readOnly":true},"updatedAt":{"allOf":[{"$ref":"#/components/schemas/Timestamp"}],"readOnly":true}}}],"example":{"id":"cp_3Mn8wX","name":"Northwind Dental - 2026 License Renewals","clientId":"cc_7Rt2vQ","status":"In Progress","startDate":"2026-09-01","dueDate":"2026-10-15","description":"Collect and renew all state dental licenses.","owner":"Priya Shah","customFields":{"board":"State Board of Dentistry"},"isArchived":false,"archivedAt":null,"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}},"ComplianceInput":{"type":"object","description":"Writable compliance catalog fields (camelCase). Unknown keys are ignored.","properties":{"name":{"type":"string","description":"Required on create; cannot be blank on update."},"description":{"type":"string"},"category":{"type":"string","enum":["Data Privacy","Safety","Financial","Environmental","Labor","Quality","Security","Regulatory","Other"],"description":"Any other value is stored as `Other`."},"customCategory":{"type":"string","description":"Only kept when `category` is `Other`."},"renewalFrequency":{"type":"string","enum":["One-Time","Monthly","Quarterly","Annually","Biennially"],"description":"Any other value is stored as `One-Time`."},"notes":{"type":"string"},"status":{"type":"string","enum":["Active","Inactive"],"description":"Any other value is stored as `Active`."},"customFields":{"type":"object","description":"Free-form key/value pairs. Keys are trimmed; values are stored as strings.","additionalProperties":{"type":"string"}}},"example":{"name":"HIPAA Security Risk Assessment","description":"Annual security risk analysis required under the HIPAA Security Rule.","category":"Data Privacy","renewalFrequency":"Annually","notes":"Keep the signed report for six years.","status":"Active","customFields":{"regulator":"HHS OCR"}}},"Compliance":{"description":"A compliance requirement in the organization catalog.","allOf":[{"$ref":"#/components/schemas/ComplianceInput"},{"type":"object","properties":{"id":{"type":"string","readOnly":true},"isArchived":{"type":"boolean","readOnly":true},"archivedAt":{"oneOf":[{"$ref":"#/components/schemas/Timestamp"},{"type":"null"}],"readOnly":true},"organization_id":{"type":"string","readOnly":true},"user_id":{"type":"string","readOnly":true},"createdAt":{"allOf":[{"$ref":"#/components/schemas/Timestamp"}],"readOnly":true},"updatedAt":{"allOf":[{"$ref":"#/components/schemas/Timestamp"}],"readOnly":true}}}],"example":{"id":"cm_9Pq4zR","name":"HIPAA Security Risk Assessment","description":"Annual security risk analysis required under the HIPAA Security Rule.","category":"Data Privacy","customCategory":"","renewalFrequency":"Annually","notes":"Keep the signed report for six years.","status":"Active","customFields":{"regulator":"HHS OCR"},"isArchived":false,"archivedAt":null,"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}},"CompliancePortalInput":{"type":"object","description":"Writable portal fields.","properties":{"name":{"type":"string","description":"Required on create."},"description":{"type":"string"},"client_name":{"type":"string"},"client_email":{"type":"string"},"expiry_ids":{"type":"array","maxItems":1000,"description":"Expiry ids shown on the portal. Every id must belong to your organization (400 otherwise); duplicates are removed.","items":{"type":"string"}},"password":{"type":"string","writeOnly":true,"description":"Optional viewer password. On update, an empty string removes the password. Never returned."},"link_expires_at":{"oneOf":[{"$ref":"#/components/schemas/Timestamp"},{"type":"null"}],"description":"After this time viewPortal returns 410."}},"example":{"name":"Northwind Dental - Vendor Compliance","description":"Current licenses and insurance certificates for Northwind Dental.","client_name":"Northwind Dental","client_email":"priya@northwinddental.example","expiry_ids":["exp_4Tq9sLm2","exp_8Jd3kQw1"],"password":"harbor-2026","link_expires_at":"2026-12-31T23:59:59.000Z"}},"CompliancePortal":{"description":"A shareable, read-only compliance portal. The password hash is never returned.","allOf":[{"$ref":"#/components/schemas/CompliancePortalInput"},{"type":"object","properties":{"id":{"type":"string","readOnly":true},"organization_id":{"type":"string","readOnly":true},"created_by":{"type":"string","readOnly":true},"access_token":{"type":"string","readOnly":true,"description":"Public portal token; pass it to viewPortal as `token`."},"has_password":{"type":"boolean","readOnly":true},"is_active":{"type":"boolean","description":"Inactive portals return 404 from viewPortal."},"view_count":{"type":"integer","readOnly":true},"last_viewed_at":{"oneOf":[{"$ref":"#/components/schemas/Timestamp"},{"type":"null"}],"readOnly":true},"created_at":{"allOf":[{"$ref":"#/components/schemas/Timestamp"}],"readOnly":true},"updated_at":{"allOf":[{"$ref":"#/components/schemas/Timestamp"}],"readOnly":true}}}],"example":{"id":"prt_5Vb1nK","name":"Northwind Dental - Vendor Compliance","description":"Current licenses and insurance certificates for Northwind Dental.","client_name":"Northwind Dental","client_email":"priya@northwinddental.example","expiry_ids":["exp_4Tq9sLm2","exp_8Jd3kQw1"],"link_expires_at":"2026-12-31T23:59:59.000Z","organization_id":"org_northwind","created_by":"u_71bXq","access_token":"0f5c2a8e-6d4b-4e3a-9c1f-7b2d8e9a3c41","has_password":true,"is_active":true,"view_count":12,"last_viewed_at":"2026-09-26T09:12:00.000Z","created_at":"2026-09-01T10:00:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}},"PortalPublicExpiry":{"type":"object","description":"The safe subset of an expiry shown on a public portal.","properties":{"name":{"type":"string"},"document_type":{"type":["string","null"]},"expiry_date":{"$ref":"#/components/schemas/IsoDate"},"start_date":{"oneOf":[{"$ref":"#/components/schemas/IsoDate"},{"type":"null"}]},"state":{"type":["string","null"]},"is_done":{"type":["boolean","null"]},"priority":{"type":["string","null"]},"vendor":{"type":["string","null"]},"issuing_authority":{"type":["string","null"]},"reference_number":{"type":["string","null"]},"share_token":{"type":["string","null"],"description":"Set only when the expiry itself is publicly shared (viewExpiry)."},"days_left":{"type":["integer","null"]},"status":{"type":"string","enum":["active","expired","completed"]}}},"PortalView":{"type":"object","description":"Result of opening a public compliance portal.","properties":{"success":{"type":"boolean"},"portal":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"client_name":{"type":"string"}}},"summary":{"type":"object","properties":{"total":{"type":"integer"},"compliant":{"type":"integer","description":"Active plus completed records."},"expired":{"type":"integer"},"completed":{"type":"integer"},"expiring_soon":{"type":"integer","description":"Active records due within 30 days."},"compliance_rate":{"type":"integer","description":"Percent of records not expired (100 when empty)."}}},"expiries":{"type":"array","description":"Expired first, then by days_left ascending.","items":{"$ref":"#/components/schemas/PortalPublicExpiry"}},"generated_at":{"$ref":"#/components/schemas/Timestamp"}}},"PortalAnalytics":{"type":"object","properties":{"success":{"type":"boolean"},"totalViews":{"type":"integer"},"lastViewedAt":{"oneOf":[{"$ref":"#/components/schemas/Timestamp"},{"type":"null"}]},"recentViews":{"type":"array","description":"Up to 20 most recent views (from the latest 100 logged).","items":{"type":"object","properties":{"portal_id":{"type":"string"},"organization_id":{"type":"string"},"viewed_at":{"$ref":"#/components/schemas/Timestamp"},"ip":{"type":"string","description":"Viewer IP (X-Forwarded-For when present)."}}}},"viewsByDay":{"type":"object","description":"`YYYY-MM-DD` -> view count, over the latest 100 logged views.","additionalProperties":{"type":"integer"}}}},"IndustryTemplate":{"type":"object","description":"A global, read-only industry template. `template_data` maps field names to default values and is copied into an expiry as `custom_columns`.","properties":{"id":{"type":"string","readOnly":true},"industry_name":{"type":"string"},"template_name":{"type":"string"},"template_data":{"type":"object","additionalProperties":true},"created_at":{"allOf":[{"$ref":"#/components/schemas/Timestamp"}],"readOnly":true},"updated_at":{"allOf":[{"$ref":"#/components/schemas/Timestamp"}],"readOnly":true}},"example":{"id":"ind_dental_license","industry_name":"Healthcare","template_name":"Dental License","template_data":{"License Number":"","Issuing Board":"State Board of Dentistry","CE Hours Required":""},"created_at":"2026-01-10T09:00:00.000Z","updated_at":"2026-06-02T11:30:00.000Z"}},"TemplateField":{"type":"object","description":"One custom field in a custom expiry template.","required":["name","label","type"],"properties":{"name":{"type":"string","description":"Storage key in the expiry `template_data` map (the web app derives it from the label)."},"label":{"type":"string"},"type":{"type":"string","enum":["text","textarea","number","date","phone","email","url","select","checkbox","currency"]},"required":{"type":"boolean"},"description":{"type":"string","description":"Help text shown to users."},"defaultValue":{"description":"Default value for new expiries."},"options":{"type":"object","description":"Type-specific validation options.","properties":{"options":{"type":"array","items":{"type":"string"},"description":"Allowed values for `select`."},"min":{"type":"number"},"max":{"type":"number"},"maxLength":{"type":"integer"}}}}},"TemplateInput":{"type":"object","description":"Writable custom template fields.","properties":{"name":{"type":"string","description":"Unique (case-insensitive) among active templates in the organization."},"description":{"type":"string"},"fields":{"type":"array","items":{"$ref":"#/components/schemas/TemplateField"}},"icon":{"type":"string","description":"Icon name; defaults to `DocumentText`."},"color":{"type":"string","description":"Hex color; defaults to `#3b82f6`."}}},"Template":{"description":"A custom expiry template (typed custom fields) owned by the organization.","allOf":[{"$ref":"#/components/schemas/TemplateInput"},{"type":"object","properties":{"id":{"type":"string","readOnly":true},"created_by":{"type":"string","readOnly":true},"usage_count":{"type":"integer","readOnly":true},"archived":{"type":"boolean","readOnly":true,"description":"Set by archiveTemplate / unarchiveTemplate."},"archived_at":{"allOf":[{"$ref":"#/components/schemas/Timestamp"}],"readOnly":true},"archived_by":{"type":"string","readOnly":true},"created_at":{"allOf":[{"$ref":"#/components/schemas/Timestamp"}],"readOnly":true},"updated_at":{"allOf":[{"$ref":"#/components/schemas/Timestamp"}],"readOnly":true}}}],"example":{"id":"tpl_Lic42a","name":"Professional License","description":"Fields tracked for every state professional license.","icon":"DocumentText","color":"#3b82f6","fields":[{"name":"license_number","label":"License Number","type":"text","required":true},{"name":"ce_hours","label":"CE Hours","type":"number","required":false,"options":{"min":0}},{"name":"board","label":"Board","type":"select","required":false,"options":{"options":["Dental","Medical","Nursing"]}}],"created_by":"u_71bXq","usage_count":3,"archived":false,"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}},"EmailTemplateInput":{"type":"object","description":"Writable email template fields. Variables use `{{expiry.name}}` style placeholders (see emailTemplateVariables).","properties":{"name":{"type":"string","maxLength":120},"subject":{"type":"string","maxLength":200,"description":"Must not contain line breaks."},"body_html":{"type":"string","description":"HTML body, at most ~900 KB."},"type":{"type":"string","enum":["expiry_reminder"],"description":"Defaults to `expiry_reminder`."},"track_opens":{"type":"boolean","description":"Defaults to true on create; kept as-is on update when omitted."},"track_clicks":{"type":"boolean","description":"Defaults to true on create; kept as-is on update when omitted."}}},"EmailTemplate":{"description":"A custom reminder email template owned by the organization.","allOf":[{"$ref":"#/components/schemas/EmailTemplateInput"},{"type":"object","properties":{"id":{"type":"string","readOnly":true},"created_by":{"type":"string","readOnly":true},"updated_by":{"type":"string","readOnly":true},"created_at":{"allOf":[{"$ref":"#/components/schemas/Timestamp"}],"readOnly":true},"updated_at":{"allOf":[{"$ref":"#/components/schemas/Timestamp"}],"readOnly":true}}}],"example":{"id":"et_Qw81zd","name":"Friendly renewal reminder","subject":"Reminder: {{expiry.name}} {{expiry.status}}","body_html":"<p>Hi {{contact.firstName}},</p><p>{{expiry.name}} {{expiry.status}}. <a href=\"{{expiry.url}}\">View details</a>.</p>","type":"expiry_reminder","track_opens":true,"track_clicks":false,"created_by":"u_71bXq","updated_by":"u_71bXq","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}},"EmailTemplateEntitlement":{"type":"object","properties":{"allowed":{"type":"boolean","description":"Whether the plan includes custom email templates."},"planName":{"type":"string"},"expiresAt":{"type":"integer","description":"Internal cache expiry (epoch ms); ignore."}}},"EmailTemplateList":{"type":"object","properties":{"templates":{"type":"array","items":{"$ref":"#/components/schemas/EmailTemplate"},"description":"Newest update first. Unbounded."},"selected":{"type":"object","description":"Template type -> selected template id (the organization default).","additionalProperties":{"type":"string"}},"entitlement":{"$ref":"#/components/schemas/EmailTemplateEntitlement"},"usage":{"type":"object","description":"Template id -> number of expiry types using it as their override.","additionalProperties":{"type":"integer"}}}},"EmailTemplateSelection":{"type":"object","properties":{"selected":{"type":"object","description":"Template type -> selected template id after the change.","additionalProperties":{"type":"string"}}}},"EmailTemplateVariable":{"type":"object","properties":{"key":{"type":"string","description":"Placeholder path, e.g. `expiry.name` or `custom.license_number`."},"label":{"type":"string"},"description":{"type":"string"},"sample":{"description":"Sample value used in previews."}}},"EmailTemplateVariables":{"type":"object","properties":{"categories":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"icon":{"type":"string"},"description":{"type":"string"},"variables":{"type":"array","items":{"$ref":"#/components/schemas/EmailTemplateVariable"}}}}},"sample":{"type":"object","additionalProperties":true,"description":"Sample render context (category -> field -> value)."},"allKeys":{"type":"array","items":{"type":"string"}}}},"EmailTemplateRendered":{"type":"object","properties":{"subject":{"type":"string"},"html":{"type":"string"},"text":{"type":"string","description":"Plain-text version derived from the HTML."}}},"PlanUpgradeRequiredError":{"type":"object","description":"Returned with 402 when the plan does not include the feature.","properties":{"error":{"type":"string"},"code":{"type":"string","const":"PLAN_UPGRADE_REQUIRED"},"required_feature":{"type":"string"},"required_plans":{"type":"array","items":{"type":"string"}},"current_plan":{"type":"string"}}},"SendNowContactResult":{"type":"object","properties":{"contactId":{"type":"string"},"email":{"type":"string","description":"Empty when the contact was not found."},"status":{"type":"string","enum":["sent","failed","skipped"]},"reason":{"type":"string","description":"Present for failed and skipped."}}},"SendNowResult":{"type":"object","properties":{"attempted":{"type":"integer"},"succeeded":{"type":"integer"},"failed":{"type":"integer"},"skipped":{"type":"integer","description":"Contact missing, notifications disabled, no email / opt-out, or no email credits left."},"results":{"type":"array","items":{"$ref":"#/components/schemas/SendNowContactResult"}}}},"ContactInput":{"type":"object","description":"Writable contact fields (camelCase except the opt-in flags and `is_default`). On create, omitted fields\ndefault as noted; on update only fields present are changed. Unknown keys are ignored.\n","properties":{"firstName":{"type":"string"},"lastName":{"type":"string","description":"Send `''` when unknown (create stores it as given)."},"email":{"type":["string","null"],"format":"email","description":"Unique per organization (case-insensitive) on create."},"smsPhone":{"type":["string","null"],"description":"E.164 phone for SMS, e.g. `+15550123456`."},"whatsappPhone":{"type":["string","null"]},"contactType":{"type":["string","null"],"description":"Free-text category, e.g. `Client`, `Staff`, `Vendor`."},"jobTitle":{"type":["string","null"]},"timezone":{"type":"string","description":"IANA timezone. Defaults to `Europe/London` on create."},"contactGroup":{"type":["string","null"],"description":"Group name (not id). A new name creates the group."},"is_default":{"type":"boolean","description":"Default contact added to new expiries. Defaults to false. `isDefault` is also accepted on create."},"email_opt_in":{"type":"boolean","description":"Defaults to true."},"sms_opt_in":{"type":"boolean","description":"Defaults to false."},"whatsapp_opt_in":{"type":"boolean","description":"Defaults to false."},"sendNotifications":{"type":"boolean","description":"Master switch for reminders to this contact. Defaults to true."}},"example":{"firstName":"Priya","lastName":"Shah","email":"priya.shah@northwinddental.example","smsPhone":"+15550123456","jobTitle":"Practice Manager","contactGroup":"Licensing","timezone":"America/New_York","sendNotifications":true}},"Contact":{"description":"A contact as returned by the API (camelCase; stored snake_case).","allOf":[{"$ref":"#/components/schemas/ContactInput"},{"type":"object","properties":{"id":{"type":"string","readOnly":true},"user_id":{"type":"string","readOnly":true,"description":"Creator's user id."},"organization_id":{"type":"string","readOnly":true},"createdAt":{"$ref":"#/components/schemas/Timestamp","readOnly":true},"updatedAt":{"$ref":"#/components/schemas/Timestamp","readOnly":true}}}],"example":{"id":"c_8Hk2pQ","user_id":"u_71bXq","organization_id":"org_northwind","firstName":"Priya","lastName":"Shah","email":"priya.shah@northwinddental.example","smsPhone":"+15550123456","whatsappPhone":null,"contactType":"Staff","jobTitle":"Practice Manager","timezone":"America/New_York","contactGroup":"Licensing","is_default":true,"email_opt_in":true,"sms_opt_in":true,"whatsapp_opt_in":false,"sendNotifications":true,"createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}},"ContactListFilters":{"type":"object","properties":{"selectedTeam":{"type":"string","description":"Only contacts with this `team_id`; `0` means all."},"contactGroup":{"type":"string","description":"Group name."},"contactType":{"type":"string"},"ungrouped":{"type":"boolean","description":"Only contacts with no group (overrides `contactGroup`)."},"limit":{"type":"integer","minimum":1,"default":5000}}},"ContactSavedResponse":{"type":"object","required":["message","contact"],"properties":{"message":{"type":"string"},"contact":{"$ref":"#/components/schemas/Contact"}}},"DefaultContactFlags":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"Contact id in your organization."},"is_default":{"type":"boolean","description":"Set to true when omitted."},"email_opt_in":{"type":"boolean"},"sms_opt_in":{"type":"boolean"},"whatsapp_opt_in":{"type":"boolean"}}},"BulkUpdateDefaultContactsResult":{"type":"object","required":["message","updated","skipped"],"properties":{"message":{"type":"string"},"updated":{"type":"integer"},"skipped":{"type":"array","description":"Ids that were not updated (missing, other organization or invalid).","items":{"type":"string"}}}},"BulkUpdateContactsInput":{"type":"object","required":["contactIds","updates"],"properties":{"contactIds":{"type":"array","minItems":1,"items":{"type":"string"}},"updates":{"type":"object","description":"At least one supported field.","minProperties":1,"properties":{"contactGroup":{"type":["string","null"],"description":"Group name; empty or null clears the group."},"jobTitle":{"type":["string","null"]},"contactType":{"type":["string","null"]},"sendNotifications":{"type":"boolean"}}}}},"BulkIdError":{"type":"object","description":"A per-id failure in a bulk operation.","properties":{"id":{"type":"string"},"error":{"type":"string","examples":["Not found"]}}},"BulkUpdateContactsResult":{"type":"object","required":["success","updated_count","updated_ids"],"properties":{"success":{"type":"boolean"},"updated_count":{"type":"integer"},"updated_ids":{"type":"array","items":{"type":"string"}},"errors":{"type":"array","items":{"$ref":"#/components/schemas/BulkIdError"}}}},"BulkDeleteContactsResult":{"type":"object","required":["success","deleted_count","deleted_ids"],"properties":{"success":{"type":"boolean"},"deleted_count":{"type":"integer"},"deleted_ids":{"type":"array","items":{"type":"string"}},"errors":{"type":"array","items":{"$ref":"#/components/schemas/BulkIdError"}}}},"BulkImportContactsInput":{"type":"object","properties":{"contacts":{"type":"array","maxItems":1000,"description":"Rows keyed by `ContactInput` names or common header spellings (`first_name`, `First Name`, `surname`,\n`Email Address`, `phone`, `mobile`, `whatsapp`, `group`, `title`, `type`, `tz`, `enable_notification`).\n`sendNotifications` accepts yes/no/true/false/1/0.\n","items":{"type":"object","additionalProperties":true}},"records":{"type":"array","maxItems":1000,"description":"Alias of `contacts`.","items":{"type":"object","additionalProperties":true}},"consent":{"type":"boolean","description":"Confirms you have permission to notify these contacts (stored on each contact)."}}},"BulkImportContactsResult":{"type":"object","properties":{"success":{"type":"boolean"},"error":{"type":"string","description":"Present on 400/403."},"created":{"type":"integer"},"skippedDuplicates":{"type":"integer","description":"Rows whose email already exists or repeats in the file."},"skippedLimit":{"type":"integer","description":"Valid rows not imported because the plan's contact limit was reached."},"totalRows":{"type":"integer"},"current":{"type":"integer","description":"Present on 403."},"limit":{"description":"Plan contact limit, or `Unlimited`.","oneOf":[{"type":"integer"},{"type":"string"}]},"remaining":{"description":"Slots left after the import, or `Unlimited`.","oneOf":[{"type":"integer"},{"type":"string"}]},"errors":{"type":"array","description":"Human-readable row messages, e.g. `Row 3: Email is required`.","items":{"type":"string"}}}},"ContactExpiryCounts":{"type":"object","required":["counts"],"properties":{"counts":{"type":"object","description":"Contact id -> number of expiries listing it in `contacts`.","additionalProperties":{"type":"integer"}}}},"ContactGroupInput":{"type":"object","properties":{"name":{"type":"string","description":"Required on create. Unique per organization (case-insensitive)."},"color":{"type":"string","description":"Hex color; empty derives one from the name."},"sortOrder":{"type":"number"}}},"ContactGroup":{"type":"object","description":"An organization-wide contact group. Membership is the contact's `contactGroup` name, not an id.\n`count` is present only in getAllContactGroups.\n","properties":{"id":{"type":"string","readOnly":true},"organization_id":{"type":"string","readOnly":true},"name":{"type":"string"},"color":{"type":"string"},"sortOrder":{"type":"number"},"count":{"type":"integer","readOnly":true,"description":"Number of contacts in the group."},"createdAt":{"$ref":"#/components/schemas/Timestamp","readOnly":true},"updatedAt":{"$ref":"#/components/schemas/Timestamp","readOnly":true}},"example":{"id":"grp_5Nc1","organization_id":"org_northwind","name":"Licensing","color":"#1976d2","sortOrder":0,"count":4,"createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}},"ContactGroupList":{"type":"object","required":["groups","totalContacts","ungroupedCount"],"properties":{"groups":{"type":"array","items":{"$ref":"#/components/schemas/ContactGroup"}},"totalContacts":{"type":"integer"},"ungroupedCount":{"type":"integer"}}},"ContactGroupSavedResponse":{"type":"object","required":["message","group"],"properties":{"message":{"type":"string"},"group":{"$ref":"#/components/schemas/ContactGroup"}}},"Folder":{"type":"object","description":"A folder for organizing expiries. Folders nest via `parent_folder_id`.","properties":{"id":{"type":"string","readOnly":true},"name":{"type":"string","maxLength":255},"parent_folder_id":{"type":["string","null"],"description":"Parent folder id; null for a root folder."},"folder_path":{"type":"string","readOnly":true,"description":"Slash-separated path of names, e.g. `/Licenses/State Board`."},"user_id":{"type":"string","readOnly":true},"organization_id":{"type":"string","readOnly":true},"is_deleted":{"type":"boolean","readOnly":true},"expiry_count":{"type":"integer","readOnly":true,"description":"Non-archived expiries directly in this folder (getAllFolders and createFolder only)."},"children":{"type":"array","readOnly":true,"description":"Subfolders (getAllFolders only).","items":{"$ref":"#/components/schemas/Folder"}},"created_at":{"$ref":"#/components/schemas/Timestamp","readOnly":true},"updated_at":{"$ref":"#/components/schemas/Timestamp","readOnly":true}},"example":{"id":"fld_2Kp9","name":"Licenses","parent_folder_id":null,"folder_path":"/Licenses","user_id":"u_71bXq","organization_id":"org_northwind","is_deleted":false,"expiry_count":6,"children":[],"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}},"FolderTree":{"type":"object","required":["success","folders","total_count","root_count"],"properties":{"success":{"type":"boolean"},"folders":{"type":"array","description":"Root folders, each with nested `children`.","items":{"$ref":"#/components/schemas/Folder"}},"total_count":{"type":"integer","description":"All non-deleted folders."},"root_count":{"type":"integer"}}},"FolderSavedResponse":{"type":"object","required":["success","message","folder"],"properties":{"success":{"type":"boolean"},"message":{"type":"string"},"folder":{"$ref":"#/components/schemas/Folder"}}},"FolderDeletedResponse":{"type":"object","required":["success","message","deleted_subfolders"],"properties":{"success":{"type":"boolean"},"message":{"type":"string"},"deleted_subfolders":{"type":"integer"}}},"FolderNotEmptyError":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"message":{"type":"string"},"children_count":{"type":"integer"},"expiries_count":{"type":"integer"},"can_force_delete":{"type":"boolean"}}},"MoveToFolderInput":{"type":"object","description":"Send `expiry_ids` or `expiryId`. `folder_id` and `folderId` are aliases; null or omitted removes the expiries from any folder.","properties":{"expiry_ids":{"type":"array","minItems":1,"maxItems":1000,"items":{"type":"string"}},"expiryId":{"type":"string"},"folder_id":{"type":["string","null"]},"folderId":{"type":["string","null"]}}},"MoveToFolderResult":{"type":"object","required":["success","message","updated_count","failed_count","folder_id"],"properties":{"success":{"type":"boolean"},"message":{"type":"string"},"updated_count":{"type":"integer"},"failed_count":{"type":"integer"},"folder_id":{"type":["string","null"]}}},"DirectoryEntryInput":{"type":"object","description":"Writable directory entry fields (camelCase).","properties":{"name":{"type":"string","description":"Required on create."},"type":{"type":"string","description":"One of Person, Company, Vendor, Client, Partner, Asset, Property, Other. Defaults to `Person`.","examples":["Vendor"]},"customType":{"type":"string","description":"Custom type label; kept only when `type` is `Other`."},"phone":{"type":"string"},"email":{"type":"string"},"address":{"type":"string"},"notes":{"type":"string"},"typedFields":{"type":"object","description":"Type-specific fields as key -> value (e.g. `website`).","additionalProperties":true},"customFields":{"type":"array","description":"Free-form label/value pairs; items without a label are dropped.","items":{"type":"object","required":["label"],"properties":{"label":{"type":"string"},"value":{"type":"string"}}}}}},"DirectoryEntry":{"description":"A directory record (vendor, location, asset, person, ...) that expiries reference via `directory_entry_ids`.","allOf":[{"$ref":"#/components/schemas/DirectoryEntryInput"},{"type":"object","properties":{"id":{"type":"string","readOnly":true},"organization_id":{"type":"string","readOnly":true},"user_id":{"type":"string","readOnly":true,"description":"Creator's user id."},"createdAt":{"$ref":"#/components/schemas/Timestamp","readOnly":true},"updatedAt":{"$ref":"#/components/schemas/Timestamp","readOnly":true}}}],"example":{"id":"dir_6Hv3","name":"Contoso Legal","type":"Vendor","customType":"","phone":"+15550198765","email":"billing@contosolegal.example","address":"400 Market St, Springfield","notes":"Outside counsel for licensing.","typedFields":{"website":"https://contosolegal.example"},"customFields":[{"label":"Account number","value":"CL-2231"}],"organization_id":"org_northwind","user_id":"u_71bXq","createdAt":"2026-09-27T14:05:00.000Z","updatedAt":"2026-09-27T14:05:00.000Z"}},"DirectoryEntrySavedResponse":{"type":"object","required":["message","entry"],"properties":{"message":{"type":"string"},"entry":{"$ref":"#/components/schemas/DirectoryEntry"}}},"BulkImportDirectoryEntriesResult":{"type":"object","properties":{"success":{"type":"boolean"},"error":{"type":"string","description":"Present on 400."},"created":{"type":"integer"},"skippedDuplicates":{"type":"integer"},"totalRows":{"type":"integer"},"errors":{"type":"array","items":{"type":"string"}}}},"BulkAttachDirectoryResult":{"type":"object","required":["success","mode","attached_count","attached_items"],"properties":{"success":{"type":"boolean"},"mode":{"type":"string","enum":["replace","add"]},"attached_count":{"type":"integer"},"attached_items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}}},"errors":{"type":"array","items":{"$ref":"#/components/schemas/BulkIdError"}}}},"CollectionReferenceFile":{"type":"object","description":"Example file attached to a template field, as stored. On input send `{storagePath, originalFilename}` (from getCollectionTemplateReferenceUploadUrl); the server verifies the object and fills in type and size.","properties":{"storage_path":{"type":"string","readOnly":true},"original_filename":{"type":"string"},"content_type":{"type":"string","readOnly":true},"size_bytes":{"type":"integer","readOnly":true},"storagePath":{"type":"string","writeOnly":true},"originalFilename":{"type":"string","writeOnly":true}}},"CollectionTemplateField":{"type":"object","description":"One question. Field ids are unique across the whole template.","required":["type","label"],"properties":{"id":{"type":"string","pattern":"^[a-zA-Z0-9_-]+$","description":"Generated when missing or invalid."},"type":{"type":"string","enum":["short_text","long_text","number","date","dropdown","checkbox","file_upload","multi_select","radio","phone","address","signature","text_block"],"description":"`text_block` is display-only text. `signature` is a drawn PNG uploaded like a file."},"label":{"type":"string","maxLength":200},"required":{"type":"boolean","default":false},"help_text":{"type":"string","maxLength":500},"options":{"type":"array","minItems":2,"maxItems":50,"items":{"type":"string"},"description":"Required for `dropdown`, `multi_select` and `radio`."},"allowed_types":{"type":"array","items":{"type":"string","enum":["pdf","jpg","png","gif","webp","docx","doc","xlsx","xls","pptx","ppt","csv","txt"]},"description":"File extensions accepted by a `file_upload` field (at least one). Always `[png]` for `signature`."},"max_size_bytes":{"type":"integer","minimum":1,"maximum":524288000,"description":"Per-file limit for `file_upload` (default and max 500MB); 2MB for `signature`."},"reference_file":{"$ref":"#/components/schemas/CollectionReferenceFile"}}},"CollectionTemplateSection":{"type":"object","required":["fields"],"properties":{"id":{"type":"string"},"title":{"type":"string","maxLength":200},"order":{"type":"integer","readOnly":true},"description_html":{"type":"string","description":"Sanitized rich-text instructions (max ~20KB)."},"fields":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/CollectionTemplateField"}}}},"CollectionTemplatePage":{"type":"object","required":["sections"],"properties":{"id":{"type":"string"},"title":{"type":"string","maxLength":200},"order":{"type":"integer","readOnly":true},"description_html":{"type":"string","description":"Sanitized rich-text instructions (max ~20KB)."},"sections":{"type":"array","minItems":1,"maxItems":20,"items":{"$ref":"#/components/schemas/CollectionTemplateSection"}}}},"CollectionTemplateInput":{"type":"object","description":"Writable template fields. `name` and `pages` are required on create.","properties":{"name":{"type":"string","maxLength":200},"description":{"type":"string","maxLength":1000},"pages":{"type":"array","minItems":1,"maxItems":20,"items":{"$ref":"#/components/schemas/CollectionTemplatePage"},"description":"Max 50 fields across all pages."},"intro_email":{"type":["object","null"],"description":"Custom introduction for the request email; null uses the default text.","properties":{"body_html":{"type":"string","description":"Sanitized HTML, max ~20KB."}}}},"example":{"name":"New client onboarding - Northwind Dental","description":"Documents we need before the first engagement.","pages":[{"id":"p_company","title":"Company details","description_html":"<p>Tell us about your practice.</p>","sections":[{"id":"s_basics","title":"Basics","fields":[{"id":"fld_company_name","type":"short_text","label":"Legal company name","required":true},{"id":"fld_insurance","type":"file_upload","label":"Liability insurance certificate","required":true,"allowed_types":["pdf","jpg","png"],"max_size_bytes":26214400}]}]}],"intro_email":{"body_html":"<p>Hi, please upload the documents below by Friday.</p>"}}},"CollectionTemplate":{"description":"A Document Collection template (form definition).","allOf":[{"$ref":"#/components/schemas/CollectionTemplateInput"},{"type":"object","properties":{"id":{"type":"string","readOnly":true},"organization_id":{"type":"string","readOnly":true},"schema_version":{"type":"integer","examples":[2],"readOnly":true},"status":{"type":"string","enum":["active","archived","request_draft","request_sent"],"description":"`request_draft` / `request_sent` are one-off copies made by createCollectionRequestDraft.","readOnly":true},"created_by":{"type":"string","readOnly":true},"created_by_name":{"type":"string","readOnly":true},"created_at":{"$ref":"#/components/schemas/Timestamp","readOnly":true},"updated_at":{"$ref":"#/components/schemas/Timestamp","readOnly":true}}}],"example":{"id":"tpl_W9onboard","organization_id":"org_northwind","name":"New client onboarding - Northwind Dental","description":"Documents we need before the first engagement.","pages":[{"id":"p_company","title":"Company details","order":0,"description_html":"<p>Tell us about your practice.</p>","sections":[{"id":"s_basics","title":"Basics","order":0,"description_html":"","fields":[{"id":"fld_company_name","type":"short_text","label":"Legal company name","required":true,"help_text":""},{"id":"fld_entity","type":"dropdown","label":"Entity type","required":true,"help_text":"","options":["LLC","Corporation","Sole proprietor"]},{"id":"fld_insurance","type":"file_upload","label":"Liability insurance certificate","required":true,"help_text":"PDF or image, current policy year.","allowed_types":["pdf","jpg","png"],"max_size_bytes":26214400,"reference_file":{"storage_path":"organizations/org_northwind/collection-templates/tpl_W9onboard/fields/fld_insurance/reference-1b2c-sample.pdf","original_filename":"sample-certificate.pdf","content_type":"application/pdf","size_bytes":120331}}]}]}],"intro_email":{"body_html":"<p>Hi, please upload the documents below by Friday.</p>"},"schema_version":2,"status":"active","created_by":"u_71bXq","created_by_name":"Priya Shah","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}},"CollectionRequest":{"type":"object","description":"One recipient's request (one per contact per send). All fields are server-owned except `name` and `due_date` (updateCollectionRequest).","properties":{"id":{"type":"string","readOnly":true},"organization_id":{"type":"string","readOnly":true},"name":{"type":["string","null"]},"due_date":{"type":["string","null"],"format":"date-time","description":"Informational \"submit by\" date."},"template_id":{"type":"string","readOnly":true},"template_name":{"type":"string","readOnly":true},"contact_id":{"type":"string","readOnly":true},"contact_name":{"type":"string","readOnly":true},"contact_email":{"type":"string","format":"email","readOnly":true},"expiry_id":{"type":["string","null"],"readOnly":true},"group_id":{"type":["string","null"],"readOnly":true},"has_password":{"type":"boolean","readOnly":true},"expires_at":{"type":["string","null"],"format":"date-time","description":"When the link stops working; null means never.","readOnly":true},"status":{"type":"string","enum":["pending","viewed","completed","cancelled","expired"],"description":"After \"Request changes\" a completed request goes back to `pending`/`viewed`.","readOnly":true},"archived":{"type":"boolean","readOnly":true},"paused":{"type":"boolean","readOnly":true},"sent_at":{"type":"string","format":"date-time","readOnly":true},"viewed_at":{"type":["string","null"],"format":"date-time","readOnly":true},"completed_at":{"type":["string","null"],"format":"date-time","readOnly":true},"cancelled_at":{"type":["string","null"],"format":"date-time","readOnly":true},"resend_count":{"type":"integer","readOnly":true},"last_resent_at":{"type":["string","null"],"format":"date-time","readOnly":true},"draft_filled_count":{"type":["integer","null"],"description":"Fields filled in the recipient's autosaved draft.","readOnly":true},"draft_total_fields":{"type":["integer","null"],"readOnly":true},"draft_updated_at":{"type":["string","null"],"format":"date-time","readOnly":true},"next_reminder_at":{"type":["string","null"],"format":"date-time","readOnly":true},"reminders_sent_count":{"type":"integer","readOnly":true},"email_stats":{"type":["object","null"],"additionalProperties":true,"description":"Counters `<event>_count` and `last_<event>_at` per email event type, plus `last_event_at`.","readOnly":true},"reply_received":{"type":"boolean","readOnly":true},"reply_at":{"type":["string","null"],"format":"date-time","readOnly":true},"reply_snippet":{"type":["string","null"],"readOnly":true},"latest_review_status":{"type":["string","null"],"enum":["pending","approved","changes_requested",null],"readOnly":true},"review_approved_count":{"type":["integer","null"],"readOnly":true},"review_total_fields":{"type":["integer","null"],"readOnly":true},"completed_field_count":{"type":["integer","null"],"readOnly":true},"created_by":{"type":"string","readOnly":true},"created_by_name":{"type":"string","readOnly":true},"created_at":{"$ref":"#/components/schemas/Timestamp","readOnly":true},"updated_at":{"$ref":"#/components/schemas/Timestamp","readOnly":true}},"example":{"id":"req_9WkT","organization_id":"org_northwind","name":"Q4 insurance refresh","due_date":"2026-10-15T00:00:00.000Z","template_id":"tpl_W9onboard","template_name":"New client onboarding - Northwind Dental","contact_id":"c_8Hk2pQ","contact_name":"Dana Whitfield","contact_email":"dana@northwinddental.com","expiry_id":"exp_4Tq9sLm2","group_id":null,"has_password":false,"expires_at":"2026-10-31T23:59:59.000Z","status":"viewed","archived":false,"paused":false,"sent_at":"2026-09-27T14:05:00.000Z","viewed_at":"2026-09-27T15:10:00.000Z","completed_at":null,"cancelled_at":null,"resend_count":0,"last_resent_at":null,"draft_filled_count":3,"draft_total_fields":5,"draft_updated_at":"2026-09-27T15:20:00.000Z","next_reminder_at":"2026-09-28T13:00:00.000Z","reminders_sent_count":0,"email_stats":{"sent_count":1,"delivered_count":1,"opened_count":1,"last_event_at":"2026-09-27T15:09:30.000Z"},"reply_received":false,"reply_at":null,"reply_snippet":null,"latest_review_status":null,"review_approved_count":null,"review_total_fields":null,"completed_field_count":null,"created_by":"u_71bXq","created_by_name":"Priya Shah","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T15:20:00.000Z"}},"CollectionReminderInput":{"type":"object","required":["amount","time_unit"],"description":"A reminder N units after the request was sent. Rows without `amount` or `time_unit` are ignored.","properties":{"firestore_id":{"type":"string","description":"Existing reminder id to update in place (updateCollectionRequestReminders)."},"amount":{"type":"integer","minimum":0,"maximum":365},"time_unit":{"type":"string","enum":["day","week","month","year"]},"period":{"type":"string","enum":["after"],"default":"after"},"time":{"type":"string","pattern":"^([01]?\\d|2[0-3]):[0-5]\\d(:[0-5]\\d)?$","default":"09:00","description":"Local time in the organization timezone."}},"example":{"amount":3,"time_unit":"day","time":"09:00"}},"CollectionRequestReminder":{"type":"object","description":"A scheduled reminder email for a request. Each reminder send rotates the request link.","properties":{"id":{"type":"string","readOnly":true},"request_id":{"type":"string","readOnly":true},"organization_id":{"type":"string","readOnly":true},"user_id":{"type":"string","readOnly":true},"amount":{"type":"integer"},"time_unit":{"type":"string","enum":["day","week","month","year"]},"period":{"type":"string","enum":["after"]},"time":{"type":"string"},"timezone":{"type":"string","readOnly":true},"reminder_date":{"type":"string","format":"date-time","readOnly":true},"scheduled_at":{"type":"string","format":"date-time","description":"UTC send time.","readOnly":true},"status":{"type":"string","enum":["pending","processing","sent","skipped","failed"],"readOnly":true},"sent_at":{"type":["string","null"],"format":"date-time","readOnly":true},"error_message":{"type":["string","null"],"readOnly":true},"created_at":{"$ref":"#/components/schemas/Timestamp","readOnly":true},"updated_at":{"$ref":"#/components/schemas/Timestamp","readOnly":true}},"example":{"id":"rem_5Gh1","request_id":"req_9WkT","organization_id":"org_northwind","user_id":"u_71bXq","amount":3,"time_unit":"day","period":"after","time":"09:00","timezone":"America/New_York","reminder_date":"2026-09-30T14:05:00.000Z","scheduled_at":"2026-09-30T13:00:00.000Z","status":"pending","sent_at":null,"error_message":null,"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}},"CollectionEmailEvent":{"type":"object","description":"Email delivery / engagement event for a request, from the email provider webhooks.","properties":{"id":{"type":"string"},"request_id":{"type":"string"},"reminder_id":{"type":["string","null"]},"organization_id":{"type":["string","null"]},"recipient_email":{"type":["string","null"]},"provider":{"type":"string"},"provider_message_id":{"type":["string","null"]},"event_type":{"type":"string","examples":["sent","delivered","opened","clicked","bounced","failed","reply_received"]},"timestamp":{"type":"string","format":"date-time"},"metadata":{"type":["object","null"],"additionalProperties":true},"raw_payload":{"description":"Provider payload, when stored."}}},"CollectionSubmissionStoredFile":{"type":"object","description":"An uploaded file as stored on a submission. Download it with getCollectionSubmissionFile.","properties":{"field_id":{"type":"string"},"original_filename":{"type":"string"},"storage_path":{"type":"string"},"content_type":{"type":"string"},"size_bytes":{"type":"integer"}}},"CollectionFieldReview":{"type":"object","properties":{"status":{"type":"string","enum":["approved","rejected"]},"comment":{"type":"string"},"reviewed_at":{"type":"string","format":"date-time"}}},"CollectionSubmissionDetail":{"type":"object","description":"A submission as returned inside getCollectionRequest (the stored document). Differs from `CollectionSubmission` (the feed shape): files carry `original_filename` / `storage_path` / `size_bytes`, and review state is included.","properties":{"id":{"type":"string","readOnly":true},"request_id":{"type":"string","readOnly":true},"organization_id":{"type":"string","readOnly":true},"template_id":{"type":"string","readOnly":true},"responses":{"type":"object","additionalProperties":true,"description":"Field id -> answer (string, number, boolean, string[] or address object; dates as ISO timestamps)."},"files":{"type":"array","items":{"$ref":"#/components/schemas/CollectionSubmissionStoredFile"}},"submitted_at":{"type":"string","format":"date-time"},"review_status":{"type":"string","enum":["pending","approved","changes_requested"]},"field_reviews":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/CollectionFieldReview"},"description":"Field id -> latest review decision."},"review_history":{"type":"array","items":{"type":"object","properties":{"revision":{"type":"integer"},"reviewed_by":{"type":"string"},"reviewed_by_name":{"type":"string"},"reviewed_at":{"type":"string","format":"date-time"},"decision":{"type":"string","enum":["approve","request_changes"]},"field_reviews":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/CollectionFieldReview"}}}}},"field_history":{"type":"object","description":"Field id -> previous answers replaced by resubmissions, oldest first.","additionalProperties":{"type":"array","items":{"type":"object","properties":{"value":{},"files":{"type":"array","items":{"$ref":"#/components/schemas/CollectionSubmissionStoredFile"}},"review_comment":{"type":"string"},"revision":{"type":"integer"},"replaced_at":{"type":"string","format":"date-time"}}}}},"revision":{"type":"integer","description":"Starts at 1; +1 per resubmission."},"reviewed_by":{"type":"string"},"reviewed_by_name":{"type":"string"},"reviewed_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"example":{"id":"sub_Qm3r","request_id":"req_9WkT","organization_id":"org_northwind","template_id":"tpl_W9onboard","responses":{"fld_company_name":"Northwind Dental LLC","fld_entity":"LLC"},"files":[{"field_id":"fld_insurance","original_filename":"liability-certificate-2026.pdf","storage_path":"organizations/org_northwind/collection-submissions/req_9WkT/fld_insurance-6c1e-liability-certificate-2026.pdf","content_type":"application/pdf","size_bytes":482113}],"submitted_at":"2026-09-28T09:12:00.000Z","review_status":"pending","field_reviews":{},"review_history":[],"revision":1}},"CollectionTriggerRuleInput":{"type":"object","required":["expiryId","templateId","triggerType"],"description":"Give `contactIds`, `groupId` or both.","properties":{"expiryId":{"type":"string"},"templateId":{"type":"string","description":"A non-archived template."},"triggerType":{"type":"string","enum":["before_expiry","on_expiry","after_expiry","on_date","on_done","on_renewal","manual"]},"triggerDays":{"type":"integer","minimum":0,"maximum":3650,"description":"Required for `before_expiry` / `after_expiry`."},"triggerTime":{"type":"string","default":"09:00","description":"HH:MM in the organization timezone (date-based types)."},"triggerDate":{"type":"string","format":"date-time","description":"Required for `on_date`; the exact fire time."},"contactIds":{"type":"array","items":{"type":"string"}},"groupId":{"type":"string"},"expiresInDays":{"type":"integer","description":"Created requests' links expire this many days after sending."},"dueInDays":{"type":"integer","description":"Created requests' due date, days after sending."},"carryToRenewal":{"type":"boolean","default":false,"description":"Copy this rule to the next occurrence when the expiry renews."}}},"CollectionTriggerRuleUpdate":{"type":"object","description":"Any subset of the create fields (except `expiryId`) plus `status`.","properties":{"id":{"type":"string","description":"Alternative to the `id` query parameter."},"templateId":{"type":"string"},"triggerType":{"type":"string","enum":["before_expiry","on_expiry","after_expiry","on_date","on_done","on_renewal","manual"]},"triggerDays":{"type":"integer","minimum":0,"maximum":3650},"triggerTime":{"type":"string"},"triggerDate":{"type":"string","format":"date-time"},"contactIds":{"type":"array","items":{"type":"string"}},"groupId":{"type":["string","null"],"description":"null or empty clears the group."},"expiresInDays":{"type":["integer","null"]},"dueInDays":{"type":["integer","null"]},"carryToRenewal":{"type":"boolean"},"status":{"type":"string","enum":["pending","paused","cancelled"]}}},"CollectionTriggerRule":{"type":"object","description":"An auto-send rule attached to an expiry.","properties":{"id":{"type":"string","readOnly":true},"organization_id":{"type":"string","readOnly":true},"expiry_id":{"type":"string","readOnly":true},"expiry_name":{"type":"string","readOnly":true},"template_id":{"type":"string"},"template_name":{"type":"string","readOnly":true},"trigger_type":{"type":"string","enum":["before_expiry","on_expiry","after_expiry","on_date","on_done","on_renewal","manual"]},"trigger_days":{"type":["integer","null"]},"trigger_time":{"type":"string"},"trigger_date":{"type":["string","null"],"format":"date-time","description":"Computed UTC fire time; null for event-based and manual rules.","readOnly":true},"contact_ids":{"type":"array","items":{"type":"string"}},"group_id":{"type":["string","null"]},"group_name":{"type":["string","null"],"readOnly":true},"expires_in_days":{"type":["integer","null"]},"due_in_days":{"type":["integer","null"]},"status":{"type":"string","enum":["pending","triggered","error","paused","cancelled"]},"created_request_ids":{"type":"array","items":{"type":"string"},"readOnly":true},"run_count":{"type":"integer","readOnly":true},"last_triggered_at":{"type":["string","null"],"format":"date-time","readOnly":true},"triggered_by":{"type":["string","null"],"description":"What fired it last: `manual` or `schedule`.","readOnly":true},"error_message":{"type":["string","null"],"readOnly":true},"carry_to_renewal":{"type":"boolean"},"created_by":{"type":"string","readOnly":true},"created_by_name":{"type":"string","readOnly":true},"created_at":{"$ref":"#/components/schemas/Timestamp","readOnly":true},"updated_at":{"$ref":"#/components/schemas/Timestamp","readOnly":true}},"example":{"id":"trg_2Vb8","organization_id":"org_northwind","expiry_id":"exp_4Tq9sLm2","expiry_name":"Northwind Dental - State Dental License","template_id":"tpl_W9onboard","template_name":"New client onboarding - Northwind Dental","trigger_type":"before_expiry","trigger_days":30,"trigger_time":"09:00","trigger_date":"2026-09-15T13:00:00.000Z","contact_ids":["c_8Hk2pQ"],"group_id":null,"group_name":null,"expires_in_days":21,"due_in_days":14,"status":"pending","created_request_ids":[],"run_count":0,"last_triggered_at":null,"triggered_by":null,"error_message":null,"carry_to_renewal":true,"created_by":"u_71bXq","created_by_name":"Priya Shah","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}},"CollectionPublicError":{"type":"object","description":"Error body of the public request-link endpoints (no `code`). `error` is absent when only a password is needed.","properties":{"error":{"type":"string"},"requiresPassword":{"type":"boolean","description":"Ask the recipient for the request password."},"alreadySubmitted":{"type":"boolean"}}},"CollectionResponses":{"type":"object","description":"Field id -> answer. Types: text/phone/long_text string, number number, date ISO string, checkbox boolean, dropdown/radio one of `options`, multi_select array of `options`, address object `{line1, line2, city, state, postal_code, country}`. Files go in `files`.","additionalProperties":true,"example":{"fld_company_name":"Northwind Dental LLC","fld_entity":"LLC"}},"CollectionUploadedFileRef":{"type":"object","required":["fieldId","storagePath"],"description":"A file uploaded through getCollectionUploadUrl (and composeCollectionUpload for chunked uploads).","properties":{"fieldId":{"type":"string"},"storagePath":{"type":"string","description":"The `storagePath` returned by the upload call."},"originalFilename":{"type":"string"},"contentType":{"type":"string","description":"Draft only; submit reads the type from storage."},"sizeBytes":{"type":"integer","description":"Draft only; submit reads the size from storage."}}},"CollectionUploadUrlResult":{"oneOf":[{"type":"object","title":"Single upload","required":["chunked","uploadUrl","storagePath"],"properties":{"chunked":{"type":"boolean","const":false},"uploadUrl":{"type":"string","format":"uri","description":"PUT the file here with every header in `uploadHeaders` (15 minutes)."},"storagePath":{"type":"string"},"uploadHeaders":{"$ref":"#/components/schemas/SignedUploadHeaders"}}},{"type":"object","title":"Chunked upload","required":["chunked","uploadId","chunkSize","totalChunks","contentType","storagePath","chunkUrls"],"properties":{"chunked":{"type":"boolean","const":true},"uploadId":{"type":"string","format":"uuid"},"chunkSize":{"type":"integer","description":"Bytes per chunk (20MB); the last chunk may be smaller."},"totalChunks":{"type":"integer"},"contentType":{"type":"string"},"storagePath":{"type":"string","description":"Final path; pass to composeCollectionUpload."},"chunkUrls":{"type":"array","items":{"type":"object","properties":{"index":{"type":"integer"},"url":{"type":"string","format":"uri","description":"PUT with every header in `uploadHeaders` (24 hours)."},"storagePath":{"type":"string"},"uploadHeaders":{"$ref":"#/components/schemas/SignedUploadHeaders"}}}}}}]},"SignedUploadHeaders":{"type":"object","additionalProperties":{"type":"string"},"description":"Headers to send, unchanged, with the PUT to a Document Collection signed URL. Always has `Content-Type`; it can also have `x-goog-content-length-range` (`0,<max bytes>`), which is part of the signature - leaving it out fails the PUT.","example":{"Content-Type":"application/pdf","x-goog-content-length-range":"0,482113"}},"CollectionPublicView":{"type":"object","required":["request","template","organization","draft"],"properties":{"request":{"type":"object","properties":{"status":{"type":"string","enum":["viewed","expired"],"description":"Always `viewed` for an open link."},"expires_at":{"type":["string","null"],"format":"date-time"},"recipient_name":{"type":"string"},"expiry_linked":{"type":"boolean"}}},"review":{"type":["object","null"],"description":"Set when the sender requested changes; only fields rejected in `field_reviews` need new answers.","properties":{"field_reviews":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/CollectionFieldReview"}},"prior_responses":{"$ref":"#/components/schemas/CollectionResponses"},"prior_files":{"type":"array","items":{"$ref":"#/components/schemas/CollectionSubmissionStoredFile"}}}},"template":{"type":"object","description":"Template without its id. Reference files omit `storage_path`; fetch them with getCollectionTemplateReferenceFile.","properties":{"name":{"type":"string"},"description":{"type":"string"},"pages":{"type":"array","items":{"$ref":"#/components/schemas/CollectionTemplatePage"}}}},"organization":{"type":"object","properties":{"name":{"type":"string"},"logo_url":{"type":["string","null"]}}},"draft":{"type":"object","description":"Autosaved progress (empty objects when none).","properties":{"responses":{"$ref":"#/components/schemas/CollectionResponses"},"files":{"type":"object","description":"Field id -> `{storage_path, original_filename, content_type, size_bytes}`.","additionalProperties":{"$ref":"#/components/schemas/CollectionSubmissionStoredFile"}},"last_page_id":{"type":["string","null"]}}}}},"ExpiryType":{"type":"object","description":"An organization-defined expiry category. Expiries reference a type by its `name` (the expiry's `type` field).","required":["id","name"],"properties":{"id":{"type":"string","readOnly":true},"name":{"type":"string","description":"Unique within the organization."},"email_template_id":{"type":["string","null"],"description":"Email template used for reminders of expiries of this type; null = organization default."},"is_default":{"type":"boolean","readOnly":true},"organization_id":{"type":"string","readOnly":true},"user_id":{"type":"string","readOnly":true,"description":"Creator's user id."},"created_at":{"$ref":"#/components/schemas/Timestamp","readOnly":true},"updated_at":{"$ref":"#/components/schemas/Timestamp","readOnly":true}},"example":{"id":"et_5Gm1rT","name":"Business License","email_template_id":null,"is_default":false,"organization_id":"org_northwind","user_id":"u_71bXq","created_at":"2026-03-02T10:15:00.000Z"}},"ExpiryTypeWithStats":{"type":"object","description":"An expiry type as returned by getExpiryTypesWithStats.","properties":{"id":{"type":"string","readOnly":true},"name":{"type":"string"},"expiry_count":{"type":"integer","readOnly":true,"description":"Number of expiries whose `type` equals this name."},"created_at":{"$ref":"#/components/schemas/Timestamp"},"email_template_id":{"type":["string","null"]},"email_template_name":{"type":["string","null"],"readOnly":true},"email_template_missing":{"type":"boolean","readOnly":true,"description":"True when the referenced template no longer exists (reminders use the default)."},"email_template_active":{"type":"boolean","readOnly":true,"description":"True when the override is set, exists, and the plan allows it."}}},"ExpiryTypeName":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}},"ExpiryTypeCounts":{"type":"object","description":"Expiry type name -> number of expiries.","additionalProperties":{"type":"integer"}},"ExpiryTypeCreatedResponse":{"type":"object","required":["message","id","name"],"properties":{"message":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"email_template_id":{"type":["string","null"],"description":"Present on addExpiryTypeNew."}}},"ExpiryTypeUpdateResult":{"type":"object","properties":{"message":{"type":"string"},"updated_expiries":{"type":"integer","description":"Expiries whose `type` was renamed (0 when only the template changed)."},"email_template_id":{"type":["string","null"]}}},"ExpiryTypeDeleteResult":{"type":"object","required":["message"],"properties":{"message":{"type":"string"},"deleted_expiries":{"type":"integer","description":"Present when the type was unused or `option` was `delete_all`."},"moved_expiries":{"type":"integer","description":"Present for `option: move`."},"updated_expiries":{"type":"integer","description":"Present for `option: rename`."}}},"ExpiryMetricFilter":{"type":"string","description":"Dashboard metric card to filter by.","enum":["total","completed","expired","archived","active","today","upcomingThisMonth","future"]},"ExpiryMetrics":{"type":"object","properties":{"total":{"type":"integer","description":"Not done and not archived."},"completed":{"type":"integer"},"expired":{"type":"integer","description":"Past expiry date, not done or archived."},"archived":{"type":"integer"},"active":{"type":"integer","description":"Expiry date today or later, not done or archived."},"future":{"type":"integer","description":"Expiry date after now and within `futureDurationDays` (all future when omitted)."},"upcomingThisMonth":{"type":"integer"},"today":{"type":"integer"},"notNotifying":{"type":"integer","description":"Active records with no queued reminder or no live contact."}}},"ExpiryListItem":{"description":"An expiry in list responses. `share_password` is replaced by `has_share_password`.","allOf":[{"$ref":"#/components/schemas/Expiry"},{"type":"object","properties":{"has_share_password":{"type":"boolean","readOnly":true}}}]},"ExpiryWithAssigneeNames":{"allOf":[{"$ref":"#/components/schemas/ExpiryListItem"},{"type":"object","properties":{"assigned_to_user_full_name":{"type":["string","null"],"readOnly":true},"assigned_to_user_email":{"type":["string","null"],"readOnly":true},"assigned_by_user_full_name":{"type":["string","null"],"readOnly":true},"assigned_by_user_email":{"type":["string","null"],"readOnly":true}}}]},"PaginatedExpiriesQuery":{"type":"object","description":"Filter names have aliases; the first listed wins when both are sent.","properties":{"page":{"type":"integer","minimum":1,"default":1},"limit":{"type":"integer","minimum":1,"maximum":10000,"default":10,"description":"Values above 10000 are clamped."},"metricFilter":{"$ref":"#/components/schemas/ExpiryMetricFilter"},"metric":{"$ref":"#/components/schemas/ExpiryMetricFilter"},"typeFilter":{"type":"string","description":"Expiry type name, or `All`. Alias `type`."},"type":{"type":"string"},"priorityFilter":{"type":"string","description":"Priority, or `All`. Alias `priority`."},"priority":{"type":"string"},"stateFilter":{"type":"string","description":"Workflow state, or `All`."},"searchQuery":{"type":"string","description":"Text search. Alias `search`."},"search":{"type":"string"},"selectedTeam":{"type":"string","description":"Team id; `'0'` = all. Alias `team_id`."},"team_id":{"type":"string"},"folder_id":{"type":["string","null"],"description":"Folder id; `root`, null or empty string = unfiled only."},"include_subfolders":{"type":"boolean","default":false},"assignedToMe":{"type":"boolean","default":false},"assignedUser":{"type":"string","description":"User id of the assignee."},"futureDurationDays":{"description":"Window for `metric: future`, in days, or `all`.","oneOf":[{"type":"integer"},{"type":"string"}]},"notifyNone":{"type":"boolean","default":false,"description":"Only records with no queued reminder or no live contact."},"sortBy":{"type":"string","default":"expiry_date","description":"A field name, or `<templateId>:::<fieldKey>` for a template field. Invalid values fall back to `expiry_date`."},"sortDirection":{"type":"string","enum":["asc","desc"],"default":"asc"}}},"PaginatedExpiriesResponse":{"type":"object","required":["data","expiries","pagination"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ExpiryWithAssigneeNames"}},"expiries":{"type":"array","description":"Same array as `data` (kept for older clients).","items":{"$ref":"#/components/schemas/ExpiryWithAssigneeNames"}},"pagination":{"$ref":"#/components/schemas/ExpiryPagination"}}},"ExpiryPagination":{"type":"object","properties":{"total":{"type":"integer"},"totalPages":{"type":"integer"},"currentPage":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer"},"optimized":{"type":"boolean","description":"False when the in-memory fallback (max 5000 scanned records) was used."}}},"ExpiryReminderStatusResponse":{"type":"object","required":["status"],"properties":{"status":{"type":"object","description":"Expiry id -> reminder status.","additionalProperties":{"$ref":"#/components/schemas/ExpiryReminderStatus"}}}},"ExpiryReminderStatus":{"type":"object","properties":{"has_pending_reminder":{"type":"boolean"},"pending_reminder_count":{"type":"integer"},"next_reminder_at":{"type":["string","null"],"format":"date-time"}}},"ExpiryCreatedResponse":{"type":"object","required":["message","id"],"properties":{"message":{"type":"string"},"id":{"type":"string","description":"The new expiry's id."}}},"ExpiryResolvedContact":{"type":"object","description":"A contact resolved from the expiry's `contacts` ids (camelCase keys).","properties":{"id":{"type":"string"},"user_id":{"type":["string","null"]},"organization_id":{"type":"string"},"firstName":{"type":["string","null"]},"lastName":{"type":["string","null"]},"email":{"type":["string","null"]},"smsPhone":{"type":["string","null"]},"whatsappPhone":{"type":["string","null"]},"contactType":{"type":["string","null"]},"jobTitle":{"type":["string","null"]},"timezone":{"type":["string","null"]},"contactGroup":{"type":["string","null"]},"is_default":{"type":["boolean","null"]},"email_opt_in":{"type":["boolean","null"]},"sms_opt_in":{"type":["boolean","null"]},"whatsapp_opt_in":{"type":["boolean","null"]},"sendNotifications":{"type":["boolean","null"]},"createdAt":{"type":["string","null"]},"updatedAt":{"type":["string","null"]}}},"ExpiryEscalationContact":{"type":"object","properties":{"id":{"type":"string"},"firstName":{"type":["string","null"]},"lastName":{"type":["string","null"]},"email":{"type":["string","null"]},"smsPhone":{"type":["string","null"]},"whatsappPhone":{"type":["string","null"]},"email_opt_in":{"type":["boolean","null"]},"sms_opt_in":{"type":["boolean","null"]},"whatsapp_opt_in":{"type":["boolean","null"]}}},"ExpiryDetail":{"description":"getExpiry response. Like `Expiry`, but `contacts` and `escalation_contacts` are resolved objects (contacts outside\nthe organization or deleted are dropped). `share_password` is still returned for the edit form; prefer `has_share_password`.\n","allOf":[{"$ref":"#/components/schemas/Expiry"},{"type":"object","properties":{"has_share_password":{"type":"boolean","readOnly":true},"contacts":{"type":"array","items":{"$ref":"#/components/schemas/ExpiryResolvedContact"}},"escalation_contacts":{"type":"array","items":{"$ref":"#/components/schemas/ExpiryEscalationContact"}},"assigned_to_user_full_name":{"type":["string","null"]},"assigned_to_user_email":{"type":["string","null"]},"assigned_by_user_full_name":{"type":["string","null"]},"assigned_by_user_email":{"type":["string","null"]}}}]},"NextRecurringExpiry":{"type":"object","description":"The next occurrence created (or rolled forward) when a recurring expiry is completed.","properties":{"next_expiry_id":{"type":"string","description":"New expiry id (`renew_as_copy`) or the same id (`renew_same`)."},"next_expiry_date":{"$ref":"#/components/schemas/IsoDate"},"mode":{"type":"string","enum":["renew_as_copy","renew_same"]},"occurrence_number":{"type":"integer"}}},"ExpiryStateUpdateResponse":{"type":"object","required":["message","state"],"properties":{"message":{"type":"string"},"state":{"type":"string","enum":["todo","inprogress","onhold","inreview","completed"]},"next_expiry":{"oneOf":[{"$ref":"#/components/schemas/NextRecurringExpiry"},{"type":"null"}]}}},"BulkImportExpiryRow":{"type":"object","description":"One spreadsheet row. Header keys are case-insensitive and accept aliases (e.g. `expirydate`, `amount` for `value`,\n`notification_emails` for `notification_email`). Unknown columns are stored as custom columns; template columns use\n`ct_<templateId>[<fieldId>]`. Server-owned columns (organization_id, share_token, is_done, ...) are ignored.\n","required":["name","type","expiry_date"],"additionalProperties":true,"properties":{"name":{"type":"string"},"type":{"type":"string","description":"New type names are created automatically."},"expiry_date":{"type":"string","description":"Parsed to `YYYY-MM-DD`; unparseable dates fail the row."},"start_date":{"type":"string"},"start_time":{"type":"string"},"expiry_time":{"type":"string"},"priority":{"type":"string","default":"Medium"},"state":{"type":"string"},"notes":{"type":"string"},"url":{"type":"string"},"team_id":{"type":"string"},"folder_id":{"type":"string"},"contacts":{"description":"Contact ids (must belong to your organization).","oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"email":{"type":"string","description":"Email of an organization user to assign."},"notification_email":{"type":"string","description":"Comma-separated emails of existing contacts who receive reminders."},"assigned_to":{"type":"string"},"assigned_by":{"type":"string"},"audit_date":{"type":"string"},"compliance_id":{"type":"string"},"value":{"type":["string","number"]},"currency":{"type":"string"},"penalty_amount":{"type":["string","number"]},"escalation_enabled":{"type":["boolean","string"]},"escalation_notification_days":{"description":"Days (0-365), as an array or a comma-separated string.","oneOf":[{"type":"string"},{"type":"array","items":{"type":"integer"}}]}}},"BulkImportExpiriesRequest":{"type":"object","description":"Send rows as `records` (or `expiries`).","properties":{"records":{"type":"array","minItems":1,"maxItems":5000,"items":{"$ref":"#/components/schemas/BulkImportExpiryRow"}},"expiries":{"type":"array","minItems":1,"maxItems":5000,"description":"Alias of `records`.","items":{"$ref":"#/components/schemas/BulkImportExpiryRow"}},"template_id":{"type":"string","description":"Custom expiry template applied to every row; template columns are validated against it."}}},"BulkImportNotificationWarning":{"type":"object","description":"Rows whose notification email could not be matched, grouped by email and reason.","properties":{"reason":{"type":"string"},"email":{"type":"string"},"rows":{"type":"array","description":"1-based row numbers.","items":{"type":"integer"}}}},"BulkImportExpiriesResult":{"type":"object","required":["message","successCount","errors"],"properties":{"message":{"type":"string"},"successCount":{"type":"integer","description":"Rows imported (0 when aborted)."},"aborted":{"type":"boolean","description":"True when any row failed validation; nothing was imported."},"deduped":{"type":"boolean","description":"True when this is a replay of an earlier identical import."},"errors":{"type":"array","description":"Per-row error messages (`Row 3: Name is required`).","items":{"type":"string"}},"warnings":{"type":"array","items":{"type":"string"}},"notificationWarnings":{"type":"array","items":{"$ref":"#/components/schemas/BulkImportNotificationWarning"}},"missingContactEmails":{"type":"array","items":{"type":"string"}},"assignedTeamId":{"type":["string","null"],"description":"Default team the imported rows were assigned to."}}},"BulkImportExpiriesFailure":{"type":"object","required":["error","successCount","errors"],"properties":{"error":{"type":"string"},"code":{"type":"string","enum":["LIMIT_REACHED","TOO_MANY_ROWS"]},"message":{"type":"string"},"status":{"type":"string"},"successCount":{"type":"integer","const":0},"errors":{"type":"array","items":{"type":"string"}},"maxRows":{"type":"integer"},"limitReached":{"type":"boolean"},"limit":{"type":"integer"},"current":{"type":"integer"},"remaining":{"type":"integer"}}},"SharedDirectoryEntry":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"type":{"type":"string"},"customType":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"},"address":{"type":"string"},"notes":{"type":"string"},"typedFields":{"type":"object","additionalProperties":true},"customFields":{"type":"array","items":{}}}},"SharedExpiryView":{"type":"object","description":"Public view of a shared expiry. Only display fields are included (never organization, contact or money fields).\nFields absent on the record are omitted.\n","properties":{"id":{"type":"string"},"name":{"type":"string"},"type":{"type":"string"},"priority":{"type":"string"},"state":{"type":"string"},"notes":{"type":"string"},"url":{"type":"string"},"start_date":{"type":["string","null"]},"start_time":{"type":"string"},"start_timezone":{"type":"string"},"expiry_date":{"type":"string"},"expiry_time":{"type":"string"},"expiry_timezone":{"type":"string"},"custom_columns":{"type":"object","additionalProperties":true},"checklist_items":{"type":"array","items":{"type":"object","additionalProperties":true}},"checklist_completed":{"description":"Checklist completion state as stored."},"template_fields":{"type":["array","null"],"items":{"type":"object","additionalProperties":true}},"template_data":{"type":["object","null"],"additionalProperties":true},"template_name":{"type":["string","null"]},"template_icon":{"type":["string","null"]},"template_color":{"type":["string","null"]},"is_recurring":{"type":"boolean"},"recurrence_type":{"type":["string","null"]},"recurrence_interval":{"type":["integer","null"]},"recurrence_count":{"type":["integer","null"]},"recurrence_end_date":{"type":["string","null"]},"occurrence_number":{"type":"integer"},"fileUrls":{"type":"array","items":{"type":"string"}},"fileUrl":{"type":"string"},"fileNames":{"type":"array","items":{"type":"string"}},"is_done":{"type":"boolean"},"is_archive":{"type":"boolean"},"is_public":{"type":"boolean"},"created_at":{"type":"string"},"updated_at":{"type":"string"},"has_workflow":{"type":"boolean"},"workflow_attachment_ids":{"type":"array","description":"Placeholders (`wf_0`, `wf_1`, ...); only the count is meaningful.","items":{"type":"string"}},"assigned_to_user_full_name":{"type":["string","null"]},"assigned_to_user_email":{"type":["string","null"]},"assigned_by_user_full_name":{"type":["string","null"]},"assigned_by_user_email":{"type":["string","null"]},"directory_entries_data":{"type":"array","items":{"$ref":"#/components/schemas/SharedDirectoryEntry"}}}},"ExpiryShareUrl":{"type":"object","required":["share_url","share_token"],"properties":{"share_url":{"type":"string","format":"uri"},"share_token":{"type":"string","readOnly":true}}},"RenewalHistoryEntry":{"type":"object","properties":{"from_expiry_id":{"type":"string"},"to_expiry_id":{"type":"string"},"occurrence_number":{"type":"integer"},"renewed_at":{"$ref":"#/components/schemas/Timestamp"},"renewal_type":{"type":"string","examples":["automatic"]},"renewal_mode":{"type":"string","enum":["renew_as_copy","renew_same"]},"previous_date":{"$ref":"#/components/schemas/IsoDate"},"new_date":{"$ref":"#/components/schemas/IsoDate"},"timestamp":{"$ref":"#/components/schemas/Timestamp"}}},"RenewalHistoryResponse":{"type":"object","required":["data","count"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RenewalHistoryEntry"}},"count":{"type":"integer"}}},"RecurrenceSettingsInput":{"type":"object","required":["expiryId"],"properties":{"expiryId":{"type":"string"},"is_recurring":{"type":"boolean"},"recurrence_type":{"type":["string","null"],"enum":["day","week","month","year",null],"description":"Required when `is_recurring` is true."},"recurrence_interval":{"type":["integer","null"],"minimum":1,"description":"Required when `is_recurring` is true."},"recurrence_count":{"type":["integer","null"],"minimum":1,"description":"Stop after this many occurrences."},"recurrence_end_date":{"oneOf":[{"$ref":"#/components/schemas/IsoDate"},{"type":"null"}],"description":"Must not be before the expiry date."},"recurrence_mode":{"type":["string","null"],"enum":["renew_as_copy","renew_same",null],"description":"Defaults to `renew_as_copy`."}}},"RecurrenceSettingsResponse":{"type":"object","properties":{"message":{"type":"string"},"settings":{"type":"object","properties":{"is_recurring":{"type":"boolean"},"recurrence_type":{"type":["string","null"]},"recurrence_interval":{"type":["integer","null"]},"recurrence_count":{"type":["integer","null"]},"recurrence_end_date":{"type":["string","null"]},"recurrence_mode":{"type":["string","null"]},"updated_at":{"$ref":"#/components/schemas/Timestamp"}}}}},"LegacyUpcomingNotificationRecipient":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":"string"},"channels":{"type":"array","items":{"type":"string","enum":["email","sms","whatsapp"]}}}},"LegacyUpcomingNotification":{"type":"object","properties":{"id":{"type":"string","description":"Expiry id."},"expiryName":{"type":"string"},"expiryType":{"type":"string"},"expiryDate":{"$ref":"#/components/schemas/IsoDate"},"nextNotification":{"$ref":"#/components/schemas/Timestamp"},"reminderTime":{"type":"string","description":"Local reminder time `HH:mm`."},"timezone":{"type":"string","description":"IANA timezone from the organization's reminder settings (UTC by default)."},"daysUntilExpiry":{"type":"integer"},"reminderDaysBefore":{"type":"integer","description":"Days offset of the matched rule (before or after, see `reminderPeriod`)."},"reminderPeriod":{"type":"string","enum":["before","after"]},"recipients":{"type":"array","items":{"$ref":"#/components/schemas/LegacyUpcomingNotificationRecipient"}},"notificationChannels":{"type":"array","items":{"type":"string","enum":["email","sms","whatsapp"]}},"priority":{"type":"string","enum":["critical","high","medium"],"description":"critical within 7 days, high within 14, else medium."}}},"ExpiryAttachment":{"type":"object","description":"A file attached to an expiry. Stored on the expiry as matching entries in `fileUrls` and `fileNames`.","required":["index","name","url"],"properties":{"index":{"type":"integer","minimum":0,"readOnly":true,"description":"Position in the expiry's file list (changes when earlier files are removed)."},"name":{"type":"string","description":"Display name - the original file name.","examples":["Gas Safety Certificate 2026.pdf"]},"url":{"type":"string","format":"uri","readOnly":true,"description":"Download URL. Anyone with this URL can download the file - keep it private."}}},"ExpiryAttachmentUploadResult":{"type":"object","required":["attachment","attachments"],"properties":{"attachment":{"$ref":"#/components/schemas/ExpiryAttachment"},"attachments":{"type":"array","description":"All attachments on the expiry after this upload.","items":{"$ref":"#/components/schemas/ExpiryAttachment"}}}},"ExpiryAttachmentDeleteRequest":{"type":"object","required":["expiryId"],"description":"Give `url` (recommended) or `index`. If both are given, `url` wins.","properties":{"expiryId":{"type":"string"},"url":{"type":"string","description":"Download URL of the attachment, as returned by getExpiryAttachments."},"index":{"type":"integer","minimum":0,"description":"Position from getExpiryAttachments; used only when url is not given."}}},"FolderFile":{"type":"object","description":"A file in the Documents (Folders) file manager. Same record the web app creates.","required":["id","name","size","type","url","storage_path","folder_id","uploaded_at","uploaded_by_name"],"properties":{"id":{"type":"string","readOnly":true,"examples":["ff_7Qm2xKp"]},"name":{"type":"string","maxLength":255,"description":"Display name (the original file name unless renamed)."},"size":{"type":"integer","readOnly":true,"description":"Size in bytes."},"type":{"type":"string","readOnly":true,"description":"MIME type.","examples":["application/pdf"]},"url":{"type":"string","format":"uri","readOnly":true,"description":"Download URL (Firebase Storage token URL). Anyone with this link can download the file."},"storage_path":{"type":"string","readOnly":true,"description":"Where the file is stored. Does not change on rename or move."},"folder_id":{"type":["string","null"],"description":"Folder id, or null for the top level."},"uploaded_at":{"$ref":"#/components/schemas/Timestamp"},"uploaded_by_name":{"type":"string","description":"Name shown as \"Owner\" in the app."}}},"FolderFileDeleted":{"type":"object","required":["deleted","id"],"properties":{"deleted":{"type":"boolean","const":true},"id":{"type":"string"}}},"KnowledgeDoc":{"type":"object","description":"A page in the Docs (knowledge base) section. Same record the web app creates.","required":["id","title","content","emoji","tags","parent_id","status","organization_id","created_at","updated_at"],"properties":{"id":{"type":"string","readOnly":true},"title":{"type":"string","maxLength":300,"description":"Defaults to \"Untitled\"."},"content":{"type":"string","description":"Page body as HTML (the web editor's format), sanitized on write."},"emoji":{"type":"string","maxLength":16,"description":"Page icon shown next to the title."},"tags":{"type":"array","maxItems":30,"items":{"type":"string","maxLength":50}},"parent_id":{"type":["string","null"],"description":"Parent page id, or null for a top-level page."},"status":{"type":"string","enum":["draft","published"]},"organization_id":{"type":"string","readOnly":true},"created_by":{"type":["string","null"],"readOnly":true},"created_by_name":{"type":["string","null"],"readOnly":true},"created_by_email":{"type":["string","null"],"readOnly":true},"created_at":{"type":["string","null"],"format":"date-time","readOnly":true},"updated_by":{"type":["string","null"],"readOnly":true},"updated_by_name":{"type":["string","null"],"readOnly":true},"updated_at":{"type":["string","null"],"format":"date-time","readOnly":true}}},"KnowledgeDocInput":{"type":"object","description":"Editable page fields. Anything else in the body is ignored.","properties":{"title":{"type":"string","maxLength":300},"content":{"type":"string","description":"HTML, max 500 KB. Allowed - headings, paragraphs, bold/italic/underline/strike, lists, task lists, links (http, https, mailto), https images, tables, code, blockquotes, text color/highlight/alignment. Everything else is stripped."},"emoji":{"type":"string","maxLength":16},"tags":{"type":"array","maxItems":30,"items":{"type":"string","maxLength":50}},"parent_id":{"type":["string","null"]}}},"KnowledgeDocBreadcrumb":{"type":"object","required":["id","title","emoji"],"properties":{"id":{"type":"string"},"title":{"type":"string"},"emoji":{"type":"string"}}},"KnowledgeDocDeleteRequest":{"type":"object","required":["id"],"properties":{"id":{"type":"string"},"cascade":{"type":"boolean","default":false,"description":"Also delete every sub-page (and their attachments)."}}},"KnowledgeDocDeleteResult":{"type":"object","required":["deleted","id","deleted_ids"],"properties":{"deleted":{"type":"boolean","const":true},"id":{"type":"string"},"deleted_ids":{"type":"array","description":"Every page removed, sub-pages first.","items":{"type":"string"}}}},"KnowledgeDocAttachment":{"type":"object","description":"A file attached to a Docs page.","required":["id","doc_id","name","size","type","url"],"properties":{"id":{"type":"string","readOnly":true},"doc_id":{"type":"string","readOnly":true},"name":{"type":"string"},"size":{"type":["integer","null"],"description":"Bytes."},"type":{"type":"string","description":"MIME type."},"url":{"type":"string","format":"uri","description":"Download URL."},"storage_path":{"type":"string","readOnly":true},"uploaded_at":{"type":["string","null"],"format":"date-time","readOnly":true},"uploaded_by":{"type":"string","readOnly":true},"uploaded_by_name":{"type":"string","readOnly":true}}},"KnowledgeDocAttachmentDeleteRequest":{"type":"object","required":["id","attachmentId"],"properties":{"id":{"type":"string","description":"Page id."},"attachmentId":{"type":"string"}}},"KnowledgeDocAttachmentDeleteResult":{"type":"object","required":["deleted","id"],"properties":{"deleted":{"type":"boolean","const":true},"id":{"type":"string","description":"The attachment id."}}},"Notification":{"type":"object","description":"An in-app notification (assignment or @mention) for one user.","required":["id","user_id","type","item_type","item_id","read"],"properties":{"id":{"type":"string","readOnly":true},"user_id":{"type":"string","readOnly":true,"description":"Recipient uid."},"organization_id":{"type":"string","readOnly":true},"type":{"type":"string","description":"`assignment` or `mention` (free text up to 50 chars when created via the API).","examples":["mention"]},"item_type":{"type":"string","description":"Kind of record the notification is about.","examples":["expiry","workflow"]},"item_id":{"type":"string"},"item_name":{"type":"string"},"message":{"type":"string"},"mentioned_by":{"type":"object","readOnly":true,"description":"Who triggered the notification (also used for assignments).","properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"}}},"comment_text":{"type":"string"},"link":{"type":"string","description":"Relative in-app path to open, or empty."},"read":{"type":"boolean","readOnly":true},"read_at":{"type":"string","format":"date-time","description":"ISO 8601 date-time string (UTC) once read.","readOnly":true},"created_at":{"readOnly":true,"type":["string","null"],"format":"date-time"},"assigned_user":{"type":"string","readOnly":true,"description":"Assignment notifications only."},"assigned_by":{"type":"string","readOnly":true,"description":"Assignment notifications only."},"item_deleted":{"type":"boolean","readOnly":true,"description":"True once the referenced item was deleted."},"comment_deleted":{"type":"boolean","readOnly":true,"description":"True once the referenced comment was deleted."}},"example":{"id":"ntf_3Jd8wQ","user_id":"u_71bXq","organization_id":"org_northwind","type":"assignment","item_type":"expiry","item_id":"exp_4Tq9sLm2","item_name":"Northwind Dental - State Dental License","message":"Priya Shah assigned you to \"Northwind Dental - State Dental License\"","mentioned_by":{"id":"u_2kPz9","name":"Priya Shah","email":"priya@northwinddental.com"},"comment_text":"","link":"/dashboard/expiry/exp_4Tq9sLm2","read":false,"created_at":"2026-09-27T14:05:00.000Z","assigned_user":"u_71bXq","assigned_by":"u_2kPz9"}},"NotificationInput":{"type":"object","required":["user_id","type","item_type","item_id"],"properties":{"user_id":{"type":"string","description":"Recipient uid; must be in your organization."},"type":{"type":"string","maxLength":50,"examples":["mention"]},"item_type":{"type":"string","maxLength":50,"examples":["expiry","workflow"]},"item_id":{"type":"string","maxLength":200},"item_name":{"type":"string","maxLength":300},"message":{"type":"string","maxLength":500},"comment_text":{"type":"string","maxLength":5000},"link":{"type":"string","maxLength":500,"description":"Relative app path starting with `/`; other values are dropped."}}},"NotificationList":{"type":"object","required":["notifications","unread_count","total"],"properties":{"notifications":{"type":"array","items":{"$ref":"#/components/schemas/Notification"}},"unread_count":{"type":"integer","description":"All unread notifications of the caller."},"total":{"type":"integer","description":"Number of notifications in this response."}}},"CreateNotificationResult":{"type":"object","properties":{"success":{"type":"boolean"},"notification_id":{"type":"string"},"message":{"type":"string"}}},"NotificationPreferences":{"type":"object","properties":{"push_enabled":{"type":"boolean","default":false},"push_scope":{"type":"string","enum":["all","assigned_to_me"],"default":"assigned_to_me","description":"Push for all expiries in the organization, or only those assigned to you."}}},"EmailActivityEvent":{"type":"object","description":"One provider-agnostic email event.","properties":{"id":{"type":"string","readOnly":true},"event_type":{"type":"string","examples":["sent","delivered","opened","clicked","bounced","complained","failed","delayed","replied"]},"timestamp":{"type":["string","null"],"format":"date-time"},"contact_email":{"type":["string","null"]},"contact_display":{"type":"string"},"message_id":{"type":["string","null"],"description":"Provider message id."},"subject":{"type":["string","null"]},"clicked_url":{"type":["string","null"]},"bounce_type":{"type":["string","null"]},"failure_reason":{"type":["string","null"]},"reply_text":{"type":["string","null"],"description":"Snippet of an inbound reply."},"provider":{"type":["string","null"]}}},"ExpiryEmailActivity":{"type":"object","required":["expiry_id","stats","events","contact_count"],"properties":{"expiry_id":{"type":"string"},"stats":{"type":"object","description":"Rollup counters: `<event_type>_count`, `last_<event_type>_at` and `last_event_at`.","additionalProperties":true},"events":{"type":"array","items":{"$ref":"#/components/schemas/EmailActivityEvent"}},"contact_count":{"type":"integer","description":"Distinct recipient addresses in `events`."}}},"EmailSend":{"type":"object","description":"One email sent for one expiry (a digest email yields one row per bundled expiry). Also carries\n`<event_type>_at` timestamps (for example `sent_at`, `opened_at`) for each event seen.\n","additionalProperties":true,"properties":{"id":{"type":"string","readOnly":true},"organization_id":{"type":"string","readOnly":true},"expiry_id":{"type":"string"},"expiry_name":{"type":["string","null"],"description":"Current expiry name, or `Deleted expiry` / `Digest email`."},"reminder_id":{"type":["string","null"]},"recipient_email":{"type":["string","null"]},"recipient_display":{"type":"string"},"provider":{"type":["string","null"]},"provider_message_id":{"type":["string","null"]},"subject":{"type":["string","null"]},"is_digest":{"type":"boolean"},"status":{"type":"string","description":"Most significant event so far (a bounce or open is never downgraded by a later `sent`).","enum":["sent","delayed","delivered","opened","clicked","bounced","complained","failed"]},"status_updated_at":{"type":"string","format":"date-time"},"failure_reason":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time","readOnly":true}}},"EscalationConfig":{"type":"object","properties":{"enabled":{"type":"boolean"},"escalation_window_days":{"type":"integer","minimum":1,"maximum":60,"description":"Days before expiry during which escalation may run."},"unopened_threshold_days":{"type":"integer","minimum":1,"maximum":30,"description":"Days a reminder may stay unopened before escalating."},"channels":{"type":"array","items":{"type":"string","enum":["sms","whatsapp","email"]}},"notify_manager":{"type":"boolean"}}},"EscalationConfigResponse":{"type":"object","required":["effective","stored","bounds","defaults","org_has_saved"],"properties":{"effective":{"$ref":"#/components/schemas/EscalationConfig"},"stored":{"oneOf":[{"$ref":"#/components/schemas/EscalationConfig"},{"type":"null"}]},"bounds":{"type":"object","properties":{"escalation_window_days":{"type":"object","properties":{"min":{"type":"integer"},"max":{"type":"integer"}}},"unopened_threshold_days":{"type":"object","properties":{"min":{"type":"integer"},"max":{"type":"integer"}}},"channels_allowed":{"type":"array","items":{"type":"string"}}}},"defaults":{"$ref":"#/components/schemas/EscalationConfig"},"org_has_saved":{"type":"boolean"}}},"TestReminderExpiryData":{"type":"object","description":"Sample expiry shown in the test message. Text is trimmed to a single short line; defaults are used for missing fields.","properties":{"name":{"type":"string","maxLength":120},"type":{"type":"string","maxLength":60,"description":"Email only."},"expiryDate":{"$ref":"#/components/schemas/IsoDate"},"priority":{"type":"string","maxLength":20,"examples":["High"]},"daysRemaining":{"type":"integer","default":30}}},"TestNotificationResult":{"type":"object","properties":{"message":{"type":"string"},"details":{"type":"object","properties":{"recipient":{"type":"string"},"expiry":{"type":"string"},"daysLeft":{"type":"integer"},"sent":{"type":"boolean"}}}}},"Team":{"type":"object","description":"A team (department) inside an organization.","required":["team_id","team_name"],"properties":{"team_id":{"type":"string","readOnly":true},"team_name":{"type":"string"},"created_at":{"$ref":"#/components/schemas/Timestamp","readOnly":true},"member_count":{"type":"integer","readOnly":true,"description":"Present in list responses."},"created_by":{"type":"string","readOnly":true,"description":"Email of the creator; present only in the createTeam response."}},"example":{"team_id":"tm_3Fh8qLx","team_name":"Front Desk","created_at":"2026-09-27T14:05:00.000Z","member_count":4}},"TeamList":{"type":"object","required":["teams"],"properties":{"teams":{"type":"array","items":{"$ref":"#/components/schemas/Team"}},"message":{"type":"string","description":"Present when there are no teams."},"allow_create":{"type":"boolean","description":"Present (true) when the organization has no teams."}}},"TeamLimitError":{"type":"object","description":"400 body from createTeam. Limit fields are present only when the plan's team limit is reached.","required":["message"],"properties":{"message":{"type":"string"},"current":{"type":"integer"},"limit":{"type":"integer"},"remaining":{"type":"integer"},"limitReached":{"type":"boolean"}}},"TeamMember":{"type":"object","description":"A user's membership in a team.","required":["membership_id","user_id","team_id"],"properties":{"membership_id":{"type":"string","readOnly":true},"user_id":{"type":"string"},"team_id":{"type":"string"},"role":{"type":"string","enum":["admin","editor","viewer"],"description":"The user's organization role (defaults to `editor` when unset)."},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"displayName":{"type":"string"}}},"TeamMembershipInput":{"type":"object","required":["teamId","userId"],"properties":{"teamId":{"type":"string"},"userId":{"type":"string"}}},"Invitation":{"type":"object","description":"An invitation to join an organization. The invitation `id` is also the token in the invitation link.","required":["id","email","status"],"properties":{"id":{"type":"string","readOnly":true},"email":{"type":"string","format":"email"},"organization_id":{"type":"string","readOnly":true},"invited_by":{"type":"string","readOnly":true,"description":"User id of the inviter."},"inviter_name":{"type":"string","readOnly":true},"status":{"type":"string","enum":["pending","accepted","cancelled"],"readOnly":true},"role":{"type":"string","enum":["admin","editor","viewer"],"description":"Role granted on acceptance. Present in the invite response, not in the pending list."},"created_at":{"$ref":"#/components/schemas/Timestamp","readOnly":true},"expires_at":{"$ref":"#/components/schemas/Timestamp","readOnly":true,"description":"7 days after creation."}}},"StatusCodedError":{"type":"object","description":"Error with an endpoint-specific `code` outside the shared catalog.","required":["error","code"],"properties":{"error":{"type":"string"},"code":{"type":"string","enum":["INVITATION_NOT_PENDING","INVITATION_EXPIRED","PLAN_UPGRADE_REQUIRED"]}}},"UserDependencies":{"type":"object","required":["expiriesCount","assignedExpiriesCount","totalExpiriesCount","contactsCount","teamsCount","teams","assignedExpiries","userData"],"properties":{"expiriesCount":{"type":"integer","description":"Expiries the user owns."},"assignedExpiriesCount":{"type":"integer","description":"Expiries assigned to (but not owned by) the user."},"totalExpiriesCount":{"type":"integer"},"contactsCount":{"type":"integer"},"teamsCount":{"type":"integer"},"teams":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}}},"assignedExpiries":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}}},"userData":{"type":"object","properties":{"email":{"type":"string"},"displayName":{"type":"string"},"role":{"type":"string"},"status":{"type":"string","enum":["active","deactivated"]}}}}},"AccountDetails":{"type":"object","description":"The caller's name plus organization plan, billing and credit details.","properties":{"name":{"type":"string"},"organization_name":{"type":["string","null"]},"subscription_id":{"type":"string","description":"Stripe product id of the plan, or `trial`."},"subscription_plan":{"type":"string"},"plan_name":{"type":"string","examples":["ProEssentials"]},"subscription_status":{"type":"string","examples":["active"]},"cancel_at_period_end":{"type":"boolean"},"last_payment_date":{"type":["string","null"]},"last_payment_amount":{"type":["number","null"]},"billing_period":{"type":"string","examples":["month"]},"next_billing_date":{"type":["string","null"]},"order_date":{"type":["string","null"]},"plan_expiry_date":{"type":["string","null"]},"account_created_at":{"description":"Organization creation time (ISO 8601 date-time string).","type":["string","object","null"]},"notification_credit":{"type":["object","null"],"properties":{"organization_id":{"type":"string"},"sms_balance":{"type":"integer","description":"Shared SMS and WhatsApp credit balance."},"email_balance":{"type":"integer"}}}}},"PersonalDataExport":{"type":"object","required":["exported_at","account"],"properties":{"exported_at":{"$ref":"#/components/schemas/Timestamp"},"account":{"type":"object","additionalProperties":true,"description":"Your full user profile record."},"organization":{"type":["object","null"],"additionalProperties":true,"description":"Full record (billing identifiers removed) for the owner; only `name` for other members."},"user_settings":{"type":["object","null"],"additionalProperties":true},"user_onboarding":{"type":["object","null"],"additionalProperties":true},"notification_preferences":{"type":["object","null"],"additionalProperties":true},"note":{"type":"string"}}},"WebhookSettings":{"type":"object","properties":{"teams_webhook":{"type":["string","null"],"format":"uri","description":"Microsoft Teams incoming webhook / Workflows URL (https)."},"slack_webhook":{"type":["string","null"],"format":"uri","description":"Slack incoming webhook URL (`https://hooks.slack.com/...`)."}}},"ReminderSetting":{"type":"object","description":"One row of the organization's default reminder sequence. A reminder fires `amount` `time_unit`s\n`period` the expiry date, at `time` in `timezone` (months count as 30 days).\n","required":["amount"],"properties":{"amount":{"type":"integer","minimum":0,"maximum":3650},"time_unit":{"type":"string","enum":["day","week","month"],"description":"The plural forms `days`, `weeks`, `months` are also accepted."},"period":{"type":"string","enum":["before","after"],"default":"before"},"time":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$","default":"09:00","description":"Local send time, 24-hour `HH:MM`."},"timezone":{"type":"string","description":"IANA timezone. On save it is taken from the top-level `timezone` field.","examples":["America/Chicago"]}},"example":{"amount":30,"time_unit":"day","period":"before","time":"09:00","timezone":"America/Chicago"}},"EscalationSettings":{"type":"object","required":["default_escalation_notification_days"],"properties":{"default_escalation_notification_days":{"type":"array","minItems":1,"description":"Days before expiry (0-365), sorted descending.","items":{"type":"integer","minimum":0,"maximum":365}}}},"OrganizationSettingsInput":{"type":"object","description":"Partial update; omitted fields are unchanged. String fields must be strings.","properties":{"name":{"type":"string","minLength":1},"industry":{"type":"string"},"timezone":{"type":"string","description":"IANA timezone."},"website":{"type":"string"},"phone":{"type":"string"},"address":{"type":"string"},"email_sender_name":{"type":["string","null"],"maxLength":40,"description":"From display name for reminder and invitation emails; no `<`, `>` or `\"`. Paid plans only; empty clears."},"email_sender_prefix":{"type":["string","null"],"pattern":"^[a-zA-Z0-9-]{0,32}$","description":"From address local part (stored lowercase). Paid plans only; empty clears."},"email_reminder_mode":{"type":"string","enum":["INDIVIDUAL","DIGEST"]},"digest_send_time":{"type":"string","pattern":"^([01]?\\d|2[0-3]):[0-5]\\d$","description":"Organization-local time the daily digest is sent."},"base_currency":{"type":"string","enum":["USD","EUR","GBP","INR","AUD","CAD","JPY","CNY","AED","SGD"]},"billing_email":{"type":["string","null"],"description":"Invoice email; empty or null clears."},"tax_id":{"type":["object","null"],"description":"Null clears.","properties":{"type":{"type":"string"},"value":{"type":"string"}}}}},"OrganizationSettings":{"type":"object","description":"Organization profile and settings. Unset strings come back as `''`.","properties":{"name":{"type":"string"},"industry":{"type":"string"},"timezone":{"type":"string","default":"UTC"},"logo_url":{"type":"string","readOnly":true,"description":"Public logo URL; set with uploadOrganizationLogo."},"website":{"type":"string"},"phone":{"type":"string"},"address":{"type":"string"},"email_sender_name":{"type":"string"},"email_sender_prefix":{"type":"string"},"email_sender_identity_enabled":{"type":"boolean","readOnly":true,"description":"Whether the plan allows a custom sender identity."},"email_reminder_mode":{"type":"string","enum":["INDIVIDUAL","DIGEST"]},"digest_send_time":{"type":"string","default":"09:00"},"base_currency":{"type":"string","default":"USD"},"billing_email":{"type":"string"},"tax_id":{"type":"object","properties":{"type":{"type":"string"},"value":{"type":"string"}}},"setup_checklist_acks":{"type":"object","readOnly":true,"additionalProperties":{"type":"boolean"},"description":"Onboarding checklist items acknowledged (see acknowledgeSetupItems)."},"expiry_form_sections":{"readOnly":true,"description":"Set with updateExpiryFormSettings; null when never configured (all sections visible).","oneOf":[{"$ref":"#/components/schemas/ExpiryFormSectionVisibility"},{"type":"null"}]},"expiry_form_section_order":{"readOnly":true,"type":["array","null"],"items":{"$ref":"#/components/schemas/ExpiryFormSectionKey"}}}},"ExpiryFormSectionKey":{"type":"string","enum":["recurring","checklist","custom_fields","directory","workflow"]},"ExpiryFormSectionVisibility":{"type":"object","description":"Visibility of optional expiry form sections; a section is visible unless set to `false`.","properties":{"recurring":{"type":"boolean"},"checklist":{"type":"boolean"},"custom_fields":{"type":"boolean"},"directory":{"type":"boolean"},"workflow":{"type":"boolean"}}},"AvailabilityRecord":{"type":"object","description":"A leave period. `created_at` / `updated_at` are ISO 8601 date-time strings (UTC).","properties":{"id":{"type":"string","readOnly":true},"user_id":{"type":"string","readOnly":true},"organization_id":{"type":"string","readOnly":true},"start_date":{"$ref":"#/components/schemas/IsoDate"},"end_date":{"$ref":"#/components/schemas/IsoDate"},"reason":{"type":"string"},"created_by":{"type":"string","readOnly":true},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 date-time string (UTC).","readOnly":true},"updated_at":{"type":"string","format":"date-time","description":"ISO 8601 date-time string (UTC).","readOnly":true},"user_name":{"type":"string","readOnly":true,"description":"Present in getTeamAvailability."},"user_photo":{"type":["string","null"],"readOnly":true,"description":"Present in getTeamAvailability."}}},"BackupRuleTypeBackup":{"type":"object","required":["expiry_type_id","backup_user_id"],"properties":{"expiry_type_id":{"type":"string"},"expiry_type_name":{"type":"string"},"backup_user_id":{"type":"string"},"backup_user_name":{"type":"string","readOnly":true}}},"BackupRuleInput":{"type":"object","required":["primary_user_id","backup_type"],"properties":{"primary_user_id":{"type":"string","description":"User whose expiries are covered while they are on leave."},"backup_type":{"type":"string","enum":["general","type_based","mixed"]},"general_backup_user_id":{"type":["string","null"]},"fallback_user_id":{"type":["string","null"],"description":"For `mixed`, catches expiry types without a type backup."},"type_backups":{"type":"array","items":{"$ref":"#/components/schemas/BackupRuleTypeBackup"}}}},"BackupRule":{"allOf":[{"$ref":"#/components/schemas/BackupRuleInput"},{"type":"object","properties":{"id":{"type":"string","readOnly":true},"organization_id":{"type":"string","readOnly":true},"primary_user_name":{"type":"string","readOnly":true},"general_backup_user_name":{"type":"string","readOnly":true},"fallback_user_name":{"type":"string","readOnly":true},"updated_by":{"type":"string","readOnly":true},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 date-time string (UTC).","readOnly":true},"updated_at":{"type":"string","format":"date-time","description":"ISO 8601 date-time string (UTC).","readOnly":true}}}]},"ReassignmentLog":{"type":"object","properties":{"id":{"type":"string","readOnly":true},"expiry_id":{"type":"string"},"expiry_name":{"type":"string"},"expiry_date":{"$ref":"#/components/schemas/IsoDate"},"from_user_id":{"type":"string"},"from_user_name":{"type":"string"},"to_user_id":{"type":"string"},"to_user_name":{"type":"string"},"reason":{"type":"string"},"triggered_by":{"type":"string","examples":["auto"]},"organization_id":{"type":"string"},"timestamp":{"type":"string","format":"date-time","description":"ISO 8601 date-time string (UTC).","readOnly":true}}},"LegacyLimitCheckError":{"type":"object","properties":{"allowed":{"type":"boolean","const":false},"message":{"type":"string"},"error":{"type":"string"}}},"UsageEndpointError":{"type":"object","properties":{"success":{"type":"boolean","const":false},"error":{"type":"string"},"details":{"type":"string"}}},"Activity":{"type":"object","description":"One activity log entry (a create, update, delete or other change on a record).","properties":{"id":{"type":"string","readOnly":true},"entity_type":{"type":"string","description":"expiry, contact, member, template, workflow, workflow_section, workflow_step, backup_rule.","examples":["expiry"]},"entity_id":{"type":"string"},"entity_name":{"type":["string","null"]},"activity_type":{"type":"string","description":"created, updated, deleted, archived, restored, marked_done, reopened, file_uploaded, file_deleted, comment_added, comment_deleted (others may appear).","examples":["updated"]},"user_id":{"type":"string","readOnly":true},"user_name":{"type":"string"},"organization_id":{"type":"string","readOnly":true},"changes":{"type":"array","description":"Field-level changes (for updates).","items":{"type":"object","properties":{"field":{"type":"string"},"field_label":{"type":"string"},"old_value":{"description":"Previous value (any type or null)."},"new_value":{"description":"New value (any type or null)."},"change_type":{"type":"string","enum":["primitive","array","object"]}}}},"metadata":{"type":"object","additionalProperties":true,"description":"Extra context that depends on the activity (priority, type, changes_count, file_count ...)."},"created_at":{"$ref":"#/components/schemas/Timestamp"},"timestamp":{"$ref":"#/components/schemas/Timestamp"}}},"ActivityList":{"type":"object","properties":{"activities":{"type":"array","items":{"$ref":"#/components/schemas/Activity"}}}},"NotificationPricingResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"whatsapp":{"type":"object","properties":{"credits_per_message":{"type":"integer"},"description":{"type":"string"}}},"sms":{"type":"object","properties":{"description":{"type":"string"},"pricing_tiers":{"type":"object","additionalProperties":{"type":"object","properties":{"credits_per_segment":{"type":"integer"},"description":{"type":"string"},"countries":{"type":"array","items":{"type":"string"}}}}},"segments_info":{"type":"object","additionalProperties":{"type":"object","properties":{"chars_per_segment":{"type":"integer"},"description":{"type":"string"}}}}}},"email":{"type":"object","properties":{"credits_per_email":{"type":"integer"},"description":{"type":"string"}}},"examples":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"destination":{"type":"string"},"message_length":{"type":"string"},"credits_required":{"type":"integer"}}}}}}}},"SmsCostResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"phone_number":{"type":"string"},"country":{"type":"string","description":"ISO country code."},"country_name":{"type":"string"},"message_length":{"type":"integer"},"segments":{"type":"integer"},"credits_per_segment":{"type":"integer"},"total_credits":{"type":"integer"},"has_unicode":{"type":"boolean"}}}}},"PaymentConfigResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"stripe":{"type":"object","properties":{"publishableKey":{"type":"string"},"environment":{"type":"string","enum":["development","production"]}}},"razorpay":{"type":"object","properties":{"key":{"type":"string"},"environment":{"type":"string","enum":["development","production"]}}}}}}},"StorageUsageResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"organization_id":{"type":"string","readOnly":true},"total_bytes_used":{"type":"integer"},"total_used_formatted":{"type":"string"},"limit_bytes":{"type":"integer"},"limit_formatted":{"type":"string"},"percentage_used":{"type":"string","description":"Percentage with two decimals, as a string."},"plan_name":{"type":"string"},"remaining_bytes":{"type":"integer"},"remaining_formatted":{"type":"string"}}}}},"StorageUploadCheck":{"type":"object","properties":{"success":{"type":"boolean"},"allowed":{"type":"boolean"},"reason":{"type":"string","description":"Present when not allowed (for example `Storage limit exceeded`)."},"current_usage":{"type":"integer"},"current_usage_formatted":{"type":"string"},"limit":{"type":"integer"},"limit_formatted":{"type":"string"},"file_size":{"type":"integer"},"file_size_formatted":{"type":"string"}}},"LegacyResourceLimitCheck":{"type":"object","properties":{"allowed":{"type":"boolean"},"message":{"type":"string"},"current":{"type":"integer"},"limit":{"type":["integer","null"],"description":"null when unlimited."},"remaining":{"oneOf":[{"type":"integer"},{"type":"string","const":"Unlimited"}]},"planName":{"type":"string"}}},"PlanLimitValues":{"type":"object","description":"Plan limits. Unlimited values serialize as `null`. `storage` is in bytes; `ai_scans` and `collection_requests` are per calendar month.","additionalProperties":true,"properties":{"name":{"type":"string"},"expiries":{"type":["integer","null"]},"contacts":{"type":["integer","null"]},"storage":{"type":["integer","null"]},"users":{"type":["integer","null"]},"email_credits":{"type":["integer","null"]},"message_credits":{"type":["integer","null"]},"workflows":{"type":["integer","null"]},"workflow_runs":{"type":["integer","null"]},"ai_scans":{"type":["integer","null"]},"teams":{"type":["integer","null"]},"categories":{"type":["integer","null"]},"collection_templates":{"type":["integer","null"]},"collection_requests":{"type":["integer","null"]},"email_templates":{"type":["integer","null"]},"custom_field_templates":{"type":["integer","null"]},"carry_over":{"type":"boolean"}}},"OrganizationUsageCounts":{"type":"object","description":"Current usage counters. `storage` is in bytes; `ai_scans` and `collection_requests` count the current calendar month.","additionalProperties":true,"properties":{"expiries":{"type":"integer"},"contacts":{"type":"integer"},"storage":{"type":"integer"},"users":{"type":"integer"},"workflows":{"type":"integer"},"workflow_runs":{"type":"integer"},"ai_scans":{"type":"integer"},"teams":{"type":"integer"},"categories":{"type":"integer"},"collection_templates":{"type":"integer"},"collection_requests":{"type":"integer"},"email_templates":{"type":"integer"},"custom_field_templates":{"type":"integer"}}},"AdditionalUserLicensesState":{"type":"object","properties":{"count":{"type":"integer"},"period":{"type":["string","null"],"examples":["monthly"]},"status":{"type":"string","examples":["none","active","past_due","canceled"]},"stripe_subscription_id":{"type":["string","null"]},"cancel_at_period_end":{"type":"boolean"},"last_invoice_at":{"description":"Timestamp or null."},"last_payment_failed_at":{"description":"Timestamp or null."}}},"OrganizationSubscriptionDetails":{"type":"object","properties":{"subscription_id":{"type":["string","null"],"description":"Stripe product id of the plan, or null on Trial."},"plan_name":{"type":"string","examples":["Pro Essentials"]},"status":{"type":"string","description":"active or expired (from the account expiry date), otherwise the stored subscription status."},"limits":{"$ref":"#/components/schemas/PlanLimitValues"},"features":{"type":"array","items":{"type":"string"}},"has_custom_limits":{"type":"boolean"},"addons":{"type":"object","properties":{"status":{"type":"string"},"period":{"type":["string","null"]},"stripe_subscription_id":{"type":["string","null"]},"cancel_at_period_end":{"type":"boolean"},"items":{"type":"object","additionalProperties":{"type":"object","properties":{"quantity":{"type":"integer"},"unit_size":{"type":"integer"}}}}}},"additional_user_licenses":{"$ref":"#/components/schemas/AdditionalUserLicensesState"},"base_user_limit":{"type":["integer","null"],"description":"Plan seats before additional licenses."}}},"UsageSummary":{"type":"object","properties":{"subscription":{"type":"object","properties":{"id":{"type":["string","null"]},"plan_name":{"type":"string"},"status":{"type":"string"},"features":{"type":"array","items":{"type":"string"}}}},"limits":{"$ref":"#/components/schemas/PlanLimitValues"},"usage":{"$ref":"#/components/schemas/OrganizationUsageCounts"},"remaining":{"type":"object","description":"Remaining capacity; `Unlimited` for unlimited resources.","additionalProperties":{"oneOf":[{"type":"integer"},{"type":"string","const":"Unlimited"}]}},"percentage":{"type":"object","description":"Percent used (0 for unlimited).","additionalProperties":{"type":"integer"}},"ai_scans_reset_at":{"$ref":"#/components/schemas/Timestamp"},"ai_scans_bucket":{"type":"string","description":"Current monthly bucket, YYYY-MM."},"base_user_limit":{"type":["integer","null"]},"additional_user_licenses":{"$ref":"#/components/schemas/AdditionalUserLicensesState"}}},"ResourceLimitCheck":{"type":"object","properties":{"allowed":{"type":"boolean"},"reason":{"type":"string","description":"Present when not allowed, or when the check itself failed."},"current":{"type":"integer"},"limit":{"oneOf":[{"type":"integer"},{"type":"string","const":"Unlimited"}]},"remaining":{"oneOf":[{"type":"integer"},{"type":"string","const":"Unlimited"}]}}},"AiDocumentMimeType":{"type":"string","default":"application/pdf","enum":["application/pdf","image/png","image/jpeg","image/jpg","image/webp","image/gif","image/tiff"]},"ExtractedDocument":{"type":"object","description":"One item the AI found in a document. Values come from the model and may be null.","properties":{"name":{"type":"string"},"document_type":{"type":"string","examples":["license","certificate","insurance","contract","permit","warranty","subscription","membership","visa","passport","registration","other"]},"issuing_authority":{"type":["string","null"]},"holder_name":{"type":["string","null"]},"reference_number":{"type":["string","null"]},"start_date":{"oneOf":[{"$ref":"#/components/schemas/IsoDate"},{"type":"null"}]},"expiry_date":{"oneOf":[{"$ref":"#/components/schemas/IsoDate"},{"type":"null"}]},"vendor":{"type":["string","null"]},"notes":{"type":["string","null"]},"confidence":{"type":"string","enum":["high","medium","low"]},"extracted_fields":{"type":"array","maxItems":15,"items":{"type":"object","properties":{"label":{"type":"string"},"value":{"type":"string"},"category":{"type":"string","enum":["financial","legal","compliance","identity","contact","technical","other"]}}}}}},"AiScanUsage":{"type":"object","description":"AI scan quota after this call.","properties":{"used":{"type":"integer"},"limit":{"oneOf":[{"type":"integer"},{"type":"string","const":"Unlimited"}]},"remaining":{"oneOf":[{"type":"integer"},{"type":"string","const":"Unlimited"}]},"resets_at":{"$ref":"#/components/schemas/Timestamp"}}},"DocumentExtractionResponse":{"type":"object","properties":{"success":{"type":"boolean"},"extraction":{"type":"object","properties":{"documents":{"type":"array","items":{"$ref":"#/components/schemas/ExtractedDocument"}}}},"fileName":{"type":"string"},"ai_scans":{"$ref":"#/components/schemas/AiScanUsage"}}},"AiScanFeatureError":{"type":"object","required":["error"],"properties":{"error":{"type":"string","const":"ai_scan_feature_not_available"},"message":{"type":"string"},"plan_required":{"type":"array","items":{"type":"string"}},"upgrade_url":{"type":"string"}}},"AiScanQuotaError":{"type":"object","description":"Body returned with HTTP 429 by extractDocument, extractDocumentDirect, bulkExtractDocuments and mapImportColumns when the monthly AI scan quota is used up.","required":["error"],"properties":{"error":{"type":"string","const":"ai_scan_quota_exceeded"},"message":{"type":"string"},"used":{"type":"integer"},"limit":{"type":"integer"},"remaining":{"type":"integer"},"resets_at":{"$ref":"#/components/schemas/Timestamp"},"bucket":{"type":"string","description":"YYYY-MM (not returned by mapImportColumns)."},"upgrade_url":{"type":"string"}},"example":{"error":"ai_scan_quota_exceeded","message":"You have reached your Pro Essentials plan limit of 25 ai_scans. Please upgrade your plan to add more.","used":25,"limit":25,"remaining":0,"resets_at":"2026-10-01T00:00:00.000Z","bucket":"2026-09","upgrade_url":"/dashboard/manage-subscription"}},"ImportColumnMapping":{"type":"object","properties":{"success":{"type":"boolean"},"mapping":{"type":"object","description":"Original header to target field.","additionalProperties":{"type":"string","enum":["name","expiry_date","type","start_date","priority","notes","url","email","notification_email","audit_date","compliance_id","value","currency","penalty_amount","custom","ignore"]}},"custom_fields":{"type":"array","description":"One entry for every header mapped to `custom`.","items":{"type":"object","properties":{"header":{"type":"string"},"label":{"type":"string"},"type":{"type":"string","enum":["text","textarea","number","date","phone","email","url","currency"]}}}},"date_format":{"type":["string","null"],"examples":["DD/MM/YYYY"]},"confidence":{"type":"string","enum":["high","medium","low"]},"warnings":{"type":"array","maxItems":5,"items":{"type":"string"}},"ai_scans":{"$ref":"#/components/schemas/AiScanUsage"}}},"EmailIngestionLogEntry":{"type":"object","properties":{"id":{"type":"string","readOnly":true},"organization_id":{"type":"string","readOnly":true},"from_email":{"type":"string"},"subject":{"type":"string"},"attachment_count":{"type":"integer"},"created":{"type":"integer","description":"Expiries created."},"updated":{"type":"integer","description":"Expiries updated."},"results":{"type":"array","items":{"type":"object","properties":{"file":{"type":"string"},"item":{"type":"string"},"action":{"type":"string","enum":["created","updated"]},"expiryId":{"type":"string"},"skipped":{"type":"boolean"},"reason":{"type":"string"},"error":{"type":"string"}}}},"processed_at":{"$ref":"#/components/schemas/Timestamp"}}},"ForecastExpiryItem":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"expiry_date":{"$ref":"#/components/schemas/IsoDate"},"type":{"type":["string","null"]},"priority":{"type":["string","null"]},"assigned_to":{"type":["string","null"],"description":"User id."}}},"ExpiryForecast":{"type":"object","properties":{"success":{"type":"boolean"},"summary":{"type":"object","properties":{"totalActive":{"type":"integer"},"totalOverdue":{"type":"integer"},"expiringThisMonth":{"type":"integer"},"expiringNextMonth":{"type":"integer"},"highPriorityAtRisk":{"type":"integer"},"avgProcessingDays":{"type":["integer","null"]},"medianProcessingDays":{"type":["integer","null"]},"completionRate":{"type":"integer","description":"Percent."},"busiest_month":{"type":["string","null"],"description":"YYYY-MM."}}},"renewalForecast":{"type":"array","description":"One entry per month in the horizon.","items":{"type":"object","properties":{"month":{"type":"string","description":"YYYY-MM."},"count":{"type":"integer"},"highPriority":{"type":"integer"},"items":{"type":"array","items":{"$ref":"#/components/schemas/ForecastExpiryItem"}}}}},"workload":{"type":"array","items":{"type":"object","properties":{"userId":{"type":"string"},"name":{"type":"string"},"total":{"type":"integer"},"thisMonth":{"type":"integer"},"nextMonth":{"type":"integer"},"overdue":{"type":"integer"},"items":{"type":"array","items":{}}}}},"overdueAging":{"type":"object","properties":{"1-7 days":{"type":"integer"},"8-30 days":{"type":"integer"},"31-90 days":{"type":"integer"},"90+ days":{"type":"integer"},"total":{"type":"integer"},"items":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/ForecastExpiryItem"},{"type":"object","properties":{"days_overdue":{"type":"integer"}}}]}}}},"typeAnalysis":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"total":{"type":"integer"},"active":{"type":"integer","description":"Count of completed (done) expiries of this type."},"expired":{"type":"integer"},"upcoming":{"type":"integer"},"avgDurationDays":{"type":["integer","null"]}}}},"riskItems":{"type":"array","description":"High or Medium priority expiries due in the next 30 days.","items":{"allOf":[{"$ref":"#/components/schemas/ForecastExpiryItem"},{"type":"object","properties":{"days_left":{"type":"integer"},"assignee_name":{"type":"string"}}}]}}}},"AssigneeProductivity":{"type":"object","properties":{"userId":{"type":"string","description":"User id or `Unassigned`."},"name":{"type":"string"},"total":{"type":"integer"},"completed":{"type":"integer"},"completedOnTime":{"type":"integer"},"completedLate":{"type":"integer"},"pending":{"type":"integer"},"overdue":{"type":"integer"},"daysBeforeExpiry":{"type":"array","items":{"type":"integer"}},"onTimeRate":{"type":["integer","null"],"description":"Percent."},"avgDaysBeforeExpiry":{"type":["integer","null"]}}},"V2Error":{"type":"object","description":"The only error shape in v2. Branch on `code`; show `message`; quote `request_id` to support.","required":["error"],"properties":{"error":{"type":"object","required":["code","message","details","request_id"],"properties":{"code":{"type":"string","description":"Stable code: `VALIDATION_FAILED`, `UNAUTHORIZED`, `SESSION_EXPIRED`, `TOKEN_INVALID`, `FORBIDDEN`,\n`PLAN_LIMIT_REACHED`, `STORAGE_LIMIT_REACHED`, `NOT_FOUND`, `METHOD_NOT_ALLOWED`, `CONFLICT`, `EMAIL_EXISTS`,\n`GROUP_EXISTS`, `FOLDER_NOT_EMPTY`, `CLIENT_HAS_PROJECTS`, `TOO_MANY_ATTACHMENTS`, `INVALID_STATE`,\n`UNSUPPORTED_FILE_TYPE`, `FILE_TOO_LARGE`, `UPLOAD_EXPIRED`, `UPLOAD_MISSING`, `UPLOAD_MISMATCH`,\n`IDEMPOTENCY_IN_PROGRESS`, `IDEMPOTENCY_KEY_REUSED`, `RATE_LIMITED`, `INTERNAL_ERROR`. Treat unknown codes by HTTP status.\n"},"message":{"type":"string","description":"Human-readable, safe to show to end users."},"details":{"description":"Structured detail (fields, limits, allowed methods) or null."},"request_id":{"type":"string"}}}}},"V2ExpiryCreate":{"description":"Expiry fields as stored (snake_case). `name` and `expiry_date` are required in v2.","allOf":[{"$ref":"#/components/schemas/ExpiryInput"},{"type":"object","required":["name","expiry_date"],"properties":{"expiry_date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}}}]},"V2Expiry":{"description":"An expiry as stored, with ISO 8601 timestamps. `share_password` is never returned.","allOf":[{"$ref":"#/components/schemas/Expiry"},{"type":"object","properties":{"has_share_password":{"type":"boolean","readOnly":true}}}]},"V2ExpiryPage":{"allOf":[{"$ref":"#/components/schemas/Page"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/V2Expiry"}},"next_cursor":{"type":["string","null"],"description":"Null on the last page."}}}]},"V2ContactInput":{"type":"object","description":"Writable contact fields, named as stored. Unknown fields are rejected.","additionalProperties":false,"properties":{"first_name":{"type":"string"},"last_name":{"type":"string","description":"Defaults to empty on create."},"email":{"type":["string","null"],"format":"email"},"sms_phone":{"type":["string","null"]},"whatsapp_phone":{"type":["string","null"]},"contact_type":{"type":["string","null"]},"job_title":{"type":["string","null"]},"timezone":{"type":"string","description":"IANA timezone; defaults to Europe/London on create."},"contact_group":{"type":["string","null"],"description":"Group name; a new name creates the group."},"is_default":{"type":"boolean"},"email_opt_in":{"type":"boolean"},"sms_opt_in":{"type":"boolean"},"whatsapp_opt_in":{"type":"boolean"},"sendNotifications":{"type":"boolean","description":"Stored camelCase (legacy name)."}}},"V2Contact":{"description":"A contact as stored, with ISO 8601 timestamps.","allOf":[{"$ref":"#/components/schemas/V2ContactInput"},{"type":"object","properties":{"id":{"type":"string","readOnly":true},"organization_id":{"type":"string","readOnly":true},"user_id":{"type":"string","readOnly":true},"created_at":{"$ref":"#/components/schemas/Timestamp"},"updated_at":{"$ref":"#/components/schemas/Timestamp"}}}],"example":{"id":"c_8Hk2pQ","organization_id":"org_northwind","user_id":"u_71bXq","first_name":"Priya","last_name":"Shah","email":"priya.shah@northwinddental.example","sms_phone":"+15550123456","whatsapp_phone":null,"contact_type":"Staff","job_title":"Practice Manager","timezone":"America/New_York","contact_group":"Licensing","is_default":false,"email_opt_in":true,"sms_opt_in":true,"whatsapp_opt_in":false,"sendNotifications":true,"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}},"V2ContactPage":{"allOf":[{"$ref":"#/components/schemas/Page"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/V2Contact"}},"next_cursor":{"type":["string","null"],"description":"Null on the last page."}}}]},"V2ReminderInput":{"type":"object","additionalProperties":false,"required":["amount","time_unit","period"],"properties":{"id":{"type":"string","description":"PATCH only: an existing reminder of this expiry to update in place."},"amount":{"type":"integer","minimum":0,"maximum":3650},"time_unit":{"type":"string","enum":["day","week","month","year"]},"period":{"type":"string","enum":["before","after"],"description":"Before or after the expiry date."},"time":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$","default":"09:00","description":"Local time in `timezone`."},"email_enabled":{"type":"boolean","default":true},"sms_enabled":{"type":"boolean","default":true},"whatsapp_enabled":{"type":"boolean","default":true}}},"V2ReminderSetInput":{"type":"object","additionalProperties":false,"required":["reminders"],"properties":{"reminders":{"type":"array","minItems":1,"maxItems":20,"items":{"$ref":"#/components/schemas/V2ReminderInput"}},"timezone":{"type":"string","description":"IANA timezone; default the organization's timezone, else UTC.","example":"America/New_York"}}},"V2Reminder":{"type":"object","description":"An expiry reminder as stored (`expiry_reminders`), with ISO 8601 timestamps.","properties":{"id":{"type":"string"},"expiry_id":{"type":"string"},"organization_id":{"type":"string"},"user_id":{"type":"string"},"amount":{"type":"integer"},"time_unit":{"type":"string"},"period":{"type":"string"},"time":{"type":"string"},"timezone":{"type":"string"},"reminder_date":{"type":"string","format":"date-time"},"scheduled_at":{"type":"string","format":"date-time","description":"When it will be sent (UTC)."},"email_enabled":{"type":"boolean"},"sms_enabled":{"type":"boolean"},"whatsapp_enabled":{"type":"boolean"},"status":{"type":"string","enum":["pending","sent","failed","cancelled"]},"sent_at":{"type":["string","null"]},"error_message":{"type":["string","null"]},"created_at":{"$ref":"#/components/schemas/Timestamp"},"updated_at":{"$ref":"#/components/schemas/Timestamp"}},"example":{"id":"rem_7Jd2","expiry_id":"exp_4Tq9sLm2","organization_id":"org_northwind","amount":30,"time_unit":"day","period":"before","time":"09:00","timezone":"America/New_York","reminder_date":"2026-09-15T00:00:00.000Z","scheduled_at":"2026-09-15T13:00:00.000Z","email_enabled":true,"sms_enabled":true,"whatsapp_enabled":true,"status":"pending","sent_at":null}},"V2ReminderList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/V2Reminder"}}}},"V2ReminderPage":{"allOf":[{"$ref":"#/components/schemas/Page"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/V2Reminder"}},"next_cursor":{"type":["string","null"]}}}]},"V2AttachmentPage":{"allOf":[{"$ref":"#/components/schemas/Page"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ExpiryAttachment"}},"next_cursor":{"type":"null"}}}]},"V2CommentInput":{"type":"object","additionalProperties":false,"required":["text"],"properties":{"text":{"type":"string","maxLength":5000,"description":"Plain text."}}},"V2Comment":{"type":"object","description":"An expiry comment as stored (`expiry_comments`, the web app's camelCase names).","properties":{"id":{"type":"string"},"expiryId":{"type":"string"},"organization_id":{"type":"string","description":"Missing on older comments."},"text":{"type":"string","description":"HTML-escaped, newlines as `<br>`."},"userId":{"type":"string"},"username":{"type":"string"},"userEmail":{"type":"string"},"edited":{"type":"boolean"},"source":{"type":"string","description":"`api` for comments made through the API."},"timestamp":{"$ref":"#/components/schemas/Timestamp"}},"example":{"id":"cm_9Qa1","expiryId":"exp_4Tq9sLm2","organization_id":"org_northwind","text":"Board confirmed the renewal fee.","userId":"u_71bXq","username":"Priya Shah","userEmail":"priya.shah@northwinddental.example","source":"api","timestamp":"2026-09-27T14:20:00.000Z"}},"V2CommentPage":{"allOf":[{"$ref":"#/components/schemas/Page"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/V2Comment"}},"next_cursor":{"type":["string","null"]}}}]},"V2FolderInput":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string","maxLength":255},"parent_folder_id":{"type":["string","null"],"description":"A folder in your organization; null = top level."}}},"V2Folder":{"type":"object","description":"A folder as stored.","properties":{"id":{"type":"string"},"name":{"type":"string"},"parent_folder_id":{"type":["string","null"]},"folder_path":{"type":"string","example":"/Licenses/Dental"},"organization_id":{"type":"string"},"user_id":{"type":"string"},"is_deleted":{"type":"boolean","description":"Always false in v2 (deleted folders are 404)."},"created_at":{"$ref":"#/components/schemas/Timestamp"},"updated_at":{"$ref":"#/components/schemas/Timestamp"}}},"V2FolderPage":{"allOf":[{"$ref":"#/components/schemas/Page"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/V2Folder"}},"next_cursor":{"type":["string","null"]}}}]},"V2ContactGroupInput":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string","maxLength":100},"color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"sort_order":{"type":"integer"}}},"V2ContactGroup":{"type":"object","description":"A contact group as stored (`contact_groups`).","properties":{"id":{"type":"string"},"organization_id":{"type":"string"},"name":{"type":"string"},"color":{"type":"string"},"sort_order":{"type":"integer"},"created_by":{"type":["string","null"]},"created_at":{"$ref":"#/components/schemas/Timestamp"},"updated_at":{"$ref":"#/components/schemas/Timestamp"}}},"V2ContactGroupPage":{"allOf":[{"$ref":"#/components/schemas/Page"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/V2ContactGroup"}},"next_cursor":{"type":["string","null"]}}}]},"V2CollectionTemplate":{"type":"object","description":"A Document Collection template as stored (`collectionTemplates`).","properties":{"id":{"type":"string"},"organization_id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"status":{"type":"string","enum":["active","archived","request_draft","request_sent"]},"pages":{"type":"array","items":{"$ref":"#/components/schemas/CollectionTemplatePage"}},"intro_email":{"type":["object","null"]},"schema_version":{"type":"integer"},"created_by":{"type":"string"},"created_by_name":{"type":"string"},"created_at":{"$ref":"#/components/schemas/Timestamp"},"updated_at":{"$ref":"#/components/schemas/Timestamp"}}},"V2CollectionTemplatePage":{"allOf":[{"$ref":"#/components/schemas/Page"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/V2CollectionTemplate"}},"next_cursor":{"type":["string","null"]}}}]},"V2CollectionRequestInput":{"type":"object","additionalProperties":false,"required":["template_id"],"description":"`contact_ids` or `group_id` (or both) is required.","properties":{"template_id":{"type":"string"},"contact_ids":{"type":"array","items":{"type":"string"},"maxItems":200},"group_id":{"type":["string","null"]},"expiry_id":{"type":["string","null"]},"password":{"type":["string","null"],"minLength":4},"expires_at":{"type":["string","null"],"format":"date-time","description":"The link stops working after this."},"name":{"type":["string","null"],"maxLength":200},"due_date":{"type":["string","null"],"description":"\"Please submit by\" date (ISO 8601)."},"reminders":{"type":"array","maxItems":20,"description":"Reminder schedule override (after the send date); default the organization's reminder settings.","items":{"type":"object","properties":{"amount":{"type":"integer"},"time_unit":{"type":"string","enum":["day","week","month","year"]},"time":{"type":"string"}}}}}},"V2CollectionRequest":{"type":"object","description":"A Document Collection request as stored (`collectionRequests`), with ISO 8601 timestamps. The link token\nhash, password hash, lockout counters and unsubmitted draft are never returned.\n","properties":{"id":{"type":"string"},"organization_id":{"type":"string"},"name":{"type":["string","null"]},"template_id":{"type":"string"},"template_name":{"type":"string"},"contact_id":{"type":"string"},"contact_name":{"type":"string"},"contact_email":{"type":"string"},"expiry_id":{"type":["string","null"]},"group_id":{"type":["string","null"]},"status":{"type":"string","enum":["pending","viewed","completed","cancelled","expired"]},"has_password":{"type":"boolean"},"archived":{"type":"boolean"},"paused":{"type":"boolean"},"expires_at":{"type":["string","null"]},"due_date":{"type":["string","null"]},"sent_at":{"type":"string"},"viewed_at":{"type":["string","null"]},"completed_at":{"type":["string","null"]},"cancelled_at":{"type":["string","null"]},"resend_count":{"type":"integer"},"latest_review_status":{"type":["string","null"]},"created_by":{"type":"string"},"created_at":{"$ref":"#/components/schemas/Timestamp"},"updated_at":{"$ref":"#/components/schemas/Timestamp"}},"example":{"id":"req_3Hs8","organization_id":"org_northwind","name":"W-9 for 2026","template_id":"tpl_W9x2","template_name":"W-9","contact_id":"c_8Hk2pQ","contact_name":"Priya Shah","contact_email":"priya.shah@northwinddental.example","expiry_id":"exp_4Tq9sLm2","status":"pending","has_password":false,"archived":false,"paused":false,"sent_at":"2026-09-27T14:30:00.000Z","resend_count":0,"created_at":"2026-09-27T14:30:00.000Z","updated_at":"2026-09-27T14:30:00.000Z"}},"V2CollectionRequestPage":{"allOf":[{"$ref":"#/components/schemas/Page"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/V2CollectionRequest"}},"next_cursor":{"type":["string","null"]}}}]},"V2Submission":{"type":"object","description":"A Document Collection submission as stored (`collectionSubmissions`); files as metadata only.","properties":{"id":{"type":"string"},"organization_id":{"type":"string"},"request_id":{"type":"string"},"template_id":{"type":"string"},"submitted_at":{"$ref":"#/components/schemas/Timestamp"},"review_status":{"type":"string","enum":["pending","approved","changes_requested","rejected"]},"revision":{"type":"integer"},"responses":{"type":"object","additionalProperties":true,"description":"Field id -> answer."},"field_reviews":{"type":"object","additionalProperties":true},"review_history":{"type":"array","items":{"type":"object"}},"files":{"type":"array","items":{"type":"object","properties":{"field_id":{"type":["string","null"]},"name":{"type":["string","null"]},"content_type":{"type":["string","null"]},"size":{"type":["integer","null"]}}}}}},"V2SubmissionPage":{"allOf":[{"$ref":"#/components/schemas/Page"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/V2Submission"}},"next_cursor":{"type":["string","null"]}}}]},"V2ComplianceClientInput":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string","maxLength":200},"primary_contact_name":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"},"website":{"type":"string"},"address":{"type":"string"},"industry":{"type":"string"},"status":{"type":"string","enum":["Active","Inactive"]},"notes":{"type":"string"},"custom_fields":{"type":"object","additionalProperties":{"type":"string"},"maxProperties":100}}},"V2ComplianceClient":{"description":"A compliance client as stored (`compliance_clients`).","allOf":[{"$ref":"#/components/schemas/V2ComplianceClientInput"},{"type":"object","properties":{"id":{"type":"string"},"organization_id":{"type":"string"},"user_id":{"type":"string"},"is_archived":{"type":"boolean"},"archived_at":{"type":["string","null"]},"created_at":{"$ref":"#/components/schemas/Timestamp"},"updated_at":{"$ref":"#/components/schemas/Timestamp"}}}]},"V2ComplianceClientPage":{"allOf":[{"$ref":"#/components/schemas/Page"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/V2ComplianceClient"}},"next_cursor":{"type":["string","null"]}}}]},"WebhookEventType":{"type":"string","enum":["expiry.created","expiry.updated","expiry.completed","expiry.deleted","collection_request.completed","collection_submission.received"]},"WebhookSubscriptionInput":{"type":"object","required":["url","events"],"properties":{"url":{"type":"string","format":"uri","maxLength":2048,"description":"https URL on a public hostname (port 443 or 8443)."},"events":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/WebhookEventType"}},"description":{"type":"string","maxLength":200}}},"WebhookSubscriptionIdRequest":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"Subscription id."}}},"WebhookSubscription":{"type":"object","properties":{"id":{"type":"string","readOnly":true},"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEventType"}},"description":{"type":"string"},"active":{"type":"boolean"},"disabled_reason":{"type":["string","null"],"enum":["manual","too_many_failures","gone",null],"readOnly":true,"description":"Why the subscription is off - `too_many_failures` after 20 failed deliveries in a row, `gone` after a 410 response."},"disabled_at":{"type":["string","null"],"format":"date-time","readOnly":true},"consecutive_failures":{"type":"integer","readOnly":true,"description":"Deliveries in a row that used up all retries."},"last_delivery_at":{"type":["string","null"],"format":"date-time","readOnly":true},"last_delivery_status":{"type":["string","null"],"enum":["succeeded","failed",null],"readOnly":true},"secret_hint":{"type":"string","readOnly":true,"description":"Last 4 characters of the signing secret, e.g. `whsec_...9f2c`."},"created_by":{"type":"string","readOnly":true},"created_at":{"$ref":"#/components/schemas/Timestamp"},"updated_at":{"$ref":"#/components/schemas/Timestamp"}}},"WebhookDelivery":{"type":"object","properties":{"id":{"type":"string","description":"Also sent as the `X-ExpiryEdge-Delivery` header; stable across retries."},"event_id":{"type":"string"},"type":{"type":"string","description":"A `WebhookEventType` or `webhook.test`."},"status":{"type":"string","enum":["pending","succeeded","failed","skipped"]},"attempts":{"type":"integer"},"created_at":{"$ref":"#/components/schemas/Timestamp"},"last_attempt_at":{"type":["string","null"],"format":"date-time"},"next_attempt_at":{"type":["string","null"],"format":"date-time","description":"Only set while `status` is `pending`."},"last_status_code":{"type":["integer","null"]},"last_error":{"type":["string","null"]},"duration_ms":{"type":["integer","null"]}}},"WebhookEvent":{"type":"object","description":"Body of every webhook POST. Headers: `X-ExpiryEdge-Event` (the type), `X-ExpiryEdge-Delivery` (delivery id),\n`X-ExpiryEdge-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256(secret, t + \".\" + raw body)>`. Reject `t` older than 5 minutes.\n","required":["id","type","created_at","organization_id","data"],"properties":{"id":{"type":"string","description":"Event id, the same for every subscription that receives this event."},"type":{"type":"string","description":"A `WebhookEventType`, or `webhook.test` for sendTestWebhook."},"created_at":{"$ref":"#/components/schemas/Timestamp"},"organization_id":{"type":"string"},"data":{"type":"object","description":"The record in API shape - an Expiry (without share_password / share_token, with `has_share_password`),\na CollectionRequest, or a submission with the client and template details. Timestamps are ISO 8601.\n"}},"example":{"id":"evt_8c1f0a2b3c4d5e6f7a8b9c0d","type":"expiry.completed","created_at":"2026-10-03T08:14:22.120Z","organization_id":"org_northwind","data":{"id":"exp_4Tq9sLm2","name":"Northwind Dental - State Dental License","expiry_date":"2026-10-15","is_done":true,"completed_at":"2026-10-03T08:14:21.900Z","has_share_password":false}}},"WorkflowTimestampValue":{"type":"string","format":"date-time","description":"ISO 8601 date-time string (UTC). Formerly a raw Firestore Timestamp (`{_seconds, _nanoseconds}`).","examples":["2026-09-27T14:05:00.000Z"]},"Workflow":{"type":"object","description":"A workflow template (checklist) that runs are started from.","properties":{"id":{"type":"string","readOnly":true},"title":{"type":"string"},"description":{"type":"string"},"owner_id":{"type":"string","readOnly":true},"organization_id":{"type":"string","readOnly":true},"team_id":{"type":["string","null"]},"workflow_type_id":{"type":["string","null"]},"status":{"type":"string","description":"`active` or `archived`.","examples":["active"]},"progress":{"type":"integer","minimum":0,"maximum":100,"description":"Percent of template steps marked done.","readOnly":true},"tags":{"type":"array","items":{"type":"string"}},"due_date":{"oneOf":[{"$ref":"#/components/schemas/WorkflowTimestampValue"},{"type":"null"}]},"created_at":{"$ref":"#/components/schemas/WorkflowTimestampValue","readOnly":true},"updated_at":{"$ref":"#/components/schemas/WorkflowTimestampValue","readOnly":true}},"example":{"id":"wf_3Kd9Tx","title":"Annual license renewal","description":"Steps to renew a state dental license for Northwind Dental.","owner_id":"u_71bXq","organization_id":"org_northwind","team_id":null,"workflow_type_id":"wtype_R4c2","status":"active","progress":50,"tags":["licensing"],"due_date":"2026-10-15T00:00:00.000Z","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}},"WorkflowRunStats":{"type":"object","readOnly":true,"properties":{"total":{"type":"integer"},"active":{"type":"integer"},"completed":{"type":"integer"},"archived":{"type":"integer"}}},"WorkflowWithDetails":{"allOf":[{"$ref":"#/components/schemas/Workflow"},{"type":"object","properties":{"sections":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowSection"}},"run_stats":{"$ref":"#/components/schemas/WorkflowRunStats"}}}]},"WorkflowCreateInput":{"type":"object","required":["title"],"properties":{"title":{"type":"string"},"description":{"type":"string"},"team_id":{"type":["string","null"]},"due_date":{"$ref":"#/components/schemas/IsoDate"},"tags":{"type":"array","items":{"type":"string"}},"workflow_type_id":{"type":["string","null"]}}},"WorkflowUpdateInput":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"team_id":{"type":["string","null"]},"due_date":{"oneOf":[{"$ref":"#/components/schemas/IsoDate"},{"type":"null"}]},"tags":{"type":"array","items":{"type":"string"}},"workflow_type_id":{"type":["string","null"]},"status":{"type":"string","examples":["active","archived"]}}},"WorkflowSection":{"type":"object","description":"A section of a workflow template. `steps` is included when read through getWorkflow / getAllWorkflows.","properties":{"id":{"type":"string","readOnly":true},"workflow_id":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"order":{"type":"integer"},"created_at":{"$ref":"#/components/schemas/WorkflowTimestampValue","readOnly":true},"steps":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowStep"},"readOnly":true}}},"WorkflowStep":{"type":"object","description":"A step of a workflow template. Extra keys set through updateWorkflowStep are stored and returned as-is.","additionalProperties":true,"properties":{"id":{"type":"string","readOnly":true},"section_id":{"type":"string","readOnly":true},"workflow_id":{"type":"string","readOnly":true},"title":{"type":"string"},"description":{"type":"string"},"assignees":{"type":"array","items":{"type":"string"},"description":"User ids."},"status":{"type":"string","examples":["todo","in_progress","done"]},"priority":{"type":"string","enum":["low","medium","high"]},"start_date":{"oneOf":[{"$ref":"#/components/schemas/WorkflowTimestampValue"},{"type":"null"}]},"due_date":{"oneOf":[{"$ref":"#/components/schemas/WorkflowTimestampValue"},{"type":"null"}]},"start_offset_days":{"type":["integer","null"],"description":"Days after the run start used for the run step start date."},"due_offset_days":{"type":["integer","null"],"description":"Days after the run start used for the run step due date."},"completed_at":{"oneOf":[{"$ref":"#/components/schemas/WorkflowTimestampValue"},{"type":"null"}],"readOnly":true},"order":{"type":"integer"},"dependencies":{"type":"array","items":{"type":"string"}},"depends_on":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Step id(s) this step waits for."},"time_estimate":{"type":"number"},"time_spent":{"type":"number"},"tags":{"type":"array","items":{"type":"string"}},"linked_expiry_id":{"type":["string","null"]},"created_at":{"$ref":"#/components/schemas/WorkflowTimestampValue","readOnly":true}}},"WorkflowStepUpdateInput":{"type":"object","description":"Any step fields. Ownership and parent fields are ignored.","additionalProperties":true,"properties":{"title":{"type":"string"},"description":{"type":"string"},"assignees":{"type":"array","items":{"type":"string"}},"status":{"type":"string"},"priority":{"type":"string","enum":["low","medium","high"]},"start_date":{"type":["string","null"]},"due_date":{"type":["string","null"]},"start_offset_days":{"type":["integer","null"]},"due_offset_days":{"type":["integer","null"]},"depends_on":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}},{"type":"null"}]},"order":{"type":"integer"}}},"WorkflowOrderItem":{"type":"object","required":["id","order"],"properties":{"id":{"type":"string"},"order":{"type":"integer"}}},"WorkflowRun":{"type":"object","description":"A run (instance) of a workflow template. `owner_name`, `total_steps`, `completed_steps` and `progress` are recomputed when listed.","properties":{"id":{"type":"string","readOnly":true},"workflow_id":{"type":"string","readOnly":true},"workflow_title":{"type":"string","readOnly":true},"title":{"type":"string"},"description":{"type":"string"},"status":{"type":"string","examples":["active","completed","archived","cancelled"]},"progress":{"type":"integer","minimum":0,"maximum":100,"readOnly":true},"owner_id":{"type":"string","readOnly":true},"owner_name":{"type":"string","readOnly":true},"organization_id":{"type":"string","readOnly":true},"workflow_type_id":{"type":["string","null"]},"assignees":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"}},"total_steps":{"type":"integer","readOnly":true},"completed_steps":{"type":"integer","readOnly":true},"triggered_by_schedule":{"type":"string","description":"Schedule id when started by a schedule.","readOnly":true},"source_attachment_id":{"type":"string","description":"Expiry workflow attachment id when started by one.","readOnly":true},"started_at":{"$ref":"#/components/schemas/WorkflowTimestampValue","readOnly":true},"completed_at":{"$ref":"#/components/schemas/WorkflowTimestampValue","readOnly":true},"created_at":{"$ref":"#/components/schemas/WorkflowTimestampValue","readOnly":true},"updated_at":{"$ref":"#/components/schemas/WorkflowTimestampValue","readOnly":true}},"example":{"id":"run_7Vb3","workflow_id":"wf_3Kd9Tx","workflow_title":"Annual license renewal","title":"Annual license renewal - 10/15/2026","description":"Steps to renew a state dental license for Northwind Dental.","status":"active","progress":0,"owner_id":"u_71bXq","organization_id":"org_northwind","workflow_type_id":"wtype_R4c2","assignees":["u_71bXq"],"tags":["licensing"],"total_steps":4,"completed_steps":0,"started_at":"2026-09-27T14:05:00.000Z","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}},"WorkflowRunSection":{"type":"object","properties":{"id":{"type":"string","readOnly":true},"run_id":{"type":"string","readOnly":true},"workflow_id":{"type":"string","readOnly":true},"title":{"type":"string"},"description":{"type":"string"},"order":{"type":"integer"},"created_at":{"$ref":"#/components/schemas/WorkflowTimestampValue","readOnly":true},"steps":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowRunStep"},"readOnly":true}}},"WorkflowRunStep":{"type":"object","description":"A step of a run. `start_date` / `due_date` are ISO strings computed from the template offsets (or copied from the template).","properties":{"id":{"type":"string","readOnly":true},"run_id":{"type":"string","readOnly":true},"section_id":{"type":"string","readOnly":true},"workflow_id":{"type":"string","readOnly":true},"title":{"type":"string"},"description":{"type":"string"},"status":{"type":"string","examples":["todo","in_progress","done"]},"priority":{"type":"string","enum":["low","medium","high"]},"order":{"type":"integer"},"assignees":{"type":"array","items":{"type":"string"}},"dependencies":{"type":"array","items":{"type":"string"}},"depends_on":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}},{"type":"null"}]},"start_date":{"oneOf":[{"type":"string","format":"date-time"},{"$ref":"#/components/schemas/WorkflowTimestampValue"},{"type":"null"}]},"due_date":{"oneOf":[{"type":"string","format":"date-time"},{"$ref":"#/components/schemas/WorkflowTimestampValue"},{"type":"null"}]},"start_offset_days":{"type":["integer","null"]},"due_offset_days":{"type":["integer","null"]},"notes":{"type":"string"},"completed_at":{"$ref":"#/components/schemas/WorkflowTimestampValue","readOnly":true},"created_at":{"$ref":"#/components/schemas/WorkflowTimestampValue","readOnly":true},"updated_at":{"$ref":"#/components/schemas/WorkflowTimestampValue","readOnly":true}}},"WorkflowMentionedUser":{"type":"object","required":["id"],"additionalProperties":true,"properties":{"id":{"type":"string","description":"User id."},"name":{"type":"string"}}},"WorkflowComment":{"type":"object","description":"A comment on a workflow template or run. `user_name` and `user_email` are added when listed.","properties":{"id":{"type":"string","readOnly":true},"workflow_id":{"type":"string","description":"Workflow id or workflow run id."},"step_id":{"type":["string","null"]},"section_id":{"type":["string","null"]},"user_id":{"type":"string","readOnly":true},"user_name":{"type":"string","readOnly":true},"user_email":{"type":"string","readOnly":true},"content":{"type":"string"},"mentioned_users":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowMentionedUser"}},"is_edited":{"type":"boolean","readOnly":true},"created_at":{"$ref":"#/components/schemas/WorkflowTimestampValue","readOnly":true},"updated_at":{"$ref":"#/components/schemas/WorkflowTimestampValue","readOnly":true}},"example":{"id":"wcm_4Rt6","workflow_id":"run_7Vb3","step_id":"rstp_2Wd8","section_id":null,"user_id":"u_71bXq","content":"@Priya certificates for Dr. Lee are uploaded.","mentioned_users":[{"id":"u_93kLp","name":"Priya Shah"}],"is_edited":false,"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}},"WorkflowScheduleFields":{"type":"object","description":"Writable schedule fields.","properties":{"title_template":{"type":["string","null"],"description":"Run title; `{date}` is replaced with the run date. Null uses `<workflow title> - <date>`."},"schedule_type":{"type":"string","enum":["once","daily","interval","weekly","monthly","yearly"]},"interval_days":{"type":["integer","null"],"description":"For `interval`."},"weekdays":{"type":"array","items":{"type":"integer","minimum":0,"maximum":6},"description":"For `weekly` (0 = Sunday)."},"day_of_month":{"type":["integer","null"],"minimum":1,"maximum":31,"description":"For `monthly` / `yearly`."},"month_of_year":{"type":["integer","null"],"minimum":1,"maximum":12,"description":"For `yearly`."},"hour":{"type":"integer","minimum":0,"maximum":23,"default":9},"minute":{"type":"integer","minimum":0,"maximum":59,"default":0},"timezone":{"type":"string","default":"UTC","description":"IANA timezone (stored; fire times are computed in server time from `hour` / `minute`)."},"start_date":{"type":"string","format":"date-time","description":"First possible fire time; for `once` this is the fire time."},"end_date":{"type":["string","null"],"format":"date-time"},"assignees":{"type":"array","items":{"type":"string"},"description":"Users of your organization assigned to created runs (default: you)."},"notify_owner":{"type":"boolean","default":true}}},"WorkflowScheduleInput":{"allOf":[{"$ref":"#/components/schemas/WorkflowScheduleFields"},{"type":"object","required":["workflow_id","schedule_type","start_date"],"properties":{"workflow_id":{"type":"string"}}}]},"WorkflowSchedule":{"allOf":[{"$ref":"#/components/schemas/WorkflowScheduleFields"},{"type":"object","properties":{"id":{"type":"string","readOnly":true},"workflow_id":{"type":"string","readOnly":true},"workflow_title":{"type":"string","readOnly":true},"owner_id":{"type":"string","readOnly":true},"organization_id":{"type":"string","readOnly":true},"next_run_at":{"type":["string","null"],"format":"date-time","readOnly":true},"last_run_at":{"type":["string","null"],"format":"date-time","readOnly":true},"last_run_id":{"type":"string","readOnly":true},"run_count":{"type":"integer","readOnly":true},"status":{"type":"string","enum":["active","paused","completed","cancelled"]},"created_at":{"$ref":"#/components/schemas/WorkflowTimestampValue","readOnly":true},"updated_at":{"$ref":"#/components/schemas/WorkflowTimestampValue","readOnly":true}}}],"example":{"id":"sch_6Yx4","workflow_id":"wf_3Kd9Tx","workflow_title":"Annual license renewal","owner_id":"u_71bXq","organization_id":"org_northwind","title_template":"License renewal {date}","schedule_type":"monthly","interval_days":null,"weekdays":[],"day_of_month":15,"month_of_year":null,"hour":9,"minute":0,"timezone":"UTC","start_date":"2026-10-15T09:00:00.000Z","end_date":null,"next_run_at":"2026-10-15T09:00:00.000Z","last_run_at":null,"run_count":0,"notify_owner":true,"assignees":["u_71bXq"],"status":"active","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}},"WorkflowTypeInput":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"color":{"type":"string","default":"#1976d2","description":"Hex color."}}},"WorkflowType":{"type":"object","description":"An organization-defined category for workflows. `workflow_count` is present when listed.","properties":{"id":{"type":"string","readOnly":true},"name":{"type":"string"},"description":{"type":"string"},"color":{"type":"string"},"organization_id":{"type":"string","readOnly":true},"created_by":{"type":"string","readOnly":true},"created_at":{"$ref":"#/components/schemas/WorkflowTimestampValue","readOnly":true},"updated_at":{"$ref":"#/components/schemas/WorkflowTimestampValue","readOnly":true},"workflow_count":{"type":"integer","readOnly":true}},"example":{"id":"wtype_R4c2","name":"Licensing","description":"License and permit renewals","color":"#1976d2","organization_id":"org_northwind","created_by":"u_71bXq","created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z","workflow_count":3}},"ExpiryWorkflowTriggerType":{"type":"string","enum":["before_expiry","on_expiry","after_expiry","on_done","on_renewal","manual"]},"ExpiryWorkflowNotifyChannels":{"type":"object","properties":{"email":{"type":"boolean","default":true},"in_app":{"type":"boolean","default":true},"sms":{"type":"boolean","default":false},"whatsapp":{"type":"boolean","default":false},"teams":{"type":"boolean","default":false}}},"ExpiryWorkflowNotificationConfig":{"type":"object","properties":{"notify_on_start":{"type":"boolean","default":true},"notify_on_step_assignment":{"type":"boolean","default":true},"notify_on_completion":{"type":"boolean","default":true},"notify_on_stall":{"type":"boolean","default":false},"stall_threshold_days":{"type":"integer","default":3},"notify_recipients":{"type":"array","items":{"type":"string"},"description":"User ids of your organization."},"notify_channels":{"$ref":"#/components/schemas/ExpiryWorkflowNotifyChannels"}}},"ExpiryWorkflowAttachmentInput":{"allOf":[{"$ref":"#/components/schemas/ExpiryWorkflowNotificationConfig"},{"type":"object","required":["expiry_id","workflow_id","trigger_type"],"description":"Notification settings may be sent flat (as here) or nested in `notification_config`.","properties":{"expiry_id":{"type":"string"},"workflow_id":{"type":"string"},"trigger_type":{"$ref":"#/components/schemas/ExpiryWorkflowTriggerType"},"trigger_days":{"type":"integer","minimum":0,"maximum":365,"description":"Required for `before_expiry` / `after_expiry`."},"trigger_time":{"type":"string","pattern":"^\\d{2}:\\d{2}$","default":"09:00","description":"`HH:mm` in the organization timezone (date-based triggers)."},"carry_to_renewal":{"type":"boolean","default":true,"description":"Copy the attachment to the renewed expiry."},"notification_config":{"$ref":"#/components/schemas/ExpiryWorkflowNotificationConfig"}}}]},"ExpiryWorkflowAttachment":{"allOf":[{"$ref":"#/components/schemas/ExpiryWorkflowNotificationConfig"},{"type":"object","description":"A workflow attached to an expiry with a trigger rule.","properties":{"id":{"type":"string","readOnly":true},"expiry_id":{"type":"string","readOnly":true},"workflow_id":{"type":"string","readOnly":true},"workflow_title":{"type":"string","readOnly":true},"expiry_name":{"type":"string","readOnly":true},"organization_id":{"type":"string","readOnly":true},"created_by":{"type":"string","readOnly":true},"team_id":{"type":["string","null"],"readOnly":true},"trigger_type":{"$ref":"#/components/schemas/ExpiryWorkflowTriggerType"},"trigger_days":{"type":["integer","null"]},"trigger_time":{"type":["string","null"]},"trigger_date":{"type":["string","null"],"format":"date-time","description":"UTC fire time for date-based triggers; null for event-based ones.","readOnly":true},"trigger_status":{"type":"string","enum":["pending","triggered","cancelled","paused","error"],"readOnly":true},"error_message":{"type":"string","readOnly":true},"current_run_id":{"type":["string","null"],"readOnly":true},"last_run_id":{"type":["string","null"],"readOnly":true},"run_count":{"type":"integer","readOnly":true},"current_run_status":{"type":["object","null"],"description":"Present in getExpiryWorkflowAttachments.","properties":{"id":{"type":"string"},"status":{"type":"string"},"progress":{"type":"integer"},"completed_steps":{"type":"integer"},"total_steps":{"type":"integer"}},"readOnly":true},"is_deleted":{"type":"boolean","readOnly":true},"carry_to_renewal":{"type":"boolean"},"created_at":{"$ref":"#/components/schemas/WorkflowTimestampValue","readOnly":true},"updated_at":{"$ref":"#/components/schemas/WorkflowTimestampValue","readOnly":true}}}],"example":{"id":"att_1Mz7","expiry_id":"exp_4Tq9sLm2","workflow_id":"wf_3Kd9Tx","workflow_title":"Annual license renewal","expiry_name":"State dental license - Dr. Lee","organization_id":"org_northwind","created_by":"u_71bXq","team_id":null,"trigger_type":"before_expiry","trigger_days":30,"trigger_time":"09:00","trigger_date":"2026-09-15T09:00:00.000Z","trigger_status":"pending","notify_on_start":true,"notify_on_step_assignment":true,"notify_on_completion":true,"notify_on_stall":false,"stall_threshold_days":3,"notify_recipients":["u_71bXq"],"notify_channels":{"email":true,"sms":false,"whatsapp":false,"in_app":true,"teams":false},"current_run_id":null,"last_run_id":null,"run_count":0,"is_deleted":false,"carry_to_renewal":true,"created_at":"2026-09-27T14:05:00.000Z","updated_at":"2026-09-27T14:05:00.000Z"}}},"requestBodies":{"ExpiryIdBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expiryId"],"properties":{"expiryId":{"type":"string"}}},"example":{"expiryId":"exp_4Tq9sLm2"}}}},"ExpiryUpdate":{"required":true,"description":"Any subset of the writable expiry fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpiryInput"},"example":{"expiry_date":"2027-10-15","state":"inprogress","notes":"Renewal submitted on 2026-09-27; awaiting board confirmation."}}}}}}}
