{"openapi":"3.1.0","info":{"title":"Vouch API","version":"2.0.0","description":"Reputation operations platform API — multi-tenant, RBAC-enforced.\n\n**Authentication:** Pass a JWT in `Authorization: Bearer <token>`. Obtain tokens via `/auth/login` (local auth) or Azure AD / SAML SSO. All `/v1/*` endpoints also require the `x-tenant-id` header.\n\n**Tenant scoping:** Every `/v1/*` route is automatically scoped to the tenant identified by `x-tenant-id`. Data from other tenants is never returned.\n\n**Platform Admin routes:** `/admin/*` endpoints require the caller to be a platform admin and accept no `x-tenant-id` — they operate across all tenants.\n\n---\n\n### MCP Server (agentic capabilities)\n\nAgentic skills (campaign strategy, template generation, compliance review, reputation insights, etc.) live behind a separate **Model Context Protocol** server, not REST. New skills and agents are exposed through MCP rather than REST going forward.\n\n- **Endpoint:** `https://api.tryvouch.io/mcp` (or the internal Container Apps FQDN for self-hosted)\n- **Discovery:** `GET /skills` returns the live skill catalog\n- **Auth:** `Authorization: Bearer <api_key>` — mint keys via `POST /v1/api-keys`\n- **Transports:** Streamable HTTP (remote) and stdio (local Claude Desktop / Claude Code)\n\nThe MCP server wraps existing agents (`services/agent-runtime`) and exposes data tools directly. Use it from any MCP-compatible client.\n\n**Full integration guide:** [/docs/mcp-integration](/docs/mcp-integration) — connecting Claude Desktop, Claude Code, Claude.ai, the Anthropic Claude API, and ChatGPT to your Vouch workspace. Includes skill reference, OAuth scopes, workflow recipes, rate limits, and troubleshooting.","contact":{"name":"Vouch Engineering"}},"servers":[{"url":"/","description":"Current server"},{"url":"https://api.tryvouch.io","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}},"parameters":{"tenantId":{"name":"x-tenant-id","in":"header","required":true,"schema":{"type":"string"},"description":"Tenant identifier (cuid)"},"cursor":{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque pagination cursor from previous response `pageInfo.endCursor`"},"limit":{"name":"limit","in":"query","schema":{"type":"integer","default":50,"minimum":1,"maximum":200},"description":"Number of items per page"}},"schemas":{"PageInfo":{"type":"object","properties":{"hasNextPage":{"type":"boolean"},"hasPreviousPage":{"type":"boolean"},"startCursor":{"type":["string","null"]},"endCursor":{"type":["string","null"]}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"},"details":{"type":"object"}}},"Contact":{"type":"object","properties":{"id":{"type":"string"},"tenantId":{"type":"string"},"email":{"type":["string","null"],"format":"email"},"phone":{"type":["string","null"]},"firstName":{"type":["string","null"]},"lastName":{"type":["string","null"]},"locale":{"type":["string","null"]},"timezone":{"type":["string","null"]},"isSuppressed":{"type":"boolean"},"suppressedAt":{"type":["string","null"],"format":"date-time"},"suppressionReason":{"type":["string","null"]},"externalIds":{"type":"object"},"sourceMetadata":{"type":"object"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"ConsentRecord":{"type":"object","properties":{"id":{"type":"string"},"contactId":{"type":"string"},"channel":{"type":"string","enum":["email","sms","whatsapp","in_app","kiosk_follow_up"]},"status":{"type":"string","enum":["granted","revoked","pending"]},"source":{"type":"string"},"ipAddress":{"type":["string","null"]},"userAgent":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"}}},"Campaign":{"type":"object","properties":{"id":{"type":"string"},"tenantId":{"type":"string"},"locationId":{"type":["string","null"]},"brandId":{"type":["string","null"]},"name":{"type":"string"},"type":{"type":"string","enum":["one_time","event_triggered","scheduled_batch","drip","continuous"]},"channels":{"type":"array","items":{"type":"string","enum":["email","sms","whatsapp"]}},"isActive":{"type":"boolean"},"startsAt":{"type":["string","null"],"format":"date-time"},"endsAt":{"type":["string","null"],"format":"date-time"},"metadata":{"type":"object"},"reviewDestination":{"type":"string","enum":["auto","survey","google","yelp","facebook","kiosk","feedback"]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Template":{"type":"object","properties":{"id":{"type":"string"},"tenantId":{"type":"string"},"name":{"type":"string"},"channel":{"type":"string","enum":["email","sms","whatsapp"]},"status":{"type":"string","enum":["draft","approved","archived"]},"subject":{"type":["string","null"]},"bodyHtml":{"type":["string","null"]},"bodyText":{"type":["string","null"]},"locale":{"type":"string"},"version":{"type":"integer"},"tokens":{"type":"array","items":{"type":"string"}},"createdByUserId":{"type":"string"},"approvedByUserId":{"type":["string","null"]},"approvedAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"CampaignTemplateBinding":{"type":"object","properties":{"id":{"type":"string"},"campaignId":{"type":"string"},"templateId":{"type":"string"},"channel":{"type":"string"},"locale":{"type":"string"},"variantKey":{"type":"string"},"priority":{"type":"integer"},"isControl":{"type":"boolean"},"activeFrom":{"type":["string","null"],"format":"date-time"},"activeTo":{"type":["string","null"],"format":"date-time"},"template":{"$ref":"#/components/schemas/Template"}}},"SequenceStep":{"type":"object","properties":{"stepIndex":{"type":"integer"},"channel":{"type":"string","enum":["email","sms","whatsapp"]},"delayDays":{"type":"integer","minimum":0},"templateId":{"type":["string","null"]},"template":{"$ref":"#/components/schemas/Template","nullable":true}}},"Sequence":{"type":"object","properties":{"id":{"type":"string"},"campaignId":{"type":"string"},"name":{"type":"string"},"steps":{"type":"array","items":{"$ref":"#/components/schemas/SequenceStep"}},"createdAt":{"type":"string","format":"date-time"}}},"Solicitation":{"type":"object","properties":{"id":{"type":"string"},"tenantId":{"type":"string"},"locationId":{"type":"string"},"campaignId":{"type":["string","null"]},"contactId":{"type":"string"},"channel":{"type":"string","enum":["email","sms","whatsapp"]},"status":{"type":"string","enum":["PENDING","SENT","DELIVERED","FAILED","BLOCKED","BOUNCED"]},"templateId":{"type":["string","null"]},"destinationUrl":{"type":["string","null"]},"shortLinkToken":{"type":"string"},"blockedReason":{"type":["string","null"]},"providerMessageId":{"type":["string","null"]},"sentAt":{"type":["string","null"],"format":"date-time"},"deliveredAt":{"type":["string","null"],"format":"date-time"},"clickedAt":{"type":["string","null"],"format":"date-time"},"convertedAt":{"type":["string","null"],"format":"date-time"},"idempotencyKey":{"type":"string"},"metadata":{"type":"object"},"createdAt":{"type":"string","format":"date-time"}}},"SolicitationWithContact":{"allOf":[{"$ref":"#/components/schemas/Solicitation"},{"type":"object","properties":{"contact":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":["string","null"]},"firstName":{"type":["string","null"]},"lastName":{"type":["string","null"]},"phone":{"type":["string","null"]}}}}}]},"Review":{"type":"object","properties":{"id":{"type":"string"},"tenantId":{"type":"string"},"locationId":{"type":"string"},"solicitationId":{"type":["string","null"]},"siteType":{"type":"string"},"externalReviewId":{"type":["string","null"]},"rating":{"type":["number","null"],"minimum":1,"maximum":5},"ratingMax":{"type":["number","null"]},"text":{"type":["string","null"]},"authorName":{"type":["string","null"]},"sentiment":{"type":["string","null"],"enum":["positive","neutral","negative",null]},"status":{"type":"string"},"language":{"type":["string","null"]},"isFirstParty":{"type":"boolean"},"isPublic":{"type":"boolean"},"publishedAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"}}},"ReviewResponse":{"type":"object","properties":{"id":{"type":"string"},"reviewId":{"type":"string"},"text":{"type":"string"},"status":{"type":"string","enum":["draft","submitted","approved","rejected","published"]},"assignedToUserId":{"type":["string","null"]},"aiGenerated":{"type":"boolean"},"rejectionReason":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Location":{"type":"object","properties":{"id":{"type":"string"},"tenantId":{"type":"string"},"brandId":{"type":"string"},"regionId":{"type":["string","null"]},"name":{"type":"string"},"externalId":{"type":["string","null"]},"addressLine1":{"type":["string","null"]},"addressLine2":{"type":["string","null"]},"city":{"type":["string","null"]},"state":{"type":["string","null"]},"postalCode":{"type":["string","null"]},"country":{"type":"string"},"timezone":{"type":"string"},"locale":{"type":"string"},"phone":{"type":["string","null"]},"email":{"type":["string","null"]},"websiteUrl":{"type":["string","null"]},"googleMyBusinessUrl":{"type":["string","null"]},"googlePlaceId":{"type":["string","null"]},"yelpUrl":{"type":["string","null"]},"yelpBusinessId":{"type":["string","null"]},"facebookUrl":{"type":["string","null"]},"facebookPageId":{"type":["string","null"]},"isActive":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"brand":{"type":["object","null"],"properties":{"id":{"type":"string"},"name":{"type":"string"}}}}},"User":{"type":"object","properties":{"id":{"type":"string"},"tenantId":{"type":"string"},"email":{"type":"string","format":"email"},"name":{"type":"string"},"avatarUrl":{"type":["string","null"]},"isActive":{"type":"boolean"},"authProvider":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"AuditEvent":{"type":"object","properties":{"id":{"type":"string"},"tenantId":{"type":"string"},"actorId":{"type":["string","null"]},"actorType":{"type":"string","enum":["user","system","platform_admin"]},"action":{"type":"string"},"resourceType":{"type":"string"},"resourceId":{"type":"string"},"previousState":{},"nextState":{},"ipAddress":{"type":["string","null"]},"userAgent":{"type":["string","null"]},"occurredAt":{"type":"string","format":"date-time"}}},"ComplianceConfig":{"type":"object","properties":{"healthcareSafeMode":{"type":"boolean"},"retentionDays":{"type":"integer","minimum":90,"maximum":2190},"requireDoubleOptIn":{"type":"boolean"},"allowDataExport":{"type":"boolean"},"allowDataDeletion":{"type":"boolean"},"quietHoursStart":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$","description":"HH:MM (24h) — sends after this time are blocked. Default 21:00."},"quietHoursEnd":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$","description":"HH:MM (24h) — sends before this time are blocked. Default 08:00."},"frequencyCapDaily":{"type":"integer","minimum":0,"maximum":100,"description":"Max non-blocked solicitations per recipient per 24h. Default 3."},"frequencyCapWeekly":{"type":"integer","minimum":0,"maximum":500,"description":"Max non-blocked solicitations per recipient per 7 days. Default 5."},"requireEmailConsent":{"type":"boolean","description":"Block email sends without granted email consent. Default true."},"requireSmsOptIn":{"type":"boolean","description":"Block SMS sends without granted SMS consent. Default true."}}},"SsoConfig":{"type":"object","properties":{"enabled":{"type":"boolean"},"entryPoint":{"type":"string","format":"uri"},"issuer":{"type":"string"},"callbackUrl":{"type":["string","null"]},"signatureAlgorithm":{"type":"string","enum":["sha1","sha256","sha512"]},"attributeMapping":{"type":"object"}}},"EmailConfig":{"type":"object","properties":{"provider":{"type":"string","enum":["acs","sendgrid","ses","smtp"]},"fromAddress":{"type":"string","format":"email"},"fromName":{"type":"string"},"replyToAddress":{"type":["string","null"],"format":"email"},"senderDomain":{"type":["string","null"]},"dkimStatus":{"type":"string","enum":["pending","verified","failed"]},"dmarcStatus":{"type":"string","enum":["pending","verified","failed"]},"fallbackToPlatform":{"type":"boolean"}}},"SmsConfig":{"type":"object","properties":{"provider":{"type":"string"},"fromNumber":{"type":["string","null"]},"fallbackToPlatform":{"type":"boolean"}}},"WhatsAppConfig":{"type":"object","properties":{"provider":{"type":"string"},"channelRegistrationId":{"type":["string","null"]},"fallbackToPlatform":{"type":"boolean"}}},"SlaRule":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"filter":{"type":"object"},"responseWindowHours":{"type":"integer"},"escalateAfterHours":{"type":"integer"},"escalateToRole":{"type":["string","null"]},"isActive":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"}}},"Experiment":{"type":"object","properties":{"id":{"type":"string"},"tenantId":{"type":"string"},"campaignId":{"type":["string","null"]},"locationId":{"type":["string","null"]},"name":{"type":"string"},"hypothesis":{"type":"string"},"status":{"type":"string","enum":["draft","running","paused","completed"]},"variants":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"trafficPercent":{"type":"number"},"templateId":{"type":["string","null"]},"config":{"type":"object"}}}},"startedAt":{"type":["string","null"],"format":"date-time"},"completedAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"}}},"Recommendation":{"type":"object","properties":{"id":{"type":"string"},"tenantId":{"type":"string"},"scopeType":{"type":"string"},"scopeId":{"type":"string"},"type":{"type":"string"},"priority":{"type":"string","enum":["low","medium","high","critical"]},"status":{"type":"string","enum":["pending","accepted","rejected","snoozed","applied"]},"title":{"type":"string"},"description":{"type":"string"},"actionPayload":{"type":"object"},"snoozedUntil":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"}}},"AutomationRule":{"type":"object","properties":{"id":{"type":"string"},"tenantId":{"type":"string"},"name":{"type":"string"},"triggerEventType":{"type":"string"},"locationId":{"type":["string","null"]},"brandId":{"type":["string","null"]},"isActive":{"type":"boolean"},"conditions":{"type":"array","items":{"type":"object"}},"actions":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"config":{"type":"object"}}}},"createdAt":{"type":"string","format":"date-time"}}},"WebhookSubscription":{"type":"object","properties":{"id":{"type":"string"},"tenantId":{"type":"string"},"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string"}},"isActive":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"}}},"Integration":{"type":"object","properties":{"id":{"type":"string"},"tenantId":{"type":"string"},"connectorType":{"type":"string"},"name":{"type":"string"},"isEnabled":{"type":"boolean"},"isSandbox":{"type":"boolean"},"lastSyncAt":{"type":["string","null"],"format":"date-time"},"config":{"type":"object"},"createdAt":{"type":"string","format":"date-time"}}},"Tenant":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"plan":{"type":"string","enum":["starter","growth","enterprise"]},"isActive":{"type":"boolean"},"pausedAt":{"type":["string","null"],"format":"date-time"},"pausedReason":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"brands":{"type":"array","items":{"$ref":"#/components/schemas/Brand"},"description":"Present on GET /admin/tenants/:id"},"emailConfig":{"type":["object","null"],"properties":{"fromAddress":{"type":"string"},"fromName":{"type":"string"},"senderDomain":{"type":["string","null"]},"dkimStatus":{"type":"string","enum":["pending","verified","failed"]},"dmarcStatus":{"type":"string","enum":["pending","verified","failed"]}},"description":"Present on GET /admin/tenants/:id"},"_count":{"type":"object","properties":{"users":{"type":"integer"},"contacts":{"type":"integer"},"solicitations":{"type":"integer"},"integrations":{"type":"integer"}}}}},"Brand":{"type":"object","properties":{"id":{"type":"string"},"tenantId":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"logoUrl":{"type":["string","null"]},"websiteUrl":{"type":["string","null"]},"corporateAddress":{"type":["string","null"]},"googleMyBusinessUrl":{"type":["string","null"]},"googlePlaceId":{"type":["string","null"]},"yelpUrl":{"type":["string","null"]},"yelpBusinessId":{"type":["string","null"]},"facebookUrl":{"type":["string","null"]},"facebookPageId":{"type":["string","null"]},"brandVoice":{"type":["string","null"],"description":"e.g. professional, friendly, casual"},"brandTone":{"type":["string","null"],"description":"e.g. warm, formal, empathetic"},"brandDescription":{"type":["string","null"]},"isActive":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"SendBulkResult":{"type":"object","properties":{"total":{"type":"integer"},"queued":{"type":"integer"},"skipped":{"type":"integer"},"failed":{"type":"integer"},"errors":{"type":"array","items":{"type":"object","properties":{"row":{"type":"integer"},"reason":{"type":"string"}}}}}},"SendResult":{"type":"object","properties":{"total":{"type":"integer"},"queued":{"type":"integer"},"blocked":{"type":"integer"},"failed":{"type":"integer"},"errors":{"type":"array","items":{"type":"object","properties":{"contactId":{"type":"string"},"reason":{"type":"string"}}}}}}}},"tags":[{"name":"System","description":"Health check, bootstrap, API docs"},{"name":"Auth","description":"Local auth (login, password reset) and SAML SSO"},{"name":"Contacts","description":"Contact management, suppression, consent"},{"name":"Campaigns","description":"Campaign lifecycle, bulk send, send history"},{"name":"Templates","description":"Email/SMS/WhatsApp templates with approval workflow"},{"name":"Sequences","description":"Multi-step campaign sequences"},{"name":"Solicitations","description":"Individual message dispatch and tracking"},{"name":"Reviews","description":"Review ingestion and sync"},{"name":"Responses","description":"Review response drafting, approval, publishing"},{"name":"Inbox","description":"Unified review + feedback inbox with SLA tracking"},{"name":"Feedback","description":"Public feedback submission via short-link tokens"},{"name":"Experiments","description":"A/B testing on templates and campaigns"},{"name":"Recommendations","description":"AI-generated action recommendations"},{"name":"Automation","description":"Event-triggered automation rules"},{"name":"Webhooks","description":"Outbound event webhooks"},{"name":"API Keys","description":"Mint, list, and revoke bearer API keys for programmatic access (used by the MCP server)"},{"name":"Integrations","description":"CRM / PMS integrations (Salesforce, HubSpot, Yardi…)"},{"name":"Analytics","description":"Funnel, reputation, operations, and leaderboard metrics"},{"name":"Locations","description":"Location management and custom variables"},{"name":"Compliance","description":"HIPAA / GDPR / SOC 2 — audit log, data export, deletion"},{"name":"Settings — Email","description":"Per-tenant email provider configuration and DKIM"},{"name":"Settings — SMS","description":"Per-tenant SMS provider configuration"},{"name":"Settings — WhatsApp","description":"Per-tenant WhatsApp provider configuration"},{"name":"Settings — SSO","description":"SAML 2.0 SSO configuration"},{"name":"Settings — SLA","description":"Inbox SLA rules"},{"name":"Settings — Brand","description":"Brand profile and review-sync credentials"},{"name":"Users","description":"Tenant user management, invites, RBAC"},{"name":"Agent","description":"AI assistant — chat and streaming"},{"name":"Platform Admin","description":"Cross-tenant platform administration (platform_admin only)"}],"paths":{"/health":{"get":{"tags":["System"],"summary":"Health check","security":[],"responses":{"200":{"description":"Service healthy","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"ok"}}}}}}}}},"/docs":{"get":{"tags":["System"],"summary":"Swagger UI","security":[],"responses":{"200":{"description":"HTML Swagger UI"}}}},"/docs/openapi.json":{"get":{"tags":["System"],"summary":"Raw OpenAPI 3.1 spec","security":[],"responses":{"200":{"description":"OpenAPI JSON"}}}},"/bootstrap":{"post":{"tags":["System"],"summary":"Bootstrap first tenant and platform admin (one-time setup)","security":[],"responses":{"201":{"description":"Bootstrapped successfully"},"409":{"description":"Already bootstrapped"}}}},"/auth/login":{"post":{"tags":["Auth"],"summary":"Local email/password login","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":8}}}}}},"responses":{"200":{"description":"JWT token and user","content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"},"user":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"name":{"type":"string"},"tenantId":{"type":"string"}}}}}}}},"401":{"description":"Invalid credentials"}}}},"/auth/setup-password":{"post":{"tags":["Auth"],"summary":"Accept invite — set name and password, receive JWT","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["inviteToken","name","password"],"properties":{"inviteToken":{"type":"string"},"name":{"type":"string"},"password":{"type":"string","minLength":8}}}}}},"responses":{"200":{"description":"JWT token","content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"},"user":{"type":"object"}}}}}},"400":{"description":"Invalid or expired token"}}}},"/auth/forgot-password":{"post":{"tags":["Auth"],"summary":"Request a password-reset email","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}}}}},"responses":{"200":{"description":"Always returns ok (email sent if account exists)","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}}}}},"/auth/reset-password":{"post":{"tags":["Auth"],"summary":"Reset password with token from email","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token","password"],"properties":{"token":{"type":"string"},"password":{"type":"string","minLength":8}}}}}},"responses":{"200":{"description":"Password updated","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"400":{"description":"Invalid or expired token"}}}},"/auth/saml/{tenantSlug}/metadata":{"get":{"tags":["Auth"],"summary":"SAML SP metadata XML","security":[],"parameters":[{"name":"tenantSlug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"XML Service Provider metadata","content":{"application/xml":{}}}}}},"/auth/saml/{tenantSlug}/init":{"get":{"tags":["Auth"],"summary":"Initiate SAML SSO — redirects to IdP","security":[],"parameters":[{"name":"tenantSlug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"302":{"description":"Redirect to IdP SSO URL"}}}},"/auth/saml/{tenantSlug}/callback":{"post":{"tags":["Auth"],"summary":"SAML ACS callback — validates assertion, issues JWT","security":[],"parameters":[{"name":"tenantSlug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"302":{"description":"Redirect to frontend with token in query param"},"401":{"description":"SAML assertion invalid"}}}},"/me":{"get":{"tags":["Auth"],"summary":"Get current user profile and role assignments","parameters":[{"$ref":"#/components/parameters/tenantId"}],"responses":{"200":{"description":"Current user with role assignments","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/contacts":{"get":{"tags":["Contacts"],"summary":"List contacts (cursor-paginated)","parameters":[{"$ref":"#/components/parameters/tenantId"},{"$ref":"#/components/parameters/cursor"},{"$ref":"#/components/parameters/limit"},{"name":"search","in":"query","schema":{"type":"string"},"description":"Filter by email, phone, or name"}],"responses":{"200":{"description":"Paginated contacts","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}},"pageInfo":{"$ref":"#/components/schemas/PageInfo"},"total":{"type":"integer"}}}}}}}},"post":{"tags":["Contacts"],"summary":"Create or upsert a contact (matches on email or phone)","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"phone":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"locale":{"type":"string"},"timezone":{"type":"string"},"externalIds":{"type":"object"},"sourceMetadata":{"type":"object"}}}}}},"responses":{"201":{"description":"Contact created or updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/contacts/import/csv":{"post":{"tags":["Contacts"],"summary":"Bulk import contacts from CSV (max 5,000 rows, 10 MB)","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"CSV with columns: email, phone, firstName (or first_name), lastName (or last_name), locale, timezone. Each row needs at least email or phone."}}}}}},"responses":{"200":{"description":"Import summary","content":{"application/json":{"schema":{"type":"object","properties":{"imported":{"type":"integer"},"skipped":{"type":"integer"},"errors":{"type":"array","items":{"type":"object","properties":{"row":{"type":"integer"},"reason":{"type":"string"}}}}}}}}}}}},"/v1/contacts/import/crm/{integrationId}":{"post":{"tags":["Contacts"],"summary":"Trigger a contact sync from a CRM integration","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"integrationId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"202":{"description":"Sync queued"}}}},"/v1/contacts/{id}":{"get":{"tags":["Contacts"],"summary":"Get contact by ID","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/contacts/{id}/suppress":{"post":{"tags":["Contacts"],"summary":"Suppress a contact (opt-out all channels)","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string"}}}}}},"responses":{"200":{"description":"Updated contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}}}},"delete":{"tags":["Contacts"],"summary":"Unsuppress a contact","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Updated contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}}}}},"/v1/contacts/{id}/consent":{"get":{"tags":["Contacts"],"summary":"Get all consent records for a contact","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Consent records","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ConsentRecord"}}}}}}},"post":{"tags":["Contacts"],"summary":"Grant or update consent for a channel","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["channel","source"],"properties":{"channel":{"type":"string","enum":["email","sms","whatsapp","in_app","kiosk_follow_up"]},"source":{"type":"string","description":"How consent was obtained (e.g. 'kiosk', 'web-form')"},"ipAddress":{"type":"string"},"userAgent":{"type":"string"}}}}}},"responses":{"201":{"description":"Consent record","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentRecord"}}}}}}},"/v1/contacts/{id}/consent/{channel}":{"delete":{"tags":["Contacts"],"summary":"Revoke consent for a specific channel","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"channel","in":"path","required":true,"schema":{"type":"string","enum":["email","sms","whatsapp","in_app","kiosk_follow_up"]}}],"responses":{"200":{"description":"Revoked consent record","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentRecord"}}}}}}},"/v1/campaigns":{"get":{"tags":["Campaigns"],"summary":"List campaigns","parameters":[{"$ref":"#/components/parameters/tenantId"},{"$ref":"#/components/parameters/cursor"},{"$ref":"#/components/parameters/limit"},{"name":"locationId","in":"query","schema":{"type":"string"}},{"name":"brandId","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated campaigns","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Campaign"}},"pageInfo":{"$ref":"#/components/schemas/PageInfo"},"total":{"type":"integer"}}}}}}}},"post":{"tags":["Campaigns"],"summary":"Create a campaign","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","type","channels"],"properties":{"name":{"type":"string"},"type":{"type":"string","enum":["one_time","event_triggered","scheduled_batch","drip","continuous"]},"channels":{"type":"array","items":{"type":"string","enum":["email","sms","whatsapp"]},"minItems":1},"locationId":{"type":"string"},"brandId":{"type":"string"},"startsAt":{"type":"string","format":"date-time"},"endsAt":{"type":"string","format":"date-time"},"metadata":{"type":"object"}}}}}},"responses":{"201":{"description":"Campaign created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Campaign"}}}}}}},"/v1/campaigns/{id}":{"get":{"tags":["Campaigns"],"summary":"Get campaign by ID","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Campaign","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Campaign"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"patch":{"tags":["Campaigns"],"summary":"Update campaign fields","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string"},"channels":{"type":"array","items":{"type":"string"}},"startsAt":{"type":"string"},"endsAt":{"type":"string"}}}}}},"responses":{"200":{"description":"Updated campaign","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Campaign"}}}}}}},"/v1/campaigns/{id}/activate":{"post":{"tags":["Campaigns"],"summary":"Activate a campaign","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Campaign","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Campaign"}}}}}}},"/v1/campaigns/{id}/deactivate":{"post":{"tags":["Campaigns"],"summary":"Deactivate a campaign","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Campaign","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Campaign"}}}}}}},"/v1/campaigns/{id}/send":{"post":{"tags":["Campaigns"],"summary":"Send campaign to existing contacts in the database","description":"Dispatches messages to all non-suppressed contacts matching the channel requirement (email contacts for email channel, phone contacts for SMS/WhatsApp). Respects consent, frequency caps, and quiet hours. Pass `contactIds` to target a specific subset.","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["locationId","channel"],"properties":{"locationId":{"type":"string","description":"Location used for token rendering ({{location.name}} etc.)"},"channel":{"type":"string","enum":["email","sms","whatsapp"]},"templateId":{"type":"string","description":"Template to render. Required for email channel to include HTML/subject."},"contactIds":{"type":"array","items":{"type":"string"},"description":"Specific contact IDs to send to. Omit to send to all eligible contacts."},"limit":{"type":"integer","minimum":1,"maximum":5000,"default":2000,"description":"Maximum contacts to process"}}}}}},"responses":{"200":{"description":"Send result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendResult"}}}}}}},"/v1/campaigns/{id}/send-bulk":{"post":{"tags":["Campaigns"],"summary":"Upload a CSV and send — upserts contacts then dispatches messages","description":"Accepts a CSV of recipients, upserts each as a contact, then creates a solicitation per row. The `locationId` and `channel` can be set per-row in the CSV or as form fields for all rows.","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["contacts"],"properties":{"contacts":{"type":"string","format":"binary","description":"CSV columns: email, phone, firstName, lastName, locationId (per-row override), channel (per-row override)"},"locationId":{"type":"string","description":"Default locationId for all rows"},"channel":{"type":"string","enum":["email","sms","whatsapp"],"description":"Default channel for all rows"},"templateId":{"type":"string","description":"Template to use for all rows"}}}}}},"responses":{"200":{"description":"Bulk send result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendBulkResult"}}}}}}},"/v1/campaigns/{id}/solicitations":{"get":{"tags":["Campaigns"],"summary":"Get send history for a campaign","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","default":200,"maximum":500}}],"responses":{"200":{"description":"Solicitations with embedded contact","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SolicitationWithContact"}},"total":{"type":"integer"}}}}}}}}},"/v1/campaigns/{id}/templates":{"get":{"tags":["Campaigns"],"summary":"List templates bound to a campaign","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Template bindings","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CampaignTemplateBinding"}}}}}}},"post":{"tags":["Campaigns"],"summary":"Bind a template to a campaign","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["templateId","channel"],"properties":{"templateId":{"type":"string"},"channel":{"type":"string","enum":["email","sms","whatsapp"]},"locale":{"type":"string","default":"en-US"},"variantKey":{"type":"string","default":"control"},"priority":{"type":"integer","default":0},"isControl":{"type":"boolean","default":true},"activeFrom":{"type":"string","format":"date-time"},"activeTo":{"type":"string","format":"date-time"}}}}}},"responses":{"201":{"description":"Binding created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignTemplateBinding"}}}}}}},"/v1/campaigns/{id}/templates/{bindingId}":{"delete":{"tags":["Campaigns"],"summary":"Remove a template binding from a campaign","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"bindingId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No content"}}}},"/v1/campaigns/{id}/sequence":{"get":{"tags":["Sequences"],"summary":"Get the sequence for a campaign","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Sequence or null","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Sequence"},{"type":"null"}]}}}}}},"put":{"tags":["Sequences"],"summary":"Create or replace the campaign sequence","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["steps"],"properties":{"name":{"type":"string"},"steps":{"type":"array","items":{"type":"object","required":["stepIndex","channel","delayDays"],"properties":{"stepIndex":{"type":"integer","minimum":0},"channel":{"type":"string","enum":["email","sms","whatsapp"]},"delayDays":{"type":"integer","minimum":0},"templateId":{"type":"string"}}}}}}}}},"responses":{"200":{"description":"Updated sequence","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Sequence"}}}}}},"delete":{"tags":["Sequences"],"summary":"Delete the campaign sequence","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No content"}}}},"/v1/templates":{"get":{"tags":["Templates"],"summary":"List templates","parameters":[{"$ref":"#/components/parameters/tenantId"},{"$ref":"#/components/parameters/cursor"},{"$ref":"#/components/parameters/limit"},{"name":"channel","in":"query","schema":{"type":"string","enum":["email","sms","whatsapp"]}},{"name":"status","in":"query","schema":{"type":"string","enum":["draft","approved","archived"]}}],"responses":{"200":{"description":"Paginated templates","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Template"}},"pageInfo":{"$ref":"#/components/schemas/PageInfo"},"total":{"type":"integer"}}}}}}}},"post":{"tags":["Templates"],"summary":"Create a template","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","channel"],"properties":{"name":{"type":"string"},"channel":{"type":"string","enum":["email","sms","whatsapp"]},"subject":{"type":"string","description":"Email subject line. Supports {{tokens}}."},"bodyHtml":{"type":"string","description":"HTML body. Supports {{tokens}}."},"bodyText":{"type":"string","description":"Plain-text body or SMS/WhatsApp message. Supports {{tokens}}."},"locale":{"type":"string","default":"en-US"}}}}}},"responses":{"201":{"description":"Template created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Template"}}}}}}},"/v1/templates/{id}":{"get":{"tags":["Templates"],"summary":"Get template by ID","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Template","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Template"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"patch":{"tags":["Templates"],"summary":"Update template content (creates a new version if approved)","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"subject":{"type":"string"},"bodyHtml":{"type":"string"},"bodyText":{"type":"string"}}}}}},"responses":{"200":{"description":"Updated template","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Template"}}}}}}},"/v1/templates/{id}/approve":{"post":{"tags":["Templates"],"summary":"Approve a draft template for use in sends","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Approved template","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Template"}}}}}}},"/v1/templates/{id}/archive":{"post":{"tags":["Templates"],"summary":"Archive a template","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Archived template","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Template"}}}}}}},"/v1/templates/{id}/preview":{"get":{"tags":["Templates"],"summary":"Render template with sample token values","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Rendered preview","content":{"application/json":{"schema":{"type":"object","properties":{"subject":{"type":"string"},"html":{"type":"string"},"text":{"type":"string"}}}}}}}}},"/v1/templates/{id}/version":{"post":{"tags":["Templates"],"summary":"Fork a new version of an approved template","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"New draft version","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Template"}}}}}}},"/v1/solicitations":{"post":{"tags":["Solicitations"],"summary":"Dispatch a single solicitation to one contact","description":"Creates a solicitation record and immediately dispatches the message (fire-and-forget). Requires `Idempotency-Key` header to prevent duplicate sends.","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string"},"description":"Unique key to prevent duplicate sends (UUID recommended)"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["locationId","contactId","channel"],"properties":{"locationId":{"type":"string"},"campaignId":{"type":"string"},"contactId":{"type":"string"},"channel":{"type":"string","enum":["email","sms","whatsapp"]},"templateId":{"type":"string"},"destinationUrl":{"type":"string","format":"uri"},"metadata":{"type":"object"}}}}}},"responses":{"201":{"description":"Solicitation created (may be BLOCKED by policy)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Solicitation"}}}},"400":{"description":"Missing Idempotency-Key or invalid body"}}}},"/v1/solicitations/{id}":{"get":{"tags":["Solicitations"],"summary":"Get solicitation status by ID","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Solicitation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Solicitation"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/reviews":{"get":{"tags":["Reviews"],"summary":"List reviews","parameters":[{"$ref":"#/components/parameters/tenantId"},{"$ref":"#/components/parameters/cursor"},{"$ref":"#/components/parameters/limit"},{"name":"locationId","in":"query","schema":{"type":"string"}},{"name":"siteType","in":"query","schema":{"type":"string"}},{"name":"sentiment","in":"query","schema":{"type":"string","enum":["positive","neutral","negative"]}},{"name":"ratingMin","in":"query","schema":{"type":"number"}},{"name":"ratingMax","in":"query","schema":{"type":"number"}},{"name":"dateFrom","in":"query","schema":{"type":"string","format":"date"}},{"name":"dateTo","in":"query","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Paginated reviews","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Review"}},"pageInfo":{"$ref":"#/components/schemas/PageInfo"},"total":{"type":"integer"}}}}}}}}},"/v1/reviews/ingest":{"post":{"tags":["Reviews"],"summary":"Ingest a review from an external source","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["locationId","siteType"],"properties":{"locationId":{"type":"string"},"solicitationId":{"type":"string"},"siteType":{"type":"string","description":"e.g. 'google', 'yelp', 'facebook', 'first_party'"},"externalReviewId":{"type":"string"},"rating":{"type":"number","minimum":1,"maximum":5},"ratingMax":{"type":"number"},"text":{"type":"string"},"authorName":{"type":"string"},"authorExternalId":{"type":"string"},"language":{"type":"string"},"isFirstParty":{"type":"boolean"},"isPublic":{"type":"boolean"},"topics":{"type":"array","items":{"type":"string"}},"publishedAt":{"type":"string","format":"date-time"}}}}}},"responses":{"201":{"description":"Review ingested","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Review"}}}}}}},"/v1/reviews/sync":{"post":{"tags":["Reviews"],"summary":"Trigger a review sync from connected platforms","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"locationId":{"type":"string"}}}}}},"responses":{"200":{"description":"Sync result(s)"}}}},"/v1/reviews/{id}":{"get":{"tags":["Reviews"],"summary":"Get review by ID","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Review","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Review"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/reviews/{reviewId}/responses":{"get":{"tags":["Responses"],"summary":"List responses for a review","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"reviewId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Responses","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ReviewResponse"}}}}}}},"post":{"tags":["Responses"],"summary":"Draft a response to a review (optionally AI-generated)","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"reviewId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string"},"assignedToUserId":{"type":"string"},"useAi":{"type":"boolean","description":"Regenerate text using AI before saving"}}}}}},"responses":{"201":{"description":"Response created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewResponse"}}}}}}},"/v1/responses/approval-queue":{"get":{"tags":["Responses"],"summary":"Get all responses awaiting approval","parameters":[{"$ref":"#/components/parameters/tenantId"}],"responses":{"200":{"description":"Responses","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ReviewResponse"}}}}}}}},"/v1/responses/overdue":{"get":{"tags":["Responses"],"summary":"Get reviews with overdue SLA response windows","parameters":[{"$ref":"#/components/parameters/tenantId"}],"responses":{"200":{"description":"Overdue items"}}}},"/v1/responses/{id}":{"patch":{"tags":["Responses"],"summary":"Update response text","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string"}}}}}},"responses":{"200":{"description":"Updated response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewResponse"}}}}}}},"/v1/responses/{id}/submit":{"post":{"tags":["Responses"],"summary":"Submit response for approval","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewResponse"}}}}}}},"/v1/responses/{id}/approve":{"post":{"tags":["Responses"],"summary":"Approve a submitted response","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewResponse"}}}}}}},"/v1/responses/{id}/reject":{"post":{"tags":["Responses"],"summary":"Reject a submitted response","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string"}}}}}},"responses":{"200":{"description":"Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewResponse"}}}}}}},"/v1/responses/{id}/publish":{"post":{"tags":["Responses"],"summary":"Publish an approved response to the review platform","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewResponse"}}}}}}},"/v1/inbox":{"get":{"tags":["Inbox"],"summary":"List inbox items (reviews + feedback, unified)","parameters":[{"$ref":"#/components/parameters/tenantId"},{"$ref":"#/components/parameters/cursor"},{"$ref":"#/components/parameters/limit"},{"name":"locationId","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string"}},{"name":"type","in":"query","schema":{"type":"string","enum":["review","feedback"]}}],"responses":{"200":{"description":"Paginated inbox items"}}}},"/v1/inbox/{id}":{"patch":{"tags":["Inbox"],"summary":"Update inbox item status","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"type":{"type":"string","enum":["review","feedback"]}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}}}}},"/v1/inbox/{id}/assign":{"post":{"tags":["Inbox"],"summary":"Assign inbox item to a user","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userId"],"properties":{"userId":{"type":"string"},"type":{"type":"string"}}}}}},"responses":{"200":{"description":"Assigned","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}}}}},"/v1/inbox/{id}/notes":{"post":{"tags":["Inbox"],"summary":"Add an internal note to an inbox item","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["note"],"properties":{"note":{"type":"string"}}}}}},"responses":{"200":{"description":"Note added","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}}}}},"/v1/inbox/{id}/tags":{"post":{"tags":["Inbox"],"summary":"Set tags on an inbox item","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["tags"],"properties":{"tags":{"type":"array","items":{"type":"string"}},"type":{"type":"string"}}}}}},"responses":{"200":{"description":"Tags updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}}}}},"/v1/inbox/{id}/escalate":{"post":{"tags":["Inbox"],"summary":"Escalate an inbox item","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string"}}}}}},"responses":{"200":{"description":"Escalated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}}}}},"/v1/inbox/bulk-status":{"post":{"tags":["Inbox"],"summary":"Bulk update status for multiple inbox items","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["itemIds","status"],"properties":{"itemIds":{"type":"array","items":{"type":"string"}},"status":{"type":"string"}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}}}}},"/v1/f/{token}":{"get":{"tags":["Feedback"],"summary":"Get feedback landing page data (public)","security":[],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Feedback landing data"}}},"post":{"tags":["Feedback"],"summary":"Submit feedback via short-link token (public)","security":[],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string"},"rating":{"type":"integer","minimum":1,"maximum":5},"npsScore":{"type":"integer","minimum":0,"maximum":10},"surveyType":{"type":"string","enum":["nps","csat","ces"]},"score":{"type":"integer"},"followUpText":{"type":"string"},"text":{"type":"string"},"callbackRequested":{"type":"boolean"},"surveyResponses":{"type":"object"}}}}}},"responses":{"201":{"description":"Feedback submitted"}}}},"/v1/r/{token}":{"get":{"tags":["Feedback"],"summary":"Resolve short-link token — returns destination data or redirects","security":[],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}},{"name":"redirect","in":"query","schema":{"type":"string","enum":["1"]},"description":"Set to '1' to issue a 302 redirect instead of JSON"}],"responses":{"200":{"description":"Short-link destination data"},"302":{"description":"Redirect to destination URL"}}}},"/v1/r/{token}/click":{"patch":{"tags":["Feedback"],"summary":"Record a click on a short-link (public)","security":[],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Click recorded","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}}}}},"/unsubscribe/{token}":{"get":{"tags":["Feedback"],"summary":"Get unsubscribe page data (public)","security":[],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Unsubscribe page data"}}},"post":{"tags":["Feedback"],"summary":"Confirm unsubscribe (public)","security":[],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Unsubscribed"}}},"delete":{"tags":["Feedback"],"summary":"Resubscribe (undo unsubscribe) (public)","security":[],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Resubscribed"}}}},"/v1/experiments":{"get":{"tags":["Experiments"],"summary":"List experiments","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"campaignId","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Experiments","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Experiment"}}}}}}},"post":{"tags":["Experiments"],"summary":"Create an A/B experiment","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","hypothesis","variants"],"properties":{"campaignId":{"type":"string"},"locationId":{"type":"string"},"name":{"type":"string"},"hypothesis":{"type":"string"},"variants":{"type":"array","minItems":2,"items":{"type":"object","required":["name","trafficPercent"],"properties":{"name":{"type":"string"},"trafficPercent":{"type":"number","minimum":0,"maximum":100},"templateId":{"type":"string"},"config":{"type":"object"}}}}}}}}},"responses":{"201":{"description":"Experiment created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Experiment"}}}}}}},"/v1/experiments/{id}":{"get":{"tags":["Experiments"],"summary":"Get experiment by ID","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Experiment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Experiment"}}}}}},"patch":{"tags":["Experiments"],"summary":"Update experiment fields (tenant_admin)","description":"Update name, hypothesis, or pick a winner. Mutating routes require tenant_admin or platform_admin.","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"hypothesis":{"type":"string","minLength":1},"winnerVariantId":{"type":["string","null"]}}}}}},"responses":{"200":{"description":"Updated experiment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Experiment"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["Experiments"],"summary":"Delete a draft experiment (tenant_admin)","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No content"},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/experiments/{id}/start":{"post":{"tags":["Experiments"],"summary":"Start an experiment","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Experiment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Experiment"}}}}}}},"/v1/experiments/{id}/pause":{"post":{"tags":["Experiments"],"summary":"Pause a running experiment","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Experiment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Experiment"}}}}}}},"/v1/experiments/{id}/complete":{"post":{"tags":["Experiments"],"summary":"Complete an experiment and declare a winner","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Experiment result"}}}},"/v1/experiments/{id}/results":{"get":{"tags":["Experiments"],"summary":"Get experiment statistical results","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Results"}}}},"/v1/experiments/{id}/variants":{"post":{"tags":["Experiments"],"summary":"Add a variant (tenant_admin, draft only)","description":"Variants can only be added while the experiment is in `draft`. Returns 409 otherwise.","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","trafficPercent"],"properties":{"name":{"type":"string","minLength":1},"trafficPercent":{"type":"number","minimum":0,"maximum":100},"templateId":{"type":["string","null"]},"config":{"type":"object"}}}}}},"responses":{"201":{"description":"Variant created"},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Experiment not in draft"}}}},"/v1/experiments/{id}/variants/{variantId}":{"patch":{"tags":["Experiments"],"summary":"Update a variant (tenant_admin, draft only)","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"variantId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"trafficPercent":{"type":"number","minimum":0,"maximum":100},"templateId":{"type":["string","null"]},"config":{"type":"object"}}}}}},"responses":{"200":{"description":"Updated variant"},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Experiment not in draft"}}},"delete":{"tags":["Experiments"],"summary":"Remove a variant (tenant_admin, draft only, must keep ≥2)","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"variantId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No content"},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Experiment not in draft, or fewer than 3 variants"}}}},"/v1/recommendations":{"get":{"tags":["Recommendations"],"summary":"List recommendations","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"scopeType","in":"query","schema":{"type":"string"}},{"name":"scopeId","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Recommendations","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Recommendation"}}}}}}}},"/v1/recommendations/inbox":{"get":{"tags":["Recommendations"],"summary":"Get recommendation inbox (grouped, prioritized)","parameters":[{"$ref":"#/components/parameters/tenantId"}],"responses":{"200":{"description":"Recommendation inbox"}}}},"/v1/recommendations/{id}/accept":{"post":{"tags":["Recommendations"],"summary":"Accept a recommendation","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Recommendation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recommendation"}}}}}}},"/v1/recommendations/{id}/reject":{"post":{"tags":["Recommendations"],"summary":"Reject a recommendation","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Recommendation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recommendation"}}}}}}},"/v1/recommendations/{id}/snooze":{"post":{"tags":["Recommendations"],"summary":"Snooze a recommendation until a future date","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["snoozedUntil"],"properties":{"snoozedUntil":{"type":"string","format":"date-time"}}}}}},"responses":{"200":{"description":"Recommendation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recommendation"}}}}}}},"/v1/recommendations/{id}/apply":{"post":{"tags":["Recommendations"],"summary":"Apply a recommendation (execute its action)","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Recommendation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recommendation"}}}}}}},"/v1/automation-rules":{"get":{"tags":["Automation"],"summary":"List automation rules","parameters":[{"$ref":"#/components/parameters/tenantId"}],"responses":{"200":{"description":"Rules","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AutomationRule"}}}}}}},"post":{"tags":["Automation"],"summary":"Create an automation rule","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","triggerEventType","actions"],"properties":{"name":{"type":"string"},"triggerEventType":{"type":"string","description":"e.g. 'review.created', 'solicitation.sent'"},"locationId":{"type":"string"},"brandId":{"type":"string"},"conditions":{"type":"array","items":{"type":"object"}},"actions":{"type":"array","items":{"type":"object","required":["type"],"properties":{"type":{"type":"string"},"config":{"type":"object"}}}}}}}}},"responses":{"201":{"description":"Rule created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutomationRule"}}}}}}},"/v1/automation-rules/dry-run":{"post":{"tags":["Automation"],"summary":"Dry-run an event against all active rules without side effects","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["eventType"],"properties":{"eventType":{"type":"string"},"payload":{"type":"object"}}}}}},"responses":{"200":{"description":"Dry-run results"}}}},"/v1/automation-rules/{id}":{"get":{"tags":["Automation"],"summary":"Get rule by ID","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Rule","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutomationRule"}}}}}},"patch":{"tags":["Automation"],"summary":"Update a rule","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Updated rule","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutomationRule"}}}}}},"delete":{"tags":["Automation"],"summary":"Delete a rule","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No content"}}}},"/v1/webhooks":{"get":{"tags":["Webhooks"],"summary":"List webhook subscriptions","parameters":[{"$ref":"#/components/parameters/tenantId"}],"responses":{"200":{"description":"Webhook subscriptions","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WebhookSubscription"}}}}}}},"post":{"tags":["Webhooks"],"summary":"Create a webhook subscription","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url","events","secret"],"properties":{"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string"},"description":"e.g. ['review.created', 'solicitation.sent']"},"secret":{"type":"string","minLength":16,"description":"HMAC signing secret (min 16 chars). Stored encrypted; returned masked."}}}}}},"responses":{"201":{"description":"Webhook created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscription"}}}}}}},"/v1/webhooks/test":{"post":{"tags":["Webhooks"],"summary":"Send a test event to a webhook endpoint","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["subscriptionId"],"properties":{"subscriptionId":{"type":"string"},"eventType":{"type":"string"}}}}}},"responses":{"200":{"description":"Delivery result"}}}},"/v1/webhooks/{id}":{"get":{"tags":["Webhooks"],"summary":"Get webhook by ID","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Webhook","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscription"}}}}}},"patch":{"tags":["Webhooks"],"summary":"Update a webhook","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Updated webhook"}}},"delete":{"tags":["Webhooks"],"summary":"Delete a webhook","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No content"}}}},"/v1/api-keys":{"get":{"tags":["API Keys"],"summary":"List API keys for the tenant","description":"Returns metadata only — raw key values are never returned after creation.","parameters":[{"$ref":"#/components/parameters/tenantId"}],"responses":{"200":{"description":"API keys","content":{"application/json":{"schema":{"type":"object","properties":{"apiKeys":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"keyPrefix":{"type":"string","example":"bk_a1b2c3d4"},"isActive":{"type":"boolean"},"expiresAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"}}}}}}}}}}},"post":{"tags":["API Keys"],"summary":"Mint a new API key","description":"**The raw key is returned ONLY in this response.** Store it immediately — it cannot be retrieved later. Use the key as `Authorization: Bearer <key>` against the MCP server (`POST /mcp` on vouch-mcp-server).","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":100,"description":"Human-readable label, e.g. 'claude-desktop-laptop'"},"expiresAt":{"type":"string","format":"date-time","description":"Optional expiration. Omit for no expiration."}}}}}},"responses":{"201":{"description":"API key minted (raw key is in `key` — store it now)","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string","description":"The raw bearer token, prefixed with `bk_`. Returned only once.","example":"bk_a1b2c3d4e5f6..."},"id":{"type":"string"},"name":{"type":"string"},"keyPrefix":{"type":"string"},"isActive":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"expiresAt":{"type":["string","null"],"format":"date-time"}}}}}}}}},"/v1/api-keys/{id}":{"delete":{"tags":["API Keys"],"summary":"Revoke an API key","description":"Sets `isActive=false`. The key will be rejected on subsequent requests immediately.","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No content"}}}},"/v1/integrations":{"get":{"tags":["Integrations"],"summary":"List CRM/PMS integrations","parameters":[{"$ref":"#/components/parameters/tenantId"}],"responses":{"200":{"description":"Integrations","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Integration"}}}}}}},"post":{"tags":["Integrations"],"summary":"Create an integration connection","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["connectorType","name","credentialsRef"],"properties":{"connectorType":{"type":"string","description":"e.g. 'salesforce', 'hubspot', 'yardi', 'cdk'"},"name":{"type":"string"},"credentialsRef":{"type":"string","description":"Encrypted credentials reference"},"config":{"type":"object"},"fieldMappings":{"type":"object"},"isSandbox":{"type":"boolean"}}}}}},"responses":{"201":{"description":"Integration created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Integration"}}}}}}},"/v1/integrations/{id}":{"get":{"tags":["Integrations"],"summary":"Get integration by ID","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Integration","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Integration"}}}}}},"patch":{"tags":["Integrations"],"summary":"Update integration settings","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Updated integration"}}},"delete":{"tags":["Integrations"],"summary":"Delete an integration","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No content"}}}},"/v1/integrations/{id}/sync":{"post":{"tags":["Integrations"],"summary":"Trigger a contact sync","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"202":{"description":"Sync queued"}}}},"/v1/integrations/{id}/sync-runs":{"get":{"tags":["Integrations"],"summary":"List sync run history","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Sync runs","content":{"application/json":{"schema":{"type":"array"}}}}}}},"/v1/integrations/{id}/backfill":{"post":{"tags":["Integrations"],"summary":"Trigger a full historical backfill","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"202":{"description":"Backfill queued"}}}},"/v1/integrations/{id}/health":{"get":{"tags":["Integrations"],"summary":"Check integration connectivity","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Health status"}}}},"/v1/integrations/{id}/replay":{"post":{"tags":["Integrations"],"summary":"Replay last failed sync run","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"202":{"description":"Replay queued"}}}},"/v1/analytics/funnel":{"get":{"tags":["Analytics"],"summary":"Solicitation → review conversion funnel metrics","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"locationId","in":"query","schema":{"type":"string"}},{"name":"dateFrom","in":"query","schema":{"type":"string","format":"date"}},{"name":"dateTo","in":"query","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Funnel metrics"}}}},"/v1/analytics/reputation":{"get":{"tags":["Analytics"],"summary":"Reputation metrics (avg rating, review count, sentiment breakdown)","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"locationId","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Reputation metrics"}}}},"/v1/analytics/operations":{"get":{"tags":["Analytics"],"summary":"Operations metrics (response rate, SLA compliance, overdue count)","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"locationId","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Operations metrics"}}}},"/v1/analytics/leaderboard":{"get":{"tags":["Analytics"],"summary":"Location leaderboard ranked by reputation score","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"brandId","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Leaderboard","content":{"application/json":{"schema":{"type":"array"}}}}}}},"/v1/analytics/employee-leaderboard":{"get":{"tags":["Analytics"],"summary":"Employee review leaderboard","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"locationId","in":"query","schema":{"type":"string"}},{"name":"period","in":"query","schema":{"type":"string","enum":["7d","30d","90d"],"default":"30d"}}],"responses":{"200":{"description":"Employee leaderboard"}}}},"/v1/analytics/qr":{"get":{"tags":["Analytics"],"summary":"QR code scan analytics","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"locationId","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"QR analytics"}}}},"/v1/analytics/competitor-benchmark":{"get":{"tags":["Analytics"],"summary":"Competitor benchmark comparison","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"locationId","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Benchmark data"}}}},"/v1/analytics/recommendation-performance":{"get":{"tags":["Analytics"],"summary":"Recommendation accept/reject/apply rates","parameters":[{"$ref":"#/components/parameters/tenantId"}],"responses":{"200":{"description":"Performance data"}}}},"/v1/locations":{"get":{"tags":["Locations"],"summary":"List all active locations for the tenant","parameters":[{"$ref":"#/components/parameters/tenantId"}],"responses":{"200":{"description":"Locations","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Location"}}}}}}},"post":{"tags":["Locations"],"summary":"Create a location","description":"Creates a new location under the tenant. If `brandId` is omitted, the tenant's primary active brand is used.","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1},"brandId":{"type":"string","description":"Optional. Defaults to the tenant's primary active brand."},"phone":{"type":"string","nullable":true},"email":{"type":"string","format":"email","nullable":true},"websiteUrl":{"type":"string","format":"uri","nullable":true},"addressLine1":{"type":"string","nullable":true},"addressLine2":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"state":{"type":"string","nullable":true},"postalCode":{"type":"string","nullable":true},"country":{"type":"string","default":"US"},"googleMyBusinessUrl":{"type":"string","format":"uri","nullable":true},"googlePlaceId":{"type":"string","nullable":true},"yelpUrl":{"type":"string","format":"uri","nullable":true},"yelpBusinessId":{"type":"string","nullable":true},"facebookUrl":{"type":"string","format":"uri","nullable":true},"facebookPageId":{"type":"string","nullable":true}}}}}},"responses":{"201":{"description":"Created location","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Location"}}}},"400":{"description":"Validation error or no brand exists for tenant"}}}},"/v1/locations/bulk":{"post":{"tags":["Locations"],"summary":"Bulk-create locations from CSV","description":"Multipart upload. Field `file` = CSV. Required column: `name`. Optional columns (case-insensitive): addressLine1/address, addressLine2, city, state, postalCode/postal_code/zip, country, phone, email, websiteUrl/website, googlePlaceId, googleMyBusinessUrl, yelpUrl, facebookUrl, brandId. Max 1000 rows, 5 MB. Pass `?enrich=true` to fire Google Places enrichment for each created row in the background.","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"enrich","in":"query","required":false,"schema":{"type":"boolean"},"description":"Auto-fill missing fields via Google Places after create"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary"}}}}}},"responses":{"201":{"description":"Bulk-create result","content":{"application/json":{"schema":{"type":"object","properties":{"total":{"type":"integer"},"created":{"type":"integer"},"failed":{"type":"integer"},"enrichmentQueued":{"type":"integer"},"errors":{"type":"array","items":{"type":"object","properties":{"row":{"type":"integer"},"reason":{"type":"string"}}}}}}}}}}}},"/v1/locations/{id}/enrich":{"post":{"tags":["Locations"],"summary":"Auto-fill missing location details via Google Places","description":"Looks up the location on Google Maps using the saved place_id (or by name + address if none set) and fills in any blank fields: address, phone, website, place ID. Existing values are never overwritten. Pass `?force=true` to re-run the lookup even if a place_id is already set.\n\nRequires a Google Places API key (per-tenant via Settings → Brand → Review Sync, or platform-level via the GOOGLE_PLACES_API_KEY env var). For deployments without a Places key, use POST /v1/locations/:id/enrich-ai instead — that endpoint uses an LLM with web search and only needs an Anthropic API key.","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"force","in":"query","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"description":"Enrichment result","content":{"application/json":{"schema":{"type":"object","properties":{"filled":{"type":"array","items":{"type":"string"},"description":"Field names that were filled in"},"skipped":{"type":"array","items":{"type":"string"},"description":"Field names that already had a value (preserved)"},"placeId":{"type":["string","null"],"description":"Resolved Google Place ID, if any"},"error":{"type":["string","null"],"description":"Reason enrichment was skipped (e.g. no API key, no match)"}}}}}}}}},"/v1/locations/{id}/enrich-ai":{"post":{"tags":["Locations"],"summary":"Auto-fill missing location details via LLM web search (agentic)","description":"Uses Claude with the server-side web_search tool to find the location's public review profile URLs (Google My Business, Yelp, Facebook), Google Place ID, business slug, and other identity fields. The agent runs up to 6 searches per call and verifies hits by cross-checking the address / phone / website against search snippets to avoid mismatched-business false positives.\n\nNo Google Places API key required — the agent finds URLs by browsing the web. Only requires ANTHROPIC_API_KEY on the API service. Existing values on the location are never overwritten.\n\nResponse includes `searchQueries` (the queries the agent ran, useful for audit / debugging) and `rawResponse` (the model's final JSON output). When the model can't confidently identify a URL it leaves the field empty rather than guessing — better-empty-than-wrong.","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"AI enrichment result","content":{"application/json":{"schema":{"type":"object","properties":{"filled":{"type":"array","items":{"type":"string"},"description":"Field names that were filled in (e.g. googleMyBusinessUrl, yelpUrl, facebookUrl, googlePlaceId, websiteUrl, phone)"},"skipped":{"type":"array","items":{"type":"string"},"description":"Field names the model returned a value for but were already populated on the location (preserved)"},"searchQueries":{"type":"array","items":{"type":"string"},"description":"The web-search queries the agent ran (audit trail)"},"rawResponse":{"type":["string","null"],"description":"The model's final JSON synthesis, with reasoning + confidence"},"error":{"type":["string","null"],"description":"Reason enrichment was skipped (e.g. no Anthropic API key, model couldn't parse, location not found)"}}}}}}}}},"/v1/locations/{id}":{"get":{"tags":["Locations"],"summary":"Get location by ID","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Location","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Location"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"put":{"tags":["Locations"],"summary":"Update location fields","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"phone":{"type":"string","nullable":true},"email":{"type":"string","format":"email","nullable":true},"websiteUrl":{"type":"string","format":"uri","nullable":true},"addressLine1":{"type":"string","nullable":true},"addressLine2":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"state":{"type":"string","nullable":true},"postalCode":{"type":"string","nullable":true},"country":{"type":"string"},"googleMyBusinessUrl":{"type":"string","format":"uri","nullable":true},"googlePlaceId":{"type":"string","nullable":true},"yelpUrl":{"type":"string","format":"uri","nullable":true},"yelpBusinessId":{"type":"string","nullable":true},"facebookUrl":{"type":"string","format":"uri","nullable":true},"facebookPageId":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"Updated location","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Location"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/locations/{id}/score":{"get":{"tags":["Locations"],"summary":"Calculate reputation score for a location","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Score"}}}},"/v1/locations/{id}/score/history":{"get":{"tags":["Locations"],"summary":"Score history over time","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Score history","content":{"application/json":{"schema":{"type":"array"}}}}}}},"/v1/locations/{locationId}/variables":{"get":{"tags":["Locations"],"summary":"Get all custom template variables for a location","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"locationId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Variables","content":{"application/json":{"schema":{"type":"object"}}}}}},"put":{"tags":["Locations"],"summary":"Replace all custom variables (full overwrite)","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"locationId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["variables"],"properties":{"variables":{"type":"object","additionalProperties":{"type":"string"}}}}}}},"responses":{"200":{"description":"Updated variables","content":{"application/json":{"schema":{"type":"object"}}}}}},"patch":{"tags":["Locations"],"summary":"Merge custom variables (partial update)","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"locationId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["variables"],"properties":{"variables":{"type":"object","additionalProperties":{"type":"string"}}}}}}},"responses":{"200":{"description":"Merged variables","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/v1/locations/{locationId}/variables/{key}":{"delete":{"tags":["Locations"],"summary":"Delete a single variable key","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"locationId","in":"path","required":true,"schema":{"type":"string"}},{"name":"key","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No content"}}}},"/v1/locations/{locationId}/competitors":{"get":{"tags":["Locations"],"summary":"List competitors for a location","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"locationId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Competitors","content":{"application/json":{"schema":{"type":"array"}}}}}},"post":{"tags":["Locations"],"summary":"Add a competitor","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"locationId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"siteUrl":{"type":"string"}}}}}},"responses":{"201":{"description":"Competitor added"}}}},"/v1/locations/{locationId}/competitors/{id}/metrics":{"patch":{"tags":["Locations"],"summary":"Update competitor rating metrics","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"locationId","in":"path","required":true,"schema":{"type":"string"}},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["avgRating","reviewCount"],"properties":{"avgRating":{"type":"number","minimum":0,"maximum":5},"reviewCount":{"type":"integer"}}}}}},"responses":{"200":{"description":"Updated competitor"}}}},"/v1/locations/{locationId}/competitors/{id}":{"delete":{"tags":["Locations"],"summary":"Remove a competitor","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"locationId","in":"path","required":true,"schema":{"type":"string"}},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No content"}}}},"/v1/locations/{locationId}/competitive-dashboard":{"get":{"tags":["Locations"],"summary":"Competitive dashboard with ranking, gap analysis","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"locationId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Competitive dashboard"}}}},"/v1/compliance/config":{"get":{"tags":["Compliance"],"summary":"Get compliance configuration","parameters":[{"$ref":"#/components/parameters/tenantId"}],"responses":{"200":{"description":"Compliance config or { configured: false }","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComplianceConfig"}}}}}},"put":{"tags":["Compliance"],"summary":"Update compliance configuration (tenant_admin)","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComplianceConfig"}}}},"responses":{"200":{"description":"Updated config","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComplianceConfig"}}}}}}},"/v1/compliance/audit-log":{"get":{"tags":["Compliance"],"summary":"Paginated SOC 2 audit log","parameters":[{"$ref":"#/components/parameters/tenantId"},{"$ref":"#/components/parameters/cursor"},{"$ref":"#/components/parameters/limit"},{"name":"resourceType","in":"query","schema":{"type":"string"}},{"name":"resourceId","in":"query","schema":{"type":"string"}},{"name":"actorId","in":"query","schema":{"type":"string"}},{"name":"dateFrom","in":"query","schema":{"type":"string","format":"date"}},{"name":"dateTo","in":"query","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Audit events","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AuditEvent"}},"pageInfo":{"$ref":"#/components/schemas/PageInfo"},"total":{"type":"integer"}}}}}}}}},"/v1/compliance/export-request":{"post":{"tags":["Compliance"],"summary":"GDPR data export — returns all PII for a contact","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["contactId"],"properties":{"contactId":{"type":"string"}}}}}},"responses":{"200":{"description":"Exported data"}}}},"/v1/compliance/deletion-request":{"post":{"tags":["Compliance"],"summary":"GDPR right to erasure — permanently delete contact PII","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["contactId"],"properties":{"contactId":{"type":"string"},"reason":{"type":"string"}}}}}},"responses":{"200":{"description":"Deletion confirmed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"contactId":{"type":"string"},"deletedAt":{"type":"string","format":"date-time"}}}}}}}}},"/v1/compliance/retention-eligible":{"get":{"tags":["Compliance"],"summary":"List contacts / records eligible for retention deletion","parameters":[{"$ref":"#/components/parameters/tenantId"}],"responses":{"200":{"description":"Eligible records"}}}},"/v1/settings/email":{"get":{"tags":["Settings — Email"],"summary":"Get email provider configuration","parameters":[{"$ref":"#/components/parameters/tenantId"}],"responses":{"200":{"description":"Email config","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailConfig"}}}}}},"put":{"tags":["Settings — Email"],"summary":"Create or update email provider configuration","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"provider":{"type":"string","enum":["acs","sendgrid","ses","smtp"]},"fromAddress":{"type":"string","format":"email"},"fromName":{"type":"string"},"replyToAddress":{"type":"string"},"senderDomain":{"type":"string"},"fallbackToPlatform":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Email config","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailConfig"}}}}}}},"/v1/settings/email/credentials":{"post":{"tags":["Settings — Email"],"summary":"Store encrypted provider credentials","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["connectionString"],"properties":{"connectionString":{"type":"string"}}}}}},"responses":{"200":{"description":"Result"}}}},"/v1/settings/email/domain-status":{"get":{"tags":["Settings — Email"],"summary":"Get DKIM / DMARC domain verification status","parameters":[{"$ref":"#/components/parameters/tenantId"}],"responses":{"200":{"description":"Domain status","content":{"application/json":{"schema":{"type":"object","properties":{"dkimStatus":{"type":"string"},"dmarcStatus":{"type":"string"},"records":{"type":"array"}}}}}}}}},"/v1/settings/email/verify-domain":{"post":{"tags":["Settings — Email"],"summary":"Trigger DNS verification check","parameters":[{"$ref":"#/components/parameters/tenantId"}],"responses":{"200":{"description":"Verification result"}}}},"/v1/settings/email/test-send":{"post":{"tags":["Settings — Email"],"summary":"Send a test email to verify configuration","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["toAddress"],"properties":{"toAddress":{"type":"string","format":"email"}}}}}},"responses":{"200":{"description":"Test send result"}}}},"/v1/settings/sms":{"get":{"tags":["Settings — SMS"],"summary":"Get SMS provider configuration","parameters":[{"$ref":"#/components/parameters/tenantId"}],"responses":{"200":{"description":"SMS config","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmsConfig"}}}}}},"put":{"tags":["Settings — SMS"],"summary":"Create or update SMS provider configuration","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"fromNumber":{"type":"string"},"fallbackToPlatform":{"type":"boolean"}}}}}},"responses":{"200":{"description":"SMS config"}}}},"/v1/settings/sms/credentials":{"post":{"tags":["Settings — SMS"],"summary":"Store encrypted SMS provider credentials","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["connectionString"],"properties":{"connectionString":{"type":"string"}}}}}},"responses":{"200":{"description":"Result"}}}},"/v1/settings/sms/test-send":{"post":{"tags":["Settings — SMS"],"summary":"Send a test SMS","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["toNumber"],"properties":{"toNumber":{"type":"string"}}}}}},"responses":{"200":{"description":"Test result"}}}},"/v1/settings/whatsapp":{"get":{"tags":["Settings — WhatsApp"],"summary":"Get WhatsApp provider configuration","parameters":[{"$ref":"#/components/parameters/tenantId"}],"responses":{"200":{"description":"WhatsApp config","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppConfig"}}}}}},"put":{"tags":["Settings — WhatsApp"],"summary":"Create or update WhatsApp provider configuration","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"channelRegistrationId":{"type":"string"},"fallbackToPlatform":{"type":"boolean"}}}}}},"responses":{"200":{"description":"WhatsApp config"}}}},"/v1/settings/whatsapp/credentials":{"post":{"tags":["Settings — WhatsApp"],"summary":"Store encrypted WhatsApp provider credentials","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["connectionString"],"properties":{"connectionString":{"type":"string"}}}}}},"responses":{"200":{"description":"Result"}}}},"/v1/settings/whatsapp/test-send":{"post":{"tags":["Settings — WhatsApp"],"summary":"Send a test WhatsApp message","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["toNumber"],"properties":{"toNumber":{"type":"string"}}}}}},"responses":{"200":{"description":"Test result"}}}},"/v1/settings/sso":{"get":{"tags":["Settings — SSO"],"summary":"Get SAML SSO configuration","parameters":[{"$ref":"#/components/parameters/tenantId"}],"responses":{"200":{"description":"SSO config","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoConfig"}}}}}},"put":{"tags":["Settings — SSO"],"summary":"Create or update SAML SSO configuration (tenant_admin)","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["entryPoint","issuer","idpCert"],"properties":{"entryPoint":{"type":"string","format":"uri"},"issuer":{"type":"string"},"idpCert":{"type":"string","description":"PEM-encoded IdP certificate"},"signatureAlgorithm":{"type":"string","enum":["sha1","sha256","sha512"]},"callbackUrl":{"type":"string"},"attributeMapping":{"type":"object"},"isEnabled":{"type":"boolean"}}}}}},"responses":{"200":{"description":"SSO config","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SsoConfig"}}}}}},"delete":{"tags":["Settings — SSO"],"summary":"Disable SAML SSO (tenant_admin)","parameters":[{"$ref":"#/components/parameters/tenantId"}],"responses":{"200":{"description":"Disabled"}}}},"/v1/settings/sla-rules":{"get":{"tags":["Settings — SLA"],"summary":"List SLA rules","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"includeInactive","in":"query","schema":{"type":"string","enum":["true"]}}],"responses":{"200":{"description":"SLA rules","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SlaRule"}}}}}}},"post":{"tags":["Settings — SLA"],"summary":"Create an SLA rule (tenant_admin)","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","responseWindowHours","escalateAfterHours"],"properties":{"name":{"type":"string"},"filter":{"type":"object"},"responseWindowHours":{"type":"integer"},"escalateAfterHours":{"type":"integer"},"escalateToRole":{"type":"string"},"isActive":{"type":"boolean"}}}}}},"responses":{"201":{"description":"Rule created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SlaRule"}}}}}}},"/v1/settings/sla-rules/{id}":{"patch":{"tags":["Settings — SLA"],"summary":"Update an SLA rule (tenant_admin)","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Updated rule","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SlaRule"}}}}}},"delete":{"tags":["Settings — SLA"],"summary":"Delete an SLA rule (tenant_admin)","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"id":{"type":"string"}}}}}}}}},"/v1/settings/brand":{"get":{"tags":["Settings — Brand"],"summary":"Get brand profile and review-sync config","parameters":[{"$ref":"#/components/parameters/tenantId"}],"responses":{"200":{"description":"Brand settings"}}},"put":{"tags":["Settings — Brand"],"summary":"Update brand profile","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"websiteUrl":{"type":"string"},"corporateAddress":{"type":"string"},"googleMyBusinessUrl":{"type":"string"},"googlePlaceId":{"type":"string"},"yelpUrl":{"type":"string"},"facebookUrl":{"type":"string"},"brandVoice":{"type":"string"},"brandTone":{"type":"string"},"brandDescription":{"type":"string"}}}}}},"responses":{"200":{"description":"Updated brand profile"}}}},"/v1/settings/brand/review-sync":{"put":{"tags":["Settings — Brand"],"summary":"Update review-sync API credentials","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"googlePlacesApiKey":{"type":"string"},"yelpApiKey":{"type":"string"},"facebookAccessToken":{"type":"string"},"syncEnabled":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Sync config"}}}},"/v1/users":{"get":{"tags":["Users"],"summary":"List tenant users and pending invites (tenant_admin)","parameters":[{"$ref":"#/components/parameters/tenantId"}],"responses":{"200":{"description":"Users and invites"}}},"post":{"tags":["Users"],"summary":"Invite a user to the tenant or create one directly (tenant_admin)","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","name"],"properties":{"email":{"type":"string","format":"email"},"name":{"type":"string"},"role":{"type":"string","enum":["tenant_admin","tenant_user"],"default":"tenant_user"},"externalId":{"type":"string"}}}}}},"responses":{"201":{"description":"User created or invite sent"}}}},"/v1/users/accept-invite":{"post":{"tags":["Users"],"summary":"Accept a pending invite (JWT auth required, no tenant scope needed)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string"},"name":{"type":"string"}}}}}},"responses":{"200":{"description":"Accepted","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"userId":{"type":"string"},"tenantId":{"type":"string"}}}}}}}}},"/v1/users/{id}":{"patch":{"tags":["Users"],"summary":"Update user role or active status (tenant_admin)","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"role":{"type":"string","enum":["tenant_admin","tenant_user"]},"isActive":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Updated"}}},"delete":{"tags":["Users"],"summary":"Remove user from tenant (tenant_admin)","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No content"}}}},"/v1/users/invites/{inviteId}":{"delete":{"tags":["Users"],"summary":"Cancel a pending invite (tenant_admin)","parameters":[{"$ref":"#/components/parameters/tenantId"},{"name":"inviteId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No content"}}}},"/v1/agent/chat":{"post":{"tags":["Agent"],"summary":"Send a message to the AI assistant (blocking)","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["message"],"properties":{"message":{"type":"string"},"sessionId":{"type":"string"}}}}}},"responses":{"200":{"description":"AI reply","content":{"application/json":{"schema":{"type":"object","properties":{"sessionId":{"type":"string"},"reply":{"type":"string"},"artifacts":{"type":"array"}}}}}}}}},"/v1/agent/stream":{"post":{"tags":["Agent"],"summary":"Send a message to the AI assistant (streaming SSE)","description":"Returns `text/event-stream`. Each SSE event carries a partial token or a `[DONE]` sentinel.","parameters":[{"$ref":"#/components/parameters/tenantId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["message"],"properties":{"message":{"type":"string"},"sessionId":{"type":"string"}}}}}},"responses":{"200":{"description":"Server-Sent Events stream","content":{"text/event-stream":{}}}}}},"/v1/agent/sessions":{"get":{"tags":["Agent"],"summary":"List AI assistant session history","parameters":[{"$ref":"#/components/parameters/tenantId"}],"responses":{"200":{"description":"Sessions","content":{"application/json":{"schema":{"type":"array"}}}}}}},"/admin/tenants":{"get":{"tags":["Platform Admin"],"summary":"List all tenants (platform_admin)","responses":{"200":{"description":"Tenants with counts","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Tenant"}}}}}}},"post":{"tags":["Platform Admin"],"summary":"Create a tenant with initial brand and admin user (platform_admin)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","slug","brandName","brandSlug"],"properties":{"name":{"type":"string"},"slug":{"type":"string"},"plan":{"type":"string","enum":["starter","growth","enterprise"]},"brandName":{"type":"string"},"brandSlug":{"type":"string"},"adminExternalId":{"type":"string"},"adminEmail":{"type":"string","format":"email"},"adminName":{"type":"string"}}}}}},"responses":{"201":{"description":"Tenant created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tenant"}}}}}}},"/admin/tenants/{id}":{"get":{"tags":["Platform Admin"],"summary":"Get tenant details (platform_admin)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tenant"}}}}}},"patch":{"tags":["Platform Admin"],"summary":"Update tenant (platform_admin)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"plan":{"type":"string"},"isActive":{"type":"boolean"},"pausedReason":{"type":"string"}}}}}},"responses":{"200":{"description":"Updated tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tenant"}}}}}}},"/admin/tenants/{id}/brands/{brandId}":{"patch":{"tags":["Platform Admin"],"summary":"Update a tenant's brand (platform_admin)","description":"Updates brand identity, voice/tone, and review-profile fields for a tenant's brand. Platform-admin scope.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"brandId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"websiteUrl":{"type":"string","format":"uri","nullable":true},"corporateAddress":{"type":"string","nullable":true},"googleMyBusinessUrl":{"type":"string","format":"uri","nullable":true},"googlePlaceId":{"type":"string","nullable":true},"yelpUrl":{"type":"string","format":"uri","nullable":true},"yelpBusinessId":{"type":"string","nullable":true},"facebookUrl":{"type":"string","format":"uri","nullable":true},"facebookPageId":{"type":"string","nullable":true},"brandVoice":{"type":"string","nullable":true,"description":"e.g. professional, friendly, casual"},"brandTone":{"type":"string","nullable":true,"description":"e.g. warm, formal, empathetic"},"brandDescription":{"type":"string","nullable":true},"isActive":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Updated brand","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Brand"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/admin/tenants/{id}/users":{"get":{"tags":["Platform Admin"],"summary":"List users in a tenant (platform_admin)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Users","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/User"}}}}}}},"post":{"tags":["Platform Admin"],"summary":"Add a user to a tenant (platform_admin)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"externalId":{"type":"string"},"email":{"type":"string","format":"email"},"name":{"type":"string"},"role":{"type":"string","enum":["tenant_admin","tenant_user"]}}}}}},"responses":{"201":{"description":"User added"}}}},"/admin/tenants/{id}/users/{userId}":{"patch":{"tags":["Platform Admin"],"summary":"Change user role in tenant (platform_admin)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"userId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["role"],"properties":{"role":{"type":"string","enum":["tenant_admin","tenant_user"]}}}}}},"responses":{"200":{"description":"Updated"}}},"delete":{"tags":["Platform Admin"],"summary":"Remove user from tenant (platform_admin)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"userId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No content"}}}},"/admin/platform-admins":{"get":{"tags":["Platform Admin"],"summary":"List platform admins (platform_admin)","responses":{"200":{"description":"Platform admins","content":{"application/json":{"schema":{"type":"array"}}}}}},"post":{"tags":["Platform Admin"],"summary":"Grant platform admin (platform_admin)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"externalId":{"type":"string"},"email":{"type":"string","format":"email"},"name":{"type":"string"}}}}}},"responses":{"201":{"description":"Admin granted"}}}},"/admin/platform-admins/{userId}":{"delete":{"tags":["Platform Admin"],"summary":"Revoke platform admin (platform_admin)","parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No content"}}}},"/admin/settings":{"get":{"tags":["Platform Admin"],"summary":"Get platform-level channel settings (platform_admin)","responses":{"200":{"description":"Platform settings"}}}},"/admin/settings/email":{"put":{"tags":["Platform Admin"],"summary":"Update platform email settings (platform_admin)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"connectionString":{"type":"string"},"fromAddress":{"type":"string"},"fromName":{"type":"string"}}}}}},"responses":{"200":{"description":"Updated"}}}},"/admin/settings/sms":{"put":{"tags":["Platform Admin"],"summary":"Update platform SMS settings (platform_admin)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"connectionString":{"type":"string"},"fromNumber":{"type":"string"}}}}}},"responses":{"200":{"description":"Updated"}}}},"/admin/settings/whatsapp":{"put":{"tags":["Platform Admin"],"summary":"Update platform WhatsApp settings (platform_admin)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"connectionString":{"type":"string"},"channelRegistrationId":{"type":"string"}}}}}},"responses":{"200":{"description":"Updated"}}}},"/admin/suppression-list":{"get":{"tags":["Platform Admin"],"summary":"List globally suppressed addresses (platform_admin)","parameters":[{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"limit","in":"query","schema":{"type":"integer","default":50,"maximum":200}}],"responses":{"200":{"description":"Suppressed contacts"}}},"post":{"tags":["Platform Admin"],"summary":"Add to global suppression list (platform_admin)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"},"tenantId":{"type":"string"},"reason":{"type":"string"}}}}}},"responses":{"200":{"description":"Added"}}}}}}