{"openapi":"3.1.0","info":{"title":"TripPay API","version":"1.0.0-rc.1","description":"REST API for the TripPay Travel FinTech, AI SaaS, and marketplace platform.\n\nThe API is now executable: run `node server/index.mjs` and the full\n`/v1/*` surface is available. Authentication is bearer-JWT or API-key.\nPlanning POST endpoints accept an optional `Idempotency-Key` header for\nidentity-scoped retries; other operations document their own requirements.\nTrip planning and disruptions default to the caller's personal (null-tenant)\ncontext. Only an explicit valid `X-Tenant-Id` opts into organization trips;\njoining an organization never implicitly hides personal or legacy trips.\nPublic consent submissions instead require a same-origin JSON request. See\n`API_SPEC.md` for the coding-ready target contract and implementation\nstatus, and `BACKEND_GUIDE.md` / `API.md` for current runtime guidance.\n"},"servers":[{"url":"https://api.trippay.com/v1"}],"security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"OAuth/OIDC bearer token. The current MVP uses locally signed JWTs."},"apiKeyAuth":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"Scoped machine credential; secrets are returned once and stored hashed."}},"parameters":{"PlanningTenant":{"name":"X-Tenant-Id","in":"header","required":false,"description":"Omit for personal/null-tenant trip planning, including legacy trips. An explicit valid organization membership opts into that tenant; trip ownership is still required and other contexts return 404. Global tenant behavior on unrelated APIs is unchanged.","schema":{"type":"string","format":"uuid"}},"PlanningIdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional 24-hour replay key scoped to user, explicitly selected tenant (personal/null if omitted), method and path. An identical JSON body replays the original response; a different body returns 409. Single-writer memory/file-store guarantee, not a distributed transaction.","schema":{"type":"string","minLength":1,"maxLength":200}},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":true,"description":"Scoped replay key for side-effecting commands.","schema":{"type":"string","minLength":1,"maxLength":128,"pattern":"^[A-Za-z0-9._:-]+$"}},"RequestId":{"name":"X-Request-Id","in":"header","required":false,"description":"Caller correlation value; the server generates one when absent.","schema":{"type":"string","maxLength":128}}},"schemas":{"CompanionInput":{"type":"object","additionalProperties":false,"required":["display_name"],"properties":{"display_name":{"type":"string","minLength":1,"maxLength":120},"traveler_type":{"type":"string","enum":["adult","child","infant"],"default":"adult"}}},"TripCompanion":{"allOf":[{"type":"object","properties":{"display_name":{"type":"string","minLength":1,"maxLength":120},"traveler_type":{"type":"string","enum":["adult","child","infant"]}}},{"type":"object","required":["id","trip_id","version"],"properties":{"id":{"type":"string","format":"uuid"},"trip_id":{"type":"string","format":"uuid"},"version":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Current record version; legacy records use zero"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}]},"CompanionPatch":{"type":"object","additionalProperties":false,"required":["version"],"minProperties":2,"properties":{"version":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Current record version; legacy records use zero"},"display_name":{"type":"string","minLength":1,"maxLength":120},"traveler_type":{"type":"string","enum":["adult","child","infant"]}}},"ItineraryItemPatch":{"type":"object","additionalProperties":false,"required":["version"],"minProperties":2,"properties":{"version":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Current record version; legacy records use zero"},"title":{"type":"string","minLength":1,"maxLength":160},"location":{"type":["string","null"],"maxLength":240},"starts_at":{"type":["string","null"],"format":"date-time"},"ends_at":{"type":["string","null"],"format":"date-time"},"cost_minor":{"type":"integer","minimum":0,"maximum":9007199254740991},"currency":{"$ref":"#/components/schemas/PlanningCurrency"}}},"TripExpensePatch":{"type":"object","additionalProperties":false,"required":["version"],"minProperties":2,"properties":{"version":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Current record version; legacy records use zero"},"amount_minor":{"type":"integer","minimum":1,"maximum":9007199254740991},"currency":{"$ref":"#/components/schemas/PlanningCurrency"},"category_name":{"type":["string","null"],"maxLength":80},"notes":{"type":["string","null"],"maxLength":2000}}},"TripInput":{"type":"object","required":["name","destination"],"properties":{"name":{"type":"string","minLength":1,"maxLength":160,"description":"Trimmed nonblank trip name"},"destination":{"type":"string","minLength":1,"maxLength":160,"description":"Trimmed nonblank destination"},"starts_on":{"type":["string","null"],"format":"date","description":"Valid calendar date; must not follow ends_on"},"ends_on":{"type":["string","null"],"format":"date"},"agency_id":{"type":"null","description":"Personal creation rejects non-null agency assignment with agency_delegation_unavailable; delegated agency planning is not available"}}},"TripPatch":{"type":"object","additionalProperties":false,"required":["version"],"minProperties":2,"properties":{"version":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Version returned by the most recent read; stale versions return 409"},"name":{"type":"string","minLength":1,"maxLength":160},"destination":{"type":"string","minLength":1,"maxLength":160},"starts_on":{"type":["string","null"],"format":"date","description":"Null clears the date; order is checked against merged existing values"},"ends_on":{"type":["string","null"],"format":"date"}}},"Trip":{"type":"object","required":["id","name","destination","version","traveler_id","status","created_at","updated_at"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"destination":{"type":"string"},"starts_on":{"type":["string","null"],"format":"date"},"ends_on":{"type":["string","null"],"format":"date"},"traveler_id":{"type":["string","null"],"format":"uuid"},"agency_id":{"type":["string","null"],"description":"Legacy or trusted internal agency metadata; never grants access"},"tenant_id":{"type":["string","null"],"format":"uuid"},"version":{"type":"integer","minimum":0,"description":"New trips start at 1; legacy unversioned trips read as 0"},"status":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"TripList":{"type":"object","required":["rows","total"],"properties":{"rows":{"type":"array","items":{"$ref":"#/components/schemas/Trip"}},"total":{"type":"integer","minimum":0,"description":"Matching owner and tenant rows before pagination"}}},"PlanningCurrency":{"type":"string","description":"Active currency code from GET /catalog/currencies. Input is case-insensitive; output is uppercase. No automatic FX conversion.","pattern":"^[A-Za-z]{3,4}$","default":"SAR"},"ItineraryItemInput":{"type":"object","required":["title"],"properties":{"title":{"type":"string","minLength":1,"maxLength":160},"location":{"type":["string","null"],"minLength":1,"maxLength":240},"starts_at":{"type":["string","null"],"format":"date-time","description":"ISO calendar date-time with Z or explicit +/-HH:MM timezone offset"},"ends_at":{"type":["string","null"],"format":"date-time","description":"Must not precede starts_at after timezone conversion"},"cost_minor":{"type":"integer","minimum":0,"maximum":9007199254740991,"default":0},"currency":{"$ref":"#/components/schemas/PlanningCurrency"}}},"ItineraryItem":{"allOf":[{"$ref":"#/components/schemas/ItineraryItemInput"},{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"itinerary_id":{"type":"string","format":"uuid"},"version":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Current record version; legacy records use zero"},"location":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}]},"Itinerary":{"type":"object","required":["itinerary","items"],"properties":{"itinerary":{"type":["object","null"],"description":"Itinerary metadata","or null before the first item is created":null},"items":{"type":"array","items":{"$ref":"#/components/schemas/ItineraryItem"}}}},"TripExpenseInput":{"type":"object","required":["amount_minor"],"properties":{"amount_minor":{"type":"integer","minimum":1,"maximum":9007199254740991},"currency":{"$ref":"#/components/schemas/PlanningCurrency"},"category_name":{"type":"string","minLength":1,"maxLength":80},"notes":{"type":"string","maxLength":2000}}},"TripExpense":{"type":"object","required":["id","trip_id","traveler_id","amount_minor","currency"],"properties":{"version":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Current record version; legacy records use zero"},"id":{"type":"string","format":"uuid"},"trip_id":{"type":"string","format":"uuid"},"traveler_id":{"type":"string","format":"uuid"},"category_id":{"type":["string","null"],"format":"uuid"},"category_name":{"type":["string","null"],"maxLength":80,"description":"Recorded free-form category label; listing resolves legacy category_id-only rows from the catalog without rewriting them"},"amount_minor":{"type":"integer","minimum":1,"maximum":9007199254740991},"currency":{"$ref":"#/components/schemas/PlanningCurrency"},"notes":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"CurrencyBudgetTotal":{"type":"object","required":["currency","itinerary_cost_minor","expense_total_minor","total_minor"],"properties":{"currency":{"type":"string"},"itinerary_cost_minor":{"type":"integer","minimum":0,"maximum":9007199254740991},"expense_total_minor":{"type":"integer","minimum":0,"maximum":9007199254740991},"total_minor":{"type":"integer","minimum":0,"maximum":9007199254740991}}},"TripBudgetSummary":{"type":"object","required":["trip_id","currency","itinerary_cost_minor","expense_total_minor","total_minor","totals_by_currency"],"description":"Single-currency totals retain legacy scalar fields. Compatibility change for mixed-currency consumers - currency and all scalar totals are now null, never an invalid numeric sum; clients must render totals_by_currency. An empty trip returns SAR zeros. Unsafe aggregate integers are rejected with 400 rather than rounded.","properties":{"trip_id":{"type":"string","format":"uuid"},"currency":{"type":["string","null"]},"itinerary_cost_minor":{"type":["integer","null"]},"expense_total_minor":{"type":["integer","null"]},"total_minor":{"type":["integer","null"]},"totals_by_currency":{"type":"array","items":{"$ref":"#/components/schemas/CurrencyBudgetTotal"}}}},"ProfilePatch":{"type":"object","additionalProperties":false,"minProperties":1,"properties":{"full_name":{"type":"string","minLength":1,"maxLength":160,"description":"Updates user.full_name and profile.display_name"},"preferred_locale":{"type":"string","enum":["en","ar"],"description":"Updates user.preferred_locale and profile.preferences.locale"},"timezone":{"type":"string","maxLength":100,"description":"IANA timezone stored in profile.preferences.timezone"},"display_name":{"type":"string","minLength":1,"maxLength":160},"preferences":{"type":"object","description":"Existing preferences merged; serialized input limited to 140000 characters to allow one locally resized raster profile image. Locale","timezone":null,"avatar_data_url":null,"bio":null,"city":null,"country and phone are validated. SVG is not accepted.":null},"kyc_ready_fields":{"type":"object","description":"Existing profile metadata only; never changes verified KYC or compliance state. Serialized input limited to 4000 characters."}}},"AuthProfile":{"type":"object","required":["user","profile","roles","permissions"],"properties":{"user":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email"},"full_name":{"type":"string"},"preferred_locale":{"type":"string","enum":["en","ar"]},"status":{"type":"string"},"email_verified_at":{"type":["string","null"],"format":"date-time"}}},"profile":{"type":["object","null"],"description":"Existing profile with display_name","preferences (locale":null,"optional timezone and user-managed contact/profile details)":null,"and kyc_ready_fields; never credentials":null},"roles":{"type":"array","items":{"type":"string"}},"permissions":{"type":"array","items":{"type":"string"}}}},"PublicSubmission":{"type":"object","additionalProperties":false,"required":["kind","email","consent","locale"],"properties":{"kind":{"type":"string","enum":["launch","partner","contact"]},"email":{"type":"string","format":"email","maxLength":254},"name":{"type":"string","maxLength":120},"organization":{"type":"string","maxLength":160},"country":{"type":"string","maxLength":80},"category":{"type":"string","maxLength":32},"website":{"type":"string","maxLength":500,"description":"Optional HTTP or HTTPS URL without credentials"},"services":{"type":"string","maxLength":2000},"integration_interest":{"type":"string","maxLength":8},"message":{"type":"string","maxLength":4000},"consent":{"type":"boolean","const":true},"company_url":{"type":"string","maxLength":500,"description":"Honeypot; legitimate submissions leave empty"},"locale":{"type":"string","enum":["en","ar"]}},"oneOf":[{"properties":{"kind":{"const":"launch"},"category":{"const":""},"integration_interest":{"const":""}}},{"required":["name","organization","country","category","services","integration_interest"],"properties":{"kind":{"const":"partner"},"name":{"minLength":1},"organization":{"minLength":1},"country":{"minLength":1},"services":{"minLength":1},"category":{"enum":["operator","accommodation","transport","services","technology"]},"integration_interest":{"enum":["yes","no","unsure"]}}},{"required":["name","category","message"],"properties":{"kind":{"const":"contact"},"name":{"minLength":1},"message":{"minLength":1},"category":{"enum":["support","partnership","technical"]},"integration_interest":{"const":""}}}]},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","requestId"],"properties":{"code":{"type":"string"},"message":{"type":"string"},"requestId":{"type":"string"},"details":{"type":"object","additionalProperties":true,"properties":{"errors":{"type":"array","description":"Field-level validation issues, including actionable messages for planning validation","items":{"type":"object","properties":{"field":{"type":"string"},"code":{"type":"string"},"message":{"type":"string"}}}}}}}}}},"IdResponse":{"type":"object","required":["id","status"],"properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string"}}},"Money":{"type":"object","additionalProperties":false,"required":["amount_minor","currency"],"properties":{"amount_minor":{"type":"integer","format":"int64","minimum":0,"description":"Integer minor units; floating-point money is prohibited."},"currency":{"type":"string","pattern":"^[A-Z]{3}$"}}},"EventEnvelope":{"type":"object","additionalProperties":false,"required":["event_id","event_type","version","timestamp","correlation_id","causation_id","actor","entity","payload"],"properties":{"event_id":{"type":"string","format":"uuid"},"event_type":{"type":"string","pattern":"^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$"},"version":{"type":"integer","minimum":1},"timestamp":{"type":"string","format":"date-time"},"correlation_id":{"type":"string"},"causation_id":{"type":["string","null"]},"actor":{"type":"object","additionalProperties":true},"entity":{"type":"object","additionalProperties":true},"payload":{"type":"object","additionalProperties":true}}}}},"paths":{"/public/submissions":{"post":{"summary":"Record first-party public inquiry and explicit consent","security":[],"description":"Same-origin JSON only (Origin and Host must match configured APP_URL).\nRequires private durable PUBLIC_INQUIRY_FILE configuration; unavailable\nstorage fails closed. Maximum body is 16 KiB. Limit is five attempts per\nten minutes per ephemeral hashed connection IP, in addition to the API\nlimiter. No CORS, public read endpoint, external CRM, or email delivery.\nA 202 records an inquiry, not marketing enrollment or a delivery promise.\nHoneypot submissions receive the same response but are not stored.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicSubmission"}}}},"responses":{"202":{"description":"Accepted without disclosing personal data","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","const":"accepted"}}}}}},"400":{"description":"Invalid fields or consent"},"403":{"description":"Origin denied"},"413":{"description":"Body too large"},"415":{"description":"JSON required"},"429":{"description":"Rate limited"},"503":{"description":"Private durable inbox unavailable or full"}}}},"/pilot/status":{"get":{"summary":"Pilot rollout status (enabled flag + emergency control state)","tags":["Pilot"],"responses":{"200":{"description":"Pilot status"},"401":{"description":"Authentication required"}}}},"/pilot/invitations":{"post":{"summary":"Invite a user to the pilot (settings.manage)","tags":["Pilot"],"responses":{"201":{"description":"Invitation created"},"403":{"description":"Forbidden"}}},"get":{"summary":"List pilot invitations (settings.manage)","tags":["Pilot"],"responses":{"200":{"description":"Invitations"},"403":{"description":"Forbidden"}}}},"/pilot/cohorts":{"post":{"summary":"Create a pilot cohort with usage limits (settings.manage)","tags":["Pilot"],"responses":{"201":{"description":"Cohort created"},"403":{"description":"Forbidden"}}},"get":{"summary":"List pilot cohorts (settings.manage)","tags":["Pilot"],"responses":{"200":{"description":"Cohorts"},"403":{"description":"Forbidden"}}}},"/admin/emergency-controls/{domain}/halt":{"post":{"summary":"Halt new bookings or payments; existing obligations continue (settings.manage, audited)","tags":["Pilot"],"parameters":[{"name":"domain","in":"path","required":true,"schema":{"type":"string","enum":["bookings","payments"]}}],"responses":{"200":{"description":"Domain halted"},"403":{"description":"Forbidden"}}}},"/admin/emergency-controls/{domain}/resume":{"post":{"summary":"Resume a halted domain (settings.manage, audited)","tags":["Pilot"],"parameters":[{"name":"domain","in":"path","required":true,"schema":{"type":"string","enum":["bookings","payments"]}}],"responses":{"200":{"description":"Domain resumed"},"403":{"description":"Forbidden"}}}},"/partners/applications":{"post":{"summary":"Submit a partner application","tags":["Partners"],"responses":{"201":{"description":"Application recorded"},"401":{"description":"Authentication required"}}},"get":{"summary":"List partner applications (partners.manage)","tags":["Partners"],"responses":{"200":{"description":"Applications"},"403":{"description":"Forbidden"}}}},"/partners/applications/{id}/review":{"post":{"summary":"Approve or reject a partner application (partners.manage)","tags":["Partners"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Decision recorded"},"403":{"description":"Forbidden"}}}},"/partners/applications/{id}/activate":{"post":{"summary":"Activate an approved application into a partner organization + integration register (partners.manage)","tags":["Partners"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Activated"},"403":{"description":"Application not approved or forbidden"}}}},"/partners/integrations":{"get":{"summary":"Integration capability register — access, verified capabilities, credential config status (never values), webhook/reconciliation verification, limitations, activation requirements (partners.manage)","tags":["Partners"],"responses":{"200":{"description":"Integration records"},"403":{"description":"Forbidden"}}}},"/ops/overview":{"get":{"summary":"Operational command-center overview (reports.view)","tags":["Ops"],"responses":{"200":{"description":"Overview counts"},"403":{"description":"Forbidden"}}}},"/ops/booking-exceptions":{"get":{"summary":"Bookings needing attention (reports.view)","tags":["Ops"],"responses":{"200":{"description":"Booking exceptions"},"403":{"description":"Forbidden"}}}},"/ops/payment-discrepancies":{"get":{"summary":"Payment-to-booking mismatches and failed intents (reports.view)","tags":["Ops"],"responses":{"200":{"description":"Discrepancies"},"403":{"description":"Forbidden"}}}},"/ops/failed-work":{"get":{"summary":"Failed background jobs and webhook deliveries (reports.view)","tags":["Ops"],"responses":{"200":{"description":"Failed work"},"403":{"description":"Forbidden"}}}},"/ops/integration-availability":{"get":{"summary":"Provider integration availability and readiness (reports.view)","tags":["Ops"],"responses":{"200":{"description":"Availability"},"403":{"description":"Forbidden"}}}},"/analytics/events":{"post":{"summary":"Ingest privacy-safe client-side product analytics events","security":[],"description":"First-party client event ingest for the product analytics taxonomy.\nUnauthenticated by design but hardened: event names are restricted to\nthe governed client taxonomy, fields pass through a strict allowlist\nplus deny-list (identity documents, precise location, credentials and\nfree-text personal data are dropped by construction), events are\ndeduplicated on dedupe_key, and a per-IP fixed-window throttle applies\non top of the global API limiter. Batches are capped at 25 events.\nTest traffic (X-Test-Traffic: 1) is recorded but excluded from metrics.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["events"],"properties":{"events":{"type":"array","maxItems":25,"items":{"type":"object","required":["event"],"properties":{"event":{"type":"string","maxLength":60,"description":"Must be one of the governed client event names"},"fields":{"type":"object","description":"Privacy-safe fields only; non-allowlisted keys are dropped"},"dedupe_key":{"type":"string","maxLength":120}}}}}}}}},"responses":{"202":{"description":"Batch accepted; rejected items never block valid ones","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","const":"accepted"},"accepted":{"type":"integer"},"rejected":{"type":"integer"}}}}}},"400":{"description":"Missing or malformed events array"},"429":{"description":"Per-IP ingest throttle or global rate limit exceeded"}}}},"/analytics/funnel":{"get":{"summary":"Approved product funnel counts, test traffic excluded (reports.view)","tags":["Analytics"],"responses":{"200":{"description":"Funnel"},"403":{"description":"Forbidden"}}}},"/analytics/outcomes":{"get":{"summary":"Operational outcomes — booking failures, mismatches, refund/support timing, provider errors (reports.view)","tags":["Analytics"],"responses":{"200":{"description":"Outcomes"},"403":{"description":"Forbidden"}}}},"/analytics/financial":{"get":{"summary":"Volume/refunds/provider-costs/estimated-margin; gross volume is not revenue (reports.view)","tags":["Analytics"],"responses":{"200":{"description":"Financial summary"},"403":{"description":"Forbidden"}}}},"/feedback":{"post":{"summary":"Submit contextual feedback with explicit consent","tags":["Feedback"],"responses":{"201":{"description":"Recorded"},"400":{"description":"Consent required or invalid"}}},"get":{"summary":"Prioritized internal feedback triage queue (support.manage)","tags":["Feedback"],"responses":{"200":{"description":"Queue"},"403":{"description":"Forbidden"}}}},"/providers":{"get":{"summary":"List provider gateway adapters (readiness only; no secrets)","responses":{"200":{"description":"Provider adapter list"}}}},"/providers/catalog":{"get":{"summary":"Static provider catalog from INTEGRATIONS-aligned registry","responses":{"200":{"description":"Catalog rows"}}}},"/providers/integrations":{"get":{"summary":"Integration Center readiness rows","responses":{"200":{"description":"Integration center"}}}},"/providers/health":{"get":{"summary":"Run synthetic health checks across adapters","responses":{"200":{"description":"Health report"}}}},"/providers/feature-flags":{"get":{"summary":"Provider feature flag definitions and env snapshot","responses":{"200":{"description":"Feature flags"}}}},"/providers/health-check":{"post":{"summary":"Trigger provider health checks","responses":{"200":{"description":"Health report"}}}},"/providers/{catalogId}":{"get":{"summary":"Single adapter status, capabilities, and config validation","parameters":[{"in":"path","name":"catalogId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Adapter detail"},"404":{"description":"Not found"}}}},"/admin/provider-integrations":{"get":{"summary":"Admin Integration Center (gateway-backed when available)","responses":{"200":{"description":"Provider integrations"}}}},"/admin/integrations":{"get":{"summary":"Integration Hub catalogue and connection state (super_admin, platform_admin)","description":"Manifests for every connector (docs, verification level, sources, credential and settings fields, go-live gates) plus per-environment connection status. Credential values are never returned — only whether each field is set, when, by whom, and a short fingerprint.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Catalogue","vault status":null,"runtime environment and summary counts":null},"403":{"description":"Not a super or platform admin"}}}},"/admin/integrations/{connector}":{"get":{"summary":"One connector with both environments and its activity log","security":[{"bearerAuth":[]}],"parameters":[{"name":"connector","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Connector detail"},"404":{"description":"Unknown connector"}}}},"/admin/integrations/{connector}/{environment}/configuration":{"put":{"summary":"Save credentials (sealed with AES-256-GCM, write-only) and settings (super_admin)","description":"Any change takes the connection back to `configured`; it must be re-tested (and, in production, re-approved) before traffic resumes.","security":[{"bearerAuth":[]}],"parameters":[{"name":"connector","in":"path","required":true,"schema":{"type":"string"}},{"name":"environment","in":"path","required":true,"schema":{"type":"string","enum":["sandbox","production"]}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"credentials":{"type":"object","additionalProperties":{"type":"string"}},"settings":{"type":"object","additionalProperties":{"type":"string"}},"clear":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Updated connector detail"},"409":{"description":"Vault key not configured (INTEGRATION_VAULT_KEY)"},"422":{"description":"Credential format or environment mismatch (e.g. a live key in sandbox)"}}}},"/admin/integrations/{connector}/{environment}/test":{"post":{"summary":"Run an authenticated connectivity probe against the provider (super_admin)","security":[{"bearerAuth":[]}],"parameters":[{"name":"connector","in":"path","required":true,"schema":{"type":"string"}},{"name":"environment","in":"path","required":true,"schema":{"type":"string","enum":["sandbox","production"]}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"test_recipient":{"type":"string","description":"Messaging connectors only — sends one real test message"}}}}}},"responses":{"200":{"description":"Probe result (ok, outcome, http_status, duration_ms, request) and connection state"}}}},"/admin/integrations/{connector}/sandbox/activate":{"post":{"summary":"Activate a verified sandbox connection (super_admin)","security":[{"bearerAuth":[]}],"parameters":[{"name":"connector","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Activated"},"409":{"description":"Not verified or test older than 24 hours"}}}},"/admin/integrations/{connector}/production/request-go-live":{"post":{"summary":"Request production go-live after confirming every gate (super_admin)","security":[{"bearerAuth":[]}],"parameters":[{"name":"connector","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["gates"],"properties":{"gates":{"type":"array","items":{"type":"string"}},"contract_reference":{"type":"string","maxLength":120},"note":{"type":"string","maxLength":500}}}}}},"responses":{"200":{"description":"Awaiting approval"},"422":{"description":"Gates incomplete or contract reference missing"}}}},"/admin/integrations/{connector}/production/approve":{"post":{"summary":"Approve a go-live request — must be a different super admin (four-eyes)","security":[{"bearerAuth":[]}],"parameters":[{"name":"connector","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Live"},"403":{"description":"Requester cannot approve their own request"}}}},"/admin/integrations/{connector}/production/reject":{"post":{"summary":"Reject a go-live request","security":[{"bearerAuth":[]}],"parameters":[{"name":"connector","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Back to verified"}}}},"/admin/integrations/{connector}/{environment}/suspend":{"post":{"summary":"Suspend an active connection immediately (reason required)","security":[{"bearerAuth":[]}],"parameters":[{"name":"connector","in":"path","required":true,"schema":{"type":"string"}},{"name":"environment","in":"path","required":true,"schema":{"type":"string","enum":["sandbox","production"]}}],"responses":{"200":{"description":"Suspended"}}}},"/admin/integrations/{connector}/{environment}/resume":{"post":{"summary":"Resume a suspended connection (returns to configured; re-test required)","security":[{"bearerAuth":[]}],"parameters":[{"name":"connector","in":"path","required":true,"schema":{"type":"string"}},{"name":"environment","in":"path","required":true,"schema":{"type":"string","enum":["sandbox","production"]}}],"responses":{"200":{"description":"Resumed"}}}},"/admin/integrations/{connector}/{environment}/routing":{"post":{"summary":"Route customer payments to this active gateway (one primary per environment)","security":[{"bearerAuth":[]}],"parameters":[{"name":"connector","in":"path","required":true,"schema":{"type":"string"}},{"name":"environment","in":"path","required":true,"schema":{"type":"string","enum":["sandbox","production"]}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"primary":{"type":"boolean","default":true}}}}}},"responses":{"200":{"description":"Routing updated"}}}},"/admin/integrations/{connector}/{environment}/rotate-webhook":{"post":{"summary":"Issue a new webhook URL token","security":[{"bearerAuth":[]}],"parameters":[{"name":"connector","in":"path","required":true,"schema":{"type":"string"}},{"name":"environment","in":"path","required":true,"schema":{"type":"string","enum":["sandbox","production"]}}],"responses":{"200":{"description":"New webhook URL"}}}},"/admin/integrations/{connector}/{environment}/disconnect":{"post":{"summary":"Delete stored credentials and settings for this environment","security":[{"bearerAuth":[]}],"parameters":[{"name":"connector","in":"path","required":true,"schema":{"type":"string"}},{"name":"environment","in":"path","required":true,"schema":{"type":"string","enum":["sandbox","production"]}}],"responses":{"200":{"description":"Disconnected"}}}},"/integrations/webhooks/{connector}/{token}":{"post":{"summary":"Provider webhook receiver","description":"Authenticated by the unguessable path token and the provider's own scheme (HMAC, hashstring, AES-256-GCM, signed fields or shared secret). Payment events only trigger a re-read of the payment from the provider API. Duplicate deliveries are acknowledged without reprocessing.","security":[],"parameters":[{"name":"connector","in":"path","required":true,"schema":{"type":"string"}},{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"202":{"description":"Accepted (verified flag and outcome)"},"401":{"description":"Signature verification failed"},"404":{"description":"Unknown endpoint"}}}},"/auth/signup":{"post":{"summary":"Create traveler, agency, or partner account","security":[],"description":"Known seeded-demo email addresses are reserved case-insensitively and rejected before any user, profile, role or audit record is written.","responses":{"201":{"description":"Created"},"400":{"description":"Invalid signup fields or reserved_email"}}}},"/auth/login":{"post":{"summary":"Login with email/password with MFA readiness","security":[],"responses":{"200":{"description":"Authenticated"}}}},"/auth/logout":{"post":{"summary":"Revoke current session","responses":{"204":{"description":"Logged out"}}}},"/auth/refresh-token":{"post":{"summary":"Rotate refresh token","responses":{"200":{"description":"Token refreshed"}}}},"/auth/forgot-password":{"post":{"summary":"Request password reset","security":[],"responses":{"202":{"description":"Accepted"}}}},"/auth/reset-password":{"post":{"summary":"Complete password reset","security":[],"responses":{"204":{"description":"Reset"}}}},"/auth/verify-account":{"post":{"summary":"Verify email or phone account ownership","responses":{"204":{"description":"Verified"}}}},"/auth/profile":{"patch":{"summary":"Update authenticated profile","security":[{"bearerAuth":[]}],"description":"End-user identity required. Only allowlisted planning and existing profile fields are accepted; roles, identity and verified compliance state cannot be edited.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfilePatch"}}}},"responses":{"200":{"description":"Updated profile; read /auth/me for updated user fields","content":{"application/json":{"schema":{"type":"object","required":["profile"],"properties":{"profile":{"type":"object"}}}}}},"400":{"description":"Invalid profile fields"},"401":{"description":"Missing or expired bearer token"},"403":{"description":"API key without end-user identity"}}}},"/traveler/trips":{"get":{"summary":"List traveler trips","responses":{"200":{"description":"Trips"}}},"post":{"summary":"Create trip","responses":{"201":{"description":"Trip created"}}}},"/traveler/trips/{tripId}":{"patch":{"summary":"Update trip","parameters":[{"name":"tripId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Trip updated"}}}},"/traveler/trips/{tripId}/itinerary":{"get":{"summary":"Get active itinerary","parameters":[{"name":"tripId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Itinerary"}}}},"/traveler/expenses":{"post":{"summary":"Add expense","responses":{"201":{"description":"Expense created"}}}},"/traveler/receipts":{"post":{"summary":"Upload receipt metadata and request signed upload URL","responses":{"201":{"description":"Receipt upload prepared"}}}},"/traveler/wallet":{"get":{"summary":"View wallet","responses":{"200":{"description":"Wallet"}}}},"/traveler/transactions":{"get":{"summary":"View transactions","responses":{"200":{"description":"Transactions"}}}},"/traveler/support-tickets":{"post":{"summary":"Create support ticket","responses":{"201":{"description":"Ticket created"}}}},"/agency/clients":{"post":{"summary":"Create agency client","responses":{"201":{"description":"Client created"}}}},"/agency/itineraries":{"post":{"summary":"Create itinerary","responses":{"201":{"description":"Itinerary created"}}}},"/agency/travelers/{travelerId}/assign":{"post":{"summary":"Assign traveler to agency trip","parameters":[{"name":"travelerId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Assigned"}}}},"/agency/payments/collect":{"post":{"summary":"Collect payment from client or traveler","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"Payment collection started"}}}},"/agency/invoices":{"post":{"summary":"Create invoice","responses":{"201":{"description":"Invoice created"}}}},"/agency/disbursements":{"post":{"summary":"Schedule disbursement","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"Disbursement scheduled"}}}},"/agency/reports":{"get":{"summary":"View agency financial reports","responses":{"200":{"description":"Reports"}}}},"/agency/staff":{"post":{"summary":"Manage staff member","responses":{"201":{"description":"Staff invited"}}}},"/admin/users":{"get":{"summary":"Manage users","responses":{"200":{"description":"Users"}}}},"/admin/agencies":{"get":{"summary":"Manage agencies","responses":{"200":{"description":"Agencies"}}}},"/admin/transactions":{"get":{"summary":"View transactions","responses":{"200":{"description":"Transactions"}}}},"/admin/compliance/reviews":{"post":{"summary":"Review compliance profile","responses":{"201":{"description":"Review recorded"}}}},"/admin/content/pages":{"post":{"summary":"Manage content pages","responses":{"201":{"description":"Page saved"}}}},"/admin/support-tickets":{"get":{"summary":"Manage support tickets","responses":{"200":{"description":"Tickets"}}}},"/admin/audit-logs":{"get":{"summary":"View audit logs","responses":{"200":{"description":"Audit logs"}}}},"/admin/analytics":{"get":{"summary":"Platform analytics","responses":{"200":{"description":"Analytics"}}}},"/payments/intents":{"post":{"summary":"Create payment intent","description":"When a super admin has routed payments to an active gateway in the Integration Hub, the intent opens a hosted checkout and returns `next_action` (redirect, widget or signed form post). The payment settles only after `/payments/intents/{id}/confirm` (or the provider webhook) reads a successful, amount-matching result from the provider API.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["amount_minor"],"properties":{"amount_minor":{"type":"integer","minimum":1},"currency":{"type":"string","default":"SAR"},"metadata":{"type":"object"},"return_url":{"type":"string","maxLength":500,"description":"Same-origin page the provider returns the payer to"},"language":{"type":"string","enum":["en","ar"]}}}}}},"responses":{"201":{"description":"Intent created","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string"},"provider":{"type":"string","description":"`sandbox` or `hub:<connector>`"},"next_action":{"type":"object","nullable":true,"properties":{"type":{"type":"string","enum":["redirect","widget","form_post"]},"url":{"type":"string"},"script_url":{"type":"string"},"fields":{"type":"object"}}}}}}}},"502":{"description":"The payment provider could not start checkout"}}}},"/payments/intents/{intentId}":{"get":{"summary":"Read a payment intent (payer or finance staff), including any hosted-checkout next_action","security":[{"bearerAuth":[]}],"parameters":[{"name":"intentId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Payment intent"},"404":{"description":"Not found or not yours"}}}},"/payment-routing/quote":{"post":{"summary":"Rank legally and operationally eligible payment rails","description":"Sandbox decision support only; a quote does not authorize or execute money movement.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["amount_minor","currency","country","payment_method","merchant_type","customer_type"],"properties":{"amount_minor":{"type":"integer","format":"int64","minimum":1},"currency":{"type":"string","pattern":"^[A-Z]{3}$"},"country":{"type":"string","pattern":"^[A-Z]{2}$"},"payment_method":{"type":"string"},"merchant_type":{"type":"string"},"customer_type":{"type":"string","enum":["consumer","business"]},"fraud_risk":{"type":"number","minimum":0,"maximum":100,"default":0},"compliance_risk":{"type":"number","minimum":0,"maximum":100,"default":0},"chargeback_risk":{"type":"number","minimum":0,"maximum":100,"default":0},"eligible_backend_settlement":{"type":"boolean","default":false}}}}}},"responses":{"200":{"description":"Ranked route decision"},"400":{"description":"Invalid request or no eligible rail"}}}},"/payments/{paymentId}/confirm":{"post":{"summary":"Confirm payment","parameters":[{"name":"paymentId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Confirmed"}}}},"/payments/webhooks/{provider}":{"post":{"summary":"Handle signed payment webhook","security":[],"parameters":[{"name":"provider","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"202":{"description":"Accepted"}}}},"/payments/{paymentId}/refund":{"post":{"summary":"Refund payment","parameters":[{"name":"paymentId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"Refund created"}}}},"/payments/transactions":{"get":{"summary":"List payment transactions","responses":{"200":{"description":"Transactions"}}}},"/payments/reconcile":{"post":{"summary":"Reconcile payment provider events","responses":{"202":{"description":"Reconciliation queued"}}}},"/disbursements":{"post":{"summary":"Create payout","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"Payout created"}}}},"/disbursements/{disbursementId}/approve":{"post":{"summary":"Approve payout","parameters":[{"name":"disbursementId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Approved"}}}},"/disbursements/{disbursementId}/reject":{"post":{"summary":"Reject payout","parameters":[{"name":"disbursementId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Rejected"}}}},"/disbursements/{disbursementId}":{"get":{"summary":"Payout status","parameters":[{"name":"disbursementId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Payout"}}}},"/disbursements/reports/suppliers":{"get":{"summary":"Supplier payout report","responses":{"200":{"description":"Supplier payout report"}}}},"/users":{"get":{"summary":"List users with pagination, filtering, and RBAC","parameters":[{"name":"page","in":"query","schema":{"type":"integer","minimum":1}},{"name":"status","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Users"}}}},"/roles":{"get":{"summary":"List roles and permissions","responses":{"200":{"description":"Roles"}}}},"/roles/{roleId}/permissions":{"put":{"summary":"Replace role permissions","parameters":[{"name":"roleId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Permissions updated"}}}},"/profiles/me":{"get":{"summary":"Get authenticated profile with KYC-ready fields","responses":{"200":{"description":"Profile"}}},"patch":{"summary":"Update authenticated profile preferences and KYC-ready fields","responses":{"200":{"description":"Profile updated"}}}},"/partners":{"get":{"summary":"List partners with type, status, and market filters","responses":{"200":{"description":"Partners"}}},"post":{"summary":"Create hotel, transport, marketplace, merchant, technology, payment, or data partner","responses":{"201":{"description":"Partner created"}}}},"/activity-providers":{"get":{"summary":"List indoor and outdoor activity providers","responses":{"200":{"description":"Activity providers"}}},"post":{"summary":"Onboard activity provider with KYB readiness","responses":{"201":{"description":"Activity provider created"}}}},"/tour-guides":{"get":{"summary":"List tour guides and language/location filters","responses":{"200":{"description":"Tour guides"}}},"post":{"summary":"Create tour guide profile","responses":{"201":{"description":"Tour guide created"}}}},"/corporate-accounts":{"get":{"summary":"List corporate travel accounts","responses":{"200":{"description":"Corporate accounts"}}},"post":{"summary":"Create corporate account with policy configuration","responses":{"201":{"description":"Corporate account created"}}}},"/activities":{"get":{"summary":"Discover indoor, outdoor, Hajj, Umrah, tour, hotel, transport, and experience listings","parameters":[{"name":"category","in":"query","schema":{"type":"string"}},{"name":"location","in":"query","schema":{"type":"string"}},{"name":"sort","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Activities"}}},"post":{"summary":"Create provider activity listing","responses":{"201":{"description":"Activity created"}}}},"/marketplace/listings":{"get":{"summary":"List marketplace listings across B2B, B2B2C, C2C, guides, activities, Hajj and Umrah","responses":{"200":{"description":"Listings"}}},"post":{"summary":"Create marketplace listing","responses":{"201":{"description":"Listing created"}}}},"/marketplace/bookings":{"post":{"summary":"Create booking request and optional payment link","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"Booking requested"}}}},"/marketplace/reviews":{"post":{"summary":"Submit review for listing moderation","responses":{"201":{"description":"Review submitted"}}}},"/wallets":{"get":{"summary":"List wallets visible to current principal","responses":{"200":{"description":"Wallets"}}},"post":{"summary":"Create wallet for traveler, agency, platform, supplier, affiliate, partner, or corporate account","responses":{"201":{"description":"Wallet created"}}}},"/wallets/{walletId}/balances":{"get":{"summary":"List wallet balances by currency","parameters":[{"name":"walletId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Balances"}}}},"/wallets/{walletId}/ledger-entries":{"get":{"summary":"List append-only ledger entries","parameters":[{"name":"walletId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Ledger entries"}}}},"/cards/requests":{"post":{"summary":"Request virtual or physical travel card through provider abstraction","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"Card request created"}}}},"/cards/{cardId}/controls":{"patch":{"summary":"Update spending limits, travel controls, MCC controls, or freeze state","parameters":[{"name":"cardId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Card controls updated"}}}},"/cards/{cardId}/transactions":{"get":{"summary":"List card transactions","parameters":[{"name":"cardId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Card transactions"}}}},"/crypto/wallets":{"get":{"summary":"List provider-custodied crypto wallet references","responses":{"200":{"description":"Crypto wallets"}}},"post":{"summary":"Request crypto wallet address through licensed provider abstraction","responses":{"201":{"description":"Crypto wallet requested"}}}},"/crypto/transactions":{"post":{"summary":"Create deposit, withdrawal, payment, or refund request for manual/provider review","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"Crypto transaction requested"}}},"get":{"summary":"List crypto transactions with network, hash, risk, and compliance status filters","responses":{"200":{"description":"Crypto transactions"}}}},"/fx/rates":{"get":{"summary":"List FX rates by pair and provider","responses":{"200":{"description":"FX rates"}}}},"/cross-border/transfers":{"post":{"summary":"Create cross-border transfer review request","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"Cross-border transfer requested"}}}},"/compliance/kyc-records":{"post":{"summary":"Create or refresh KYC provider review placeholder","responses":{"201":{"description":"KYC record created"}}}},"/compliance/kyb-records":{"post":{"summary":"Create or refresh KYB provider review placeholder","responses":{"201":{"description":"KYB record created"}}}},"/compliance/risk-flags":{"get":{"summary":"List risk flags with severity and status filters","responses":{"200":{"description":"Risk flags"}}},"post":{"summary":"Create AML, fraud, card, crypto, or manual review risk flag","responses":{"201":{"description":"Risk flag created"}}}},"/admin/marketplace/moderation":{"get":{"summary":"List marketplace listings, activities, guides, and reviews awaiting moderation","responses":{"200":{"description":"Moderation queue"}}}},"/admin/payments/approvals":{"get":{"summary":"List payment, refund, dispute, settlement, and manual adjustment approval queues","responses":{"200":{"description":"Approval queues"}}}},"/admin/feature-flags":{"get":{"summary":"List feature flags for market, provider, card, crypto, AI, and marketplace rollout","responses":{"200":{"description":"Feature flags"}}}},"/admin/regulatory-documents":{"post":{"summary":"Create terms, privacy, AML, compliance, card, crypto, or regulatory CMS document","responses":{"201":{"description":"Regulatory document saved"}}}},"/support/tickets":{"get":{"summary":"List support tickets visible to current principal","responses":{"200":{"description":"Support tickets"}}},"post":{"summary":"Create support ticket","responses":{"201":{"description":"Support ticket created"}}}},"/notifications":{"get":{"summary":"List notifications","responses":{"200":{"description":"Notifications"}}}},"/cms/pages":{"get":{"summary":"List CMS pages by locale, slug, and status","responses":{"200":{"description":"CMS pages"}}}},"/cms/blog-posts":{"get":{"summary":"List blog and insight posts","responses":{"200":{"description":"Blog posts"}}}},"/cms/faqs":{"get":{"summary":"List FAQs by locale and category","responses":{"200":{"description":"FAQs"}}}},"/ai/conversations":{"post":{"summary":"Start AI travel, Hajj/Umrah, activity, expense, support, fraud, content, agency, or DaaS conversation","responses":{"201":{"description":"AI conversation started"}}}},"/ai/conversations/{conversationId}/messages":{"post":{"summary":"Send AI message through configured provider abstraction","parameters":[{"name":"conversationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"AI message created"}}}},"/developer/api-keys":{"get":{"summary":"List API keys for developer portal","responses":{"200":{"description":"API keys"}}},"post":{"summary":"Create scoped API key and return one-time secret","responses":{"201":{"description":"API key created"}}}},"/webhooks":{"get":{"summary":"List webhook events with provider, status, and replay filters","responses":{"200":{"description":"Webhook events"}}}},"/reports/financial":{"get":{"summary":"Financial reports for wallets, transactions, settlements, disputes and reconciliation","responses":{"200":{"description":"Financial reports"}}}},"/search":{"get":{"operationId":"searchOwnedResources","tags":["Search"],"summary":"Search minimal references to owned resources","security":[{"bearerAuth":[]}],"description":"Executable GET /v1/search. Bearer-user authentication only; API-key\nsubjects are rejected. All roles, including administrators, are restricted\nto owned records. No broad legacy permission grants platform-wide search.\nMatches case-insensitive literal substrings of internal IDs only, never\nnames, emails, ticket subjects, descriptions, KYC, provider references,\npayment credentials, beneficiary details or arbitrary metadata.\nResults contain static labels and references, not resource bodies or links.\nClients must render all strings as text, not HTML, and must not construct\ndeep links without separate destination authorization. Identify results\nusing the pair (type, ref), not ref alone.\n\nCoverage: customer = own users record; business = owned active organization\nselected by validated membership; transaction = own payments without an\nagency association (not the legacy transactions table); transfer = own\ncross_border_transfers; booking = own booking_requests (not legacy bookings),\nrequiring marketplace.view; refund = refunds linked to own non-agency\npayment, requiring payments.refund; support = own non-agency support_tickets.\nOther supported sources use existing authenticated own-resource access.\nMerchant, invoice and compliance search are intentionally omitted.\nAgencies, legacy transactions/bookings and subscription invoices are not\nsearched. Coverage metadata describes eligible categories, not record counts.\n\nTenant comes exclusively from middleware-validated membership and optional\nX-Tenant-Id, never from query parameters. Tenant-tagged records must match\nthe active tenant; untagged legacy records are searched only when there\nis no active tenant. Both refund and its parent payment must be in scope.\nResults use fixed category order then store insertion order, with no total\ncount or pagination. At most limit rows plus one authorized match are\ncollected to determine has_more. Reads scan the existing in-memory store;\nno search index or database migration is introduced.\n","parameters":[{"name":"q","in":"query","required":true,"description":"Single literal query; trim whitespace, minimum 2 characters, maximum raw length 100.","schema":{"type":"string","minLength":2,"maxLength":100}},{"name":"limit","in":"query","description":"Single decimal integer; malformed, repeated and out-of-range values are rejected.","schema":{"type":"integer","minimum":1,"maximum":20,"default":20}},{"name":"X-Tenant-Id","in":"header","description":"Optional active organization ID; requires active membership. Otherwise first active membership is selected.","schema":{"type":"string"}}],"responses":{"200":{"description":"Minimal owned references; empty rows is a valid result. Cache-Control is no-store.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["rows","limit","has_more","coverage"],"properties":{"rows":{"type":"array","maxItems":20,"items":{"type":"object","additionalProperties":false,"required":["type","ref","label"],"properties":{"type":{"type":"string","enum":["customer","business","transaction","transfer","booking","refund","support"]},"ref":{"type":"string","maxLength":128},"label":{"type":"string","description":"Static resource label; no user-entered content."}}}},"limit":{"type":"integer","minimum":1,"maximum":20},"has_more":{"type":"boolean","description":"Another authorized matching reference exists."},"coverage":{"type":"object","additionalProperties":false,"required":["mode","tenant_scope","included","omitted"],"properties":{"mode":{"type":"string","const":"owned_reference_only"},"tenant_scope":{"type":"string","enum":["personal","active_tenant"]},"included":{"type":"array","items":{"type":"string"}},"omitted":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["type","reason"],"properties":{"type":{"type":"string"},"reason":{"type":"string"}}}}}}}}}}},"400":{"description":"Invalid query or limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"API-key-only authentication or invalid tenant membership.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/health":{"get":{"summary":"Health check endpoint for deployment probes","security":[],"responses":{"200":{"description":"Healthy"}}}},"/version":{"get":{"summary":"API version metadata","security":[],"responses":{"200":{"description":"Version metadata"}}}},"/catalog/countries":{"get":{"summary":"Public country catalog","security":[],"responses":{"200":{"description":"Countries"}}}},"/catalog/currencies":{"get":{"summary":"Public currency catalog","security":[],"responses":{"200":{"description":"Currencies"}}}},"/catalog/activity-categories":{"get":{"summary":"Public activity category catalog","security":[],"responses":{"200":{"description":"Activity categories"}}}},"/catalog/expense-categories":{"get":{"summary":"Public expense category catalog","security":[],"responses":{"200":{"description":"Expense categories"}}}},"/catalog/feature-flags":{"get":{"summary":"Public feature flag catalog","security":[],"responses":{"200":{"description":"Feature flags"}}}},"/auth/me":{"get":{"summary":"Resolve authenticated profile, roles, and permissions","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Authenticated profile without credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthProfile"}}}},"401":{"description":"Missing or expired bearer token"},"403":{"description":"API key without end-user identity"}}}},"/auth/refresh":{"post":{"summary":"Rotate access token using a refresh token (alias of /auth/refresh-token)","security":[],"responses":{"200":{"description":"Refreshed"}}}},"/auth/mfa/enrol":{"post":{"summary":"Enrol an MFA TOTP secret (returns otpauth URL)","responses":{"200":{"description":"MFA enrolment"}}}},"/auth/mfa/confirm":{"post":{"summary":"Confirm enrolment with a generated TOTP code","responses":{"200":{"description":"MFA confirmed"}}}},"/auth/password":{"post":{"summary":"Change the signed-in user's password","description":"Requires the current password (throttled with the sign-in lockout). Ends every existing session and returns one fresh session for the calling device; a security notice is sent to the account email.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["current_password","new_password"],"properties":{"current_password":{"type":"string","minLength":1,"maxLength":256},"new_password":{"type":"string","minLength":10,"maxLength":256,"description":"Must satisfy the password policy"}}}}}},"responses":{"200":{"description":"Password changed; previous sessions revoked","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"revoked_count":{"type":"integer"},"access_token":{"type":"string"},"refresh_token":{"type":"string"},"expires_in":{"type":"integer"}}}}}},"400":{"description":"Current password incorrect (invalid_current_password)","unchanged (password_unchanged) or policy failure (weak_password)":null},"401":{"description":"Missing or expired bearer token"},"429":{"description":"Too many incorrect attempts"}}}},"/trips":{"parameters":[{"$ref":"#/components/parameters/PlanningTenant"}],"get":{"summary":"List the authenticated traveler's trips","security":[{"bearerAuth":[]}],"description":"Owner and explicit trip-context scoped. Without X-Tenant-Id, lists personal/null-tenant trips even after joining organizations. An explicit valid header selects that organization's trips. agency_id never broadens access. Omitting pagination and sorting preserves existing list order and all matching rows.","parameters":[{"name":"q","in":"query","schema":{"type":"string","maxLength":160},"description":"Case-insensitive name or destination search"},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}},{"name":"sort","in":"query","schema":{"type":"string","enum":["name","starts_on","created_at","updated_at"]}},{"name":"order","in":"query","schema":{"type":"string","enum":["asc","desc"],"default":"asc"}}],"responses":{"200":{"description":"Owner trips","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripList"}}}},"400":{"description":"Invalid query parameters"},"401":{"description":"Missing or expired bearer token"},"403":{"description":"API key without end-user identity"}}},"post":{"summary":"Create a traveler-owned trip","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/PlanningIdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripInput"}}}},"responses":{"201":{"description":"Trip created with version 1","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Trip"}}}},"400":{"description":"Invalid trip fields","date order or agency_delegation_unavailable for non-null agency assignment":null},"401":{"description":"Missing or expired bearer token"},"403":{"description":"API key without end-user identity"},"409":{"description":"Idempotency key already used with another body"}}}},"/trips/{tripId}":{"parameters":[{"$ref":"#/components/parameters/PlanningTenant"}],"get":{"summary":"Fetch trip detail","security":[{"bearerAuth":[]}],"parameters":[{"name":"tripId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Owner trip","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Trip"}}}},"401":{"description":"Missing or expired bearer token"},"403":{"description":"API key without end-user identity"},"404":{"description":"Missing trip or different owner or active tenant"}}},"patch":{"summary":"Update an owned trip using optimistic concurrency","security":[{"bearerAuth":[]}],"parameters":[{"name":"tripId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripPatch"}}}},"responses":{"200":{"description":"Updated trip with incremented version","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Trip"}}}},"400":{"description":"Missing version or invalid merged trip fields"},"401":{"description":"Missing or expired bearer token"},"403":{"description":"API key without end-user identity"},"404":{"description":"Missing trip or different owner or active tenant"},"409":{"description":"trip_version_conflict; reload trip before retrying"}}}},"/trips/{tripId}/itinerary":{"parameters":[{"$ref":"#/components/parameters/PlanningTenant"}],"get":{"summary":"Fetch itinerary items","security":[{"bearerAuth":[]}],"parameters":[{"name":"tripId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Owner itinerary or empty items","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Itinerary"}}}},"401":{"description":"Missing or expired bearer token"},"403":{"description":"API key without end-user identity"},"404":{"description":"Missing trip or different owner or active tenant"}}},"post":{"summary":"Add itinerary item","security":[{"bearerAuth":[]}],"parameters":[{"name":"tripId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/PlanningIdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ItineraryItemInput"}}}},"responses":{"201":{"description":"Itinerary item created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ItineraryItem"}}}},"400":{"description":"Invalid fields","calendar dates":null,"timezone offsets":null,"date order":null,"currency or safe integer money":null},"401":{"description":"Missing or expired bearer token"},"403":{"description":"API key without end-user identity"},"404":{"description":"Missing trip or different owner or active tenant"},"409":{"description":"Idempotency key already used with another body"}}}},"/trips/{tripId}/expenses":{"parameters":[{"$ref":"#/components/parameters/PlanningTenant"}],"get":{"summary":"List trip expenses","security":[{"bearerAuth":[]}],"parameters":[{"name":"tripId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Owner expenses","content":{"application/json":{"schema":{"type":"object","required":["rows","total"],"properties":{"rows":{"type":"array","items":{"$ref":"#/components/schemas/TripExpense"}},"total":{"type":"integer","minimum":0}}}}}},"401":{"description":"Missing or expired bearer token"},"403":{"description":"API key without end-user identity"},"404":{"description":"Missing trip or different owner or active tenant"}}},"post":{"summary":"Add expense","security":[{"bearerAuth":[]}],"parameters":[{"name":"tripId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/PlanningIdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripExpenseInput"}}}},"responses":{"201":{"description":"Expense created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripExpense"}}}},"400":{"description":"Invalid fields","supported currency or safe integer money":null},"401":{"description":"Missing or expired bearer token"},"403":{"description":"API key without end-user identity"},"404":{"description":"Missing trip or different owner or active tenant"},"409":{"description":"Idempotency key already used with another body"}}}},"/trips/{tripId}/budget":{"parameters":[{"$ref":"#/components/parameters/PlanningTenant"}],"get":{"summary":"Currency-separated trip budget rollup","security":[{"bearerAuth":[]}],"parameters":[{"name":"tripId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Owner trip budget with no mixed-currency addition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripBudgetSummary"}}}},"400":{"description":"Legacy unsupported currency or unsafe aggregate integer"},"401":{"description":"Missing or expired bearer token"},"403":{"description":"API key without end-user identity"},"404":{"description":"Missing trip or different owner or active tenant"}}},"post":{"summary":"Create a TripBudget with per-category planned amounts","security":[{"bearerAuth":[]}],"parameters":[{"name":"tripId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"currency":{"$ref":"#/components/schemas/PlanningCurrency"},"categories":{"type":"array","maxItems":13,"items":{"type":"object","required":["category"],"properties":{"category":{"type":"string","enum":["flights","hotels","visa","hajj_umrah_package","transport","food","shopping","activities","esim","insurance","fx","atm_fees","emergency_reserve"]},"planned_minor":{"type":"integer","minimum":0,"maximum":9007199254740991,"default":0}}}}}}}}},"responses":{"201":{"description":"Budget created"},"400":{"description":"Invalid currency","safe integer amount":null,"category or duplicate budget":null},"401":{"description":"Missing or expired bearer token"},"403":{"description":"API key without end-user identity"},"404":{"description":"Missing trip or different owner or active tenant"}}}},"/fx/quote":{"post":{"summary":"Quote an FX conversion with applied spread","responses":{"200":{"description":"FX quote"}}}},"/admin/reports/marketplace":{"get":{"summary":"Marketplace KPI report","responses":{"200":{"description":"Marketplace report"}}}},"/admin/reports/compliance":{"get":{"summary":"Compliance KPI report","responses":{"200":{"description":"Compliance report"}}}},"/admin/reports/financial":{"get":{"summary":"Financial KPI report (wallets, payments, refunds, ledger reconciliation)","responses":{"200":{"description":"Financial report"}}}},"/admin/feature-flags/{key}":{"patch":{"summary":"Toggle a feature flag","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Feature flag updated"}}}},"/admin/users/{id}/status":{"patch":{"summary":"Update a user's account status (admin)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Status updated"}}}},"/compliance/kyc":{"post":{"summary":"Submit KYC for the authenticated user","responses":{"201":{"description":"KYC submitted"}}}},"/compliance/kyb":{"post":{"summary":"Submit KYB for an organisation owner","responses":{"201":{"description":"KYB submitted"}}}},"/compliance/aml/screen":{"post":{"summary":"Run an AML screen and open a review if applicable","responses":{"200":{"description":"AML result"}}}},"/compliance/reviews/{id}/decide":{"post":{"summary":"Approve or reject a compliance review","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Review decided"}}}},"/support/tickets/{id}/replies":{"post":{"summary":"Reply to a support ticket","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"Reply created"}}}},"/support/tickets/{id}/close":{"post":{"summary":"Close a support ticket","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Ticket closed"}}}},"/settlements":{"get":{"summary":"List settlements visible to the principal","responses":{"200":{"description":"Settlements"}}}},"/settlements/pending":{"get":{"summary":"Pending settlements grouped by owner (admin)","responses":{"200":{"description":"Pending settlements"}}}},"/settlements/run":{"post":{"summary":"Run a settlement batch for an owner (idempotent)","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"Settlement created"}}}},"/settlements/{id}":{"get":{"summary":"Settlement detail","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Settlement"}}}},"/receipts/uploads":{"post":{"summary":"Request a signed upload URL for a receipt","responses":{"201":{"description":"Upload prepared"}}}},"/receipts/{id}/confirm":{"post":{"summary":"Confirm receipt upload completed","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Receipt confirmed"}}}},"/receipts/{id}/categorize":{"post":{"summary":"Run AI auto-categorization on a receipt","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Receipt categorized"}}}},"/receipts/{id}/download":{"get":{"summary":"Signed download URL (uploader-only)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Signed URL"}}}},"/receipts":{"get":{"summary":"List my receipts","responses":{"200":{"description":"Receipts"}}}},"/groups":{"get":{"summary":"List groups","responses":{"200":{"description":"Groups"}}},"post":{"summary":"Create group (provisions wallet + checklist)","responses":{"201":{"description":"Group created"}}}},"/groups/{id}":{"get":{"summary":"Group detail with members, contributions, checklist and budget","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Group detail"}}}},"/groups/{id}/members":{"post":{"summary":"Add a member to a group","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"Member added"}}}},"/groups/{id}/members/{memberId}":{"patch":{"summary":"Change an active member role or membership state (leader only)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"memberId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Membership updated"},"403":{"description":"Leader permission required"}}}},"/groups/{id}/leader":{"post":{"summary":"Change the primary mission leader without expanding existing location grants","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Leader changed"}}}},"/groups/{id}/leave":{"post":{"summary":"Leave a group and revoke the member's active group location sessions","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Membership ended and location sharing stopped"}}}},"/groups/{id}/invitations":{"post":{"summary":"Create a signed, expiring, limited-use group invitation","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"Invitation token returned once"}}}},"/group-invitations/redeem":{"post":{"summary":"Redeem an unexpired signed invitation","responses":{"200":{"description":"Active or approval-pending membership created"},"400":{"description":"Expired or invalid invitation"}}}},"/groups/{id}/missions":{"post":{"summary":"Create a scheduled group mission with participants and meeting point","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"Mission created"}}}},"/groups/{id}/missions/{missionId}":{"get":{"summary":"Read mission, task and check-in state","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"missionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Mission detail"}}},"patch":{"summary":"Reassign, cancel, complete or update a mission (leader only)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"missionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Mission updated"}}}},"/groups/{id}/missions/{missionId}/tasks":{"post":{"summary":"Add and assign a mission task","responses":{"201":{"description":"Task created"}}}},"/groups/{id}/missions/{missionId}/tasks/{taskId}":{"patch":{"summary":"Update task progress or assignment","responses":{"200":{"description":"Task updated"}}}},"/groups/{id}/missions/{missionId}/check-ins":{"post":{"summary":"Record a timestamped manual on-my-way, arrived, need-help or check-in signal","responses":{"201":{"description":"Check-in recorded"}}}},"/groups/{id}/announcements":{"post":{"summary":"Publish a deduplicated TripPay group announcement","responses":{"201":{"description":"Announcement recorded with in-app delivery states"}}}},"/groups/{id}/announcements/{announcementId}/acknowledge":{"post":{"summary":"Acknowledge a group announcement","responses":{"200":{"description":"Acknowledgement recorded"}}}},"/groups/{id}/location-sessions":{"post":{"summary":"Start an explicit, time-limited foreground location session","description":"Viewer IDs are snapshotted at grant time. Approximate precision is the default.","responses":{"201":{"description":"Consent grant and location session created"}}}},"/groups/{id}/location-sessions/current":{"delete":{"summary":"Stop current location sharing and revoke its consent grant","responses":{"200":{"description":"Sharing stopped"}}}},"/groups/{id}/locations":{"get":{"summary":"List only unexpired locations explicitly shared with the requesting member","responses":{"200":{"description":"Consent-filtered live locations"}}}},"/groups/{id}/location-sharing/viewers":{"get":{"summary":"Show current location-sharing state, precision, expiry and active viewers","responses":{"200":{"description":"Current sharing viewers"}}}},"/groups/channels/capabilities":{"get":{"summary":"List truthful share, open, connect and send capability flags","responses":{"200":{"description":"Channel capabilities"}}}},"/groups/{id}/channel-preferences/{channel}":{"put":{"summary":"Set a member's explicit group-channel and reminder preference","responses":{"200":{"description":"Preference stored"}}}},"/groups/{id}/communications/preview":{"post":{"summary":"Prepare an exact communication preview requiring confirmation","responses":{"201":{"description":"Pending confirmation with identity","audience":null,"content and location disclosure":null}}}},"/groups/{id}/communications/{deliveryId}/confirm":{"post":{"summary":"Confirm an in-app send or prepare an external native-share action","responses":{"200":{"description":"Truthful accepted or share-ready state"}}}},"/groups/{id}/contributions":{"post":{"summary":"Contribute to the group wallet (idempotent, ledger-backed)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"Contribution recorded"}}}},"/groups/{id}/checklist/{itemId}":{"patch":{"summary":"Update a checklist item status","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"itemId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Checklist updated"}}}},"/exports":{"get":{"summary":"List available CSV exports","responses":{"200":{"description":"Exports"}}}},"/exports/{name}":{"get":{"summary":"Stream a CSV export","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"CSV","content":{"text/csv":{"schema":{"type":"string"}}}}}}},"/liveness":{"get":{"summary":"Liveness probe","security":[],"responses":{"200":{"description":"Alive"}}}},"/readiness":{"get":{"summary":"Readiness probe (ledger reconciled, reference data present)","security":[],"responses":{"200":{"description":"Ready"},"503":{"description":"Not ready"}}}},"/metrics":{"get":{"summary":"Prometheus-style metrics exposition","security":[],"responses":{"200":{"description":"Metrics","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/events/stream":{"get":{"summary":"Server-Sent Events realtime stream (filtered by tenant + types)","description":"Returns a `text/event-stream` connection that emits domain events\n(payment.intent.confirm, marketplace.booking.request, ...).\nEventSource clients must pass the access token via the\n`access_token` query parameter (Authorization headers are not\nsupported by EventSource).\n","responses":{"200":{"description":"SSE stream","content":{"text/event-stream":{"schema":{"type":"string"}}}}}}},"/events/recent":{"get":{"summary":"Recent domain events derived from the audit log","responses":{"200":{"description":"Events"}}}},"/events/test":{"post":{"summary":"Emit a test event (settings.manage required)","responses":{"201":{"description":"Event emitted"}}}},"/organizations":{"get":{"summary":"List the current user's organization memberships","responses":{"200":{"description":"Memberships"}}},"post":{"summary":"Create an organization (the caller becomes owner)","responses":{"201":{"description":"Organization created"}}}},"/organizations/{id}":{"get":{"summary":"Organization detail","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Organization"}}}},"/organizations/{id}/members":{"post":{"summary":"Invite a user to an organization","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"Member added"}}}},"/webhook-subscriptions":{"get":{"summary":"List webhook subscriptions visible to the caller","responses":{"200":{"description":"Subscriptions"}}},"post":{"summary":"Create a webhook subscription","responses":{"201":{"description":"Subscription created"}}}},"/webhook-subscriptions/{id}/pause":{"post":{"summary":"Pause delivery","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Paused"}}}},"/webhook-subscriptions/{id}/resume":{"post":{"summary":"Resume delivery","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Resumed"}}}},"/webhook-subscriptions/{id}":{"delete":{"summary":"Revoke subscription","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Revoked"}}}},"/webhook-deliveries":{"get":{"summary":"List recent webhook deliveries (with retry status)","responses":{"200":{"description":"Deliveries"}}}},"/webhook-deliveries/drain":{"post":{"summary":"Force the worker to drain due deliveries (settings.manage required)","responses":{"200":{"description":"Drained"}}}},"/webhook-deliveries/{id}/replay":{"post":{"summary":"Re-queue a previously-processed delivery","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Replayed"}}}},"/webhook-stats":{"get":{"summary":"Per-subscription delivery stats (counts, p95 latency)","responses":{"200":{"description":"Stats"}}}},"/push/subscriptions":{"get":{"summary":"List my Web Push subscriptions","responses":{"200":{"description":"Subscriptions"}}},"post":{"summary":"Register a Web Push subscription","responses":{"201":{"description":"Subscribed"}}}},"/push/subscriptions/{id}":{"delete":{"summary":"Revoke a Web Push subscription","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Revoked"}}}},"/push/test":{"post":{"summary":"Send a test push to all of my active subscriptions","responses":{"200":{"description":"Sent"}}}},"/push/broadcast":{"post":{"summary":"Broadcast push to many users (settings.manage)","responses":{"200":{"description":"Sent"}}}},"/receipts/{id}/extract":{"post":{"summary":"Run OCR + AI categorization on a receipt; backfills the linked expense","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Receipt extracted"}}}},"/billing/subscriptions":{"get":{"summary":"List subscriptions (admins see all; members see their own)","responses":{"200":{"description":"Subscriptions"}}},"post":{"summary":"Create a SaaS subscription (idempotent)","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"Subscription created"}}}},"/billing/subscriptions/{id}":{"get":{"summary":"Subscription detail","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Subscription"}}}},"/billing/subscriptions/{id}/switch":{"post":{"summary":"Switch plan","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Switched"}}}},"/billing/subscriptions/{id}/cancel":{"post":{"summary":"Cancel (immediate or at period end)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Cancellation scheduled"}}}},"/billing/subscriptions/{id}/charge":{"post":{"summary":"Charge the current period (payments.manage required)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Charge attempted"}}}},"/billing/invoices":{"get":{"summary":"List subscription invoices","responses":{"200":{"description":"Invoices"}}}},"/insights/overview":{"get":{"summary":"Time-series overview for the last `days` days (reports.view required)","responses":{"200":{"description":"Overview"}}}},"/insights/series":{"get":{"summary":"Single-collection time-series (reports.view required)","responses":{"200":{"description":"Series"}}}},"/compliance/documents/uploads":{"post":{"summary":"Request a signed upload URL for a KYC/KYB document","responses":{"201":{"description":"Upload prepared"}}}},"/compliance/documents":{"get":{"summary":"List compliance documents (reviewers see all; users see their own)","responses":{"200":{"description":"Documents"}}}},"/compliance/documents/queue":{"get":{"summary":"Reviewer queue (compliance.review)","responses":{"200":{"description":"Queue"}}}},"/compliance/documents/{id}/confirm":{"post":{"summary":"Mark a document upload as completed","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Confirmed"}}}},"/compliance/documents/{id}/assign":{"post":{"summary":"Assign a document to a reviewer","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Assigned"}}}},"/compliance/documents/{id}/decide":{"post":{"summary":"Approve or reject; auto-transitions parent KYC/KYB record","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Decided"}}}},"/compliance/documents/{id}/download":{"get":{"summary":"Signed download URL (uploader or compliance reviewer only)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Signed URL"}}}},"/disputes":{"get":{"summary":"List disputes (admins see all; users see their own)","responses":{"200":{"description":"Disputes"}}},"post":{"summary":"Open a dispute against a payment","responses":{"201":{"description":"Dispute opened"}}}},"/disputes/{id}":{"get":{"summary":"Dispute detail with evidence + linked refund","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Dispute detail"}}}},"/disputes/{id}/evidence":{"post":{"summary":"Submit evidence (text and/or signed file upload)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"Evidence submitted"}}}},"/disputes/{id}/assign":{"post":{"summary":"Assign the dispute to a reviewer (compliance.review)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Assigned"}}}},"/disputes/{id}/rule":{"post":{"summary":"Rule the dispute (payments.refund); customer-favored or split rulings post a refund through the ledger","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Ruled"}}}},"/admin/platform-health":{"get":{"summary":"Aggregated platform health (ledger, webhooks, disputes, fraud, volumes, jobs)","responses":{"200":{"description":"Platform health snapshot"}}}},"/admin/fraud-scores":{"get":{"summary":"List fraud risk scores (reports.view)","responses":{"200":{"description":"Fraud scores"}}}},"/travel/search":{"post":{"summary":"Provider-neutral travel search (flights, hotels, transport, activities, insurance, eSIM, pilgrimage). Sandbox-labelled until a partner is LIVE.","responses":{"200":{"description":"Search results with normalized offers"}}}},"/travel/bookings":{"post":{"summary":"Book a travel offer via the approved provider adapter (Idempotency-Key required)","responses":{"201":{"description":"Booking created"}}},"get":{"summary":"List travel bookings (optional trip_id filter)","responses":{"200":{"description":"Bookings"}}}},"/travel/bookings/{bookingId}":{"get":{"summary":"Travel booking detail","parameters":[{"name":"bookingId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Booking detail"}}}},"/travel/bookings/{bookingId}/cancel":{"post":{"summary":"Cancel a travel booking via the provider","parameters":[{"name":"bookingId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Booking cancelled"}}}},"/trips/{tripId}/travel-budget":{"parameters":[{"$ref":"#/components/parameters/PlanningTenant"}],"get":{"summary":"TripBudget with planned/committed/paid/refunded/remaining/forecast/variance per category","security":[{"bearerAuth":[]}],"parameters":[{"name":"tripId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Owner trip budget summary"},"401":{"description":"Missing or expired bearer token"},"403":{"description":"API key without end-user identity"},"404":{"description":"Missing trip or different owner or active tenant"}}}},"/trips/{tripId}/financial-readiness":{"parameters":[{"$ref":"#/components/parameters/PlanningTenant"}],"get":{"summary":"Trip Financial Readiness Score (planning indicator only; not investment advice)","security":[{"bearerAuth":[]}],"parameters":[{"name":"tripId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Owner trip readiness score"},"401":{"description":"Missing or expired bearer token"},"403":{"description":"API key without end-user identity"},"404":{"description":"Missing trip or different owner or active tenant"}}}},"/trips/{tripId}/readiness":{"parameters":[{"$ref":"#/components/parameters/PlanningTenant"}],"get":{"summary":"Travel Readiness Center checklist (passport, visa, bookings, wallet, FX, eSIM, etc.)","security":[{"bearerAuth":[]}],"parameters":[{"name":"tripId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Owner trip readiness checklist"},"401":{"description":"Missing or expired bearer token"},"403":{"description":"API key without end-user identity"},"404":{"description":"Missing trip or different owner or active tenant"}}},"post":{"summary":"Set a readiness item status (NOT_STARTED|ACTION_REQUIRED|PENDING|READY|EXPIRED|NOT_APPLICABLE)","security":[{"bearerAuth":[]}],"parameters":[{"name":"tripId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Owner trip readiness item updated"},"400":{"description":"Invalid readiness item or status"},"401":{"description":"Missing or expired bearer token"},"403":{"description":"API key without end-user identity"},"404":{"description":"Missing trip or different owner or active tenant"}}}},"/disruptions":{"parameters":[{"$ref":"#/components/parameters/PlanningTenant"}],"post":{"summary":"Report a travel disruption (verified events require source + source_reference)","security":[{"bearerAuth":[]}],"description":"A referenced trip must belong to the caller in the explicit trip context (personal/null when X-Tenant-Id is omitted). Tripless reports use the same reporting-user and explicit-context boundaries.","responses":{"201":{"description":"Disruption recorded"},"400":{"description":"Invalid disruption type or missing verification evidence"},"401":{"description":"Missing or expired bearer token"},"403":{"description":"API key without end-user identity"},"404":{"description":"Missing trip or different owner or active tenant"}}}},"/trips/{tripId}/disruptions":{"parameters":[{"$ref":"#/components/parameters/PlanningTenant"}],"get":{"summary":"List disruption events for a trip","security":[{"bearerAuth":[]}],"parameters":[{"name":"tripId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Owner trip disruptions"},"401":{"description":"Missing or expired bearer token"},"403":{"description":"API key without end-user identity"},"404":{"description":"Missing trip or different owner or active tenant"}}}},"/disruptions/{disruptionId}/options":{"parameters":[{"$ref":"#/components/parameters/PlanningTenant"}],"post":{"summary":"Add a recovery option to a disruption","security":[{"bearerAuth":[]}],"parameters":[{"name":"disruptionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"Recovery option added"},"400":{"description":"Invalid label","currency or safe integer amount":null},"401":{"description":"Missing or expired bearer token"},"403":{"description":"API key without end-user identity"},"404":{"description":"Missing disruption or different owner or active tenant"}}}},"/disruptions/{disruptionId}/decide":{"parameters":[{"$ref":"#/components/parameters/PlanningTenant"}],"post":{"summary":"Record the customer decision on a recovery option","security":[{"bearerAuth":[]}],"parameters":[{"name":"disruptionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Decision recorded"},"401":{"description":"Missing or expired bearer token"},"403":{"description":"API key without end-user identity"},"404":{"description":"Missing disruption or different owner or active tenant"}}}},"/refunds":{"get":{"summary":"List refund requests — the caller's own, or all for platform finance staff","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Refund requests"}}}},"/refunds/request":{"post":{"summary":"Request a refund (unified refund hub)","responses":{"201":{"description":"Refund requested"}}}},"/refunds/{refundId}":{"get":{"summary":"Track a refund's authoritative state (\"Where is my refund?\")","parameters":[{"name":"refundId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Refund state"}}}},"/refunds/{refundId}/transition":{"post":{"summary":"Advance a refund through the state machine (requested→…→paid/failed/escalated). Platform finance staff with payments.refund only.","parameters":[{"name":"refundId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Refund transitioned"}}}},"/splits":{"post":{"summary":"Create a split expense (equal, custom, percentage or traveler split)","responses":{"201":{"description":"Split created"}}}},"/splits/{splitId}":{"get":{"summary":"Split balances per member","parameters":[{"name":"splitId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Split balances"}}}},"/splits/{splitId}/settle":{"post":{"summary":"Record a settlement against a member's share","parameters":[{"name":"splitId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Share settled"}}}},"/splits/{splitId}/mark-external":{"post":{"summary":"Mark a member's share as paid externally (off-platform)","parameters":[{"name":"splitId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Share marked external"}}}},"/ai/sessions":{"post":{"summary":"Create an AITripPay session","responses":{"201":{"description":"AI session created"}}},"get":{"summary":"List the caller's AITripPay sessions","responses":{"200":{"description":"AI sessions"}}}},"/ai/sessions/{sessionId}/messages":{"post":{"summary":"Post a message to an AITripPay session","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"Message recorded"}}}},"/ai/sessions/{sessionId}/actions":{"post":{"summary":"Propose a governed AI action (enforces the Agent OS permission matrix)","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"Action proposed"},"403":{"description":"Prohibited or unauthorized"}}}},"/ai/actions":{"get":{"summary":"List proposed AI actions (optional session_id/status filters)","responses":{"200":{"description":"Proposed actions"}}}},"/ai/actions/{actionId}/decide":{"post":{"summary":"Human approval gate for a proposed AI action (ai.approve)","parameters":[{"name":"actionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Decision recorded"},"403":{"description":"Missing ai.approve"}}}},"/trips/{tripId}/companions":{"parameters":[{"name":"tripId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/PlanningTenant"}],"get":{"summary":"List private trip companion roster","description":"Planning entries only; no account access or group invitation is granted.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Active companions","content":{"application/json":{"schema":{"type":"object","required":["rows","total"],"properties":{"rows":{"type":"array","items":{"$ref":"#/components/schemas/TripCompanion"}},"total":{"type":"integer"}}}}}},"400":{"description":"Invalid or unsupported fields or missing version"},"401":{"description":"Missing or expired user session"},"403":{"description":"API key without end-user identity"},"404":{"description":"Trip or record missing or not owned in this tenant"},"409":{"description":"Version conflict; reload and review before retrying"},"503":{"description":"Persistence unavailable"}}},"post":{"summary":"Add a companion to the private trip roster","description":"Up to 100 active companion entries per trip.","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/PlanningIdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanionInput"}}}},"responses":{"201":{"description":"Companion created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripCompanion"}}}},"400":{"description":"Invalid or unsupported fields or missing version"},"401":{"description":"Missing or expired user session"},"403":{"description":"API key without end-user identity"},"404":{"description":"Trip or record missing or not owned in this tenant"},"409":{"description":"Version conflict; reload and review before retrying"},"503":{"description":"Persistence unavailable"}}}},"/trips/{tripId}/companions/{recordId}":{"parameters":[{"name":"tripId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"recordId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/PlanningTenant"}],"patch":{"summary":"Update a trip companions record","description":"Owner-only partial update with optimistic version check. Optional nullable fields can be cleared.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanionPatch"}}}},"responses":{"200":{"description":"Updated record with incremented version","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripCompanion"}}}},"400":{"description":"Invalid or unsupported fields or missing version"},"401":{"description":"Missing or expired user session"},"403":{"description":"API key without end-user identity"},"404":{"description":"Trip or record missing or not owned in this tenant"},"409":{"description":"Version conflict; reload and review before retrying"},"503":{"description":"Persistence unavailable"}}},"delete":{"summary":"Remove a trip companions record","description":"Soft delete with current version. Removed records no longer appear in lists or budget totals; this does not cancel a booking or move money.","security":[{"bearerAuth":[]}],"parameters":[{"name":"version","in":"query","required":true,"schema":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Current record version; legacy records use zero"}}],"responses":{"204":{"description":"Record removed"},"400":{"description":"Invalid or unsupported fields or missing version"},"401":{"description":"Missing or expired user session"},"403":{"description":"API key without end-user identity"},"404":{"description":"Trip or record missing or not owned in this tenant"},"409":{"description":"Version conflict; reload and review before retrying"},"503":{"description":"Persistence unavailable"}}}},"/trips/{tripId}/itinerary/{recordId}":{"parameters":[{"name":"tripId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"recordId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/PlanningTenant"}],"patch":{"summary":"Update a trip itinerary record","description":"Owner-only partial update with optimistic version check. Optional nullable fields can be cleared.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ItineraryItemPatch"}}}},"responses":{"200":{"description":"Updated record with incremented version","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ItineraryItem"}}}},"400":{"description":"Invalid or unsupported fields or missing version"},"401":{"description":"Missing or expired user session"},"403":{"description":"API key without end-user identity"},"404":{"description":"Trip or record missing or not owned in this tenant"},"409":{"description":"Version conflict; reload and review before retrying"},"503":{"description":"Persistence unavailable"}}},"delete":{"summary":"Remove a trip itinerary record","description":"Soft delete with current version. Removed records no longer appear in lists or budget totals; this does not cancel a booking or move money.","security":[{"bearerAuth":[]}],"parameters":[{"name":"version","in":"query","required":true,"schema":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Current record version; legacy records use zero"}}],"responses":{"204":{"description":"Record removed"},"400":{"description":"Invalid or unsupported fields or missing version"},"401":{"description":"Missing or expired user session"},"403":{"description":"API key without end-user identity"},"404":{"description":"Trip or record missing or not owned in this tenant"},"409":{"description":"Version conflict; reload and review before retrying"},"503":{"description":"Persistence unavailable"}}}},"/trips/{tripId}/expenses/{recordId}":{"parameters":[{"name":"tripId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"recordId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/PlanningTenant"}],"patch":{"summary":"Update a trip expenses record","description":"Owner-only partial update with optimistic version check. Optional nullable fields can be cleared.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripExpensePatch"}}}},"responses":{"200":{"description":"Updated record with incremented version","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripExpense"}}}},"400":{"description":"Invalid or unsupported fields or missing version"},"401":{"description":"Missing or expired user session"},"403":{"description":"API key without end-user identity"},"404":{"description":"Trip or record missing or not owned in this tenant"},"409":{"description":"Version conflict; reload and review before retrying"},"503":{"description":"Persistence unavailable"}}},"delete":{"summary":"Remove a trip expenses record","description":"Soft delete with current version. Removed records no longer appear in lists or budget totals; this does not cancel a booking or move money.","security":[{"bearerAuth":[]}],"parameters":[{"name":"version","in":"query","required":true,"schema":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Current record version; legacy records use zero"}}],"responses":{"204":{"description":"Record removed"},"400":{"description":"Invalid or unsupported fields or missing version"},"401":{"description":"Missing or expired user session"},"403":{"description":"API key without end-user identity"},"404":{"description":"Trip or record missing or not owned in this tenant"},"409":{"description":"Version conflict; reload and review before retrying"},"503":{"description":"Persistence unavailable"}}}}}}