{"openapi":"3.1.0","info":{"title":"Mango Receptionist API","version":"2026-07-12","description":"The public REST API for Mango Receptionist — the AI office manager for service businesses. Authenticate with a per-tenant API key or OAuth 2.1 access token as a Bearer token. Responses use a uniform envelope: single resources as `{ data }`, lists as `{ data, has_more, next_cursor }`, and errors as `{ error: { type, code, message } }`. Writes accept an optional `Idempotency-Key` header. Each operation lists the scope(s) it needs under the `x-required-scopes` extension."},"servers":[{"url":"https://mangoreceptionist.com/api/v1"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"Per-tenant key mrk_live_… / mrk_test_…, or OAuth access token mro_at_…"}},"schemas":{"Contact":{"type":"object","description":"A customer or lead. Redacted view — internal match keys and profiles are never exposed.","properties":{"object":{"type":"string","const":"contact"},"id":{"type":"string"},"display_name":{"type":"string"},"first_name":{"type":["string","null"]},"last_name":{"type":["string","null"]},"phones":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":["string","null"]},"primary":{"type":"boolean"}},"required":["value","primary"]}},"emails":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":["string","null"]},"primary":{"type":"boolean"}},"required":["value","primary"]}},"status":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"source":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["object","id","display_name","status","created_at","updated_at"]},"Job":{"type":"object","description":"A job, ticket, or opportunity.","properties":{"object":{"type":"string","const":"job"},"id":{"type":"string"},"contact_id":{"type":"string"},"title":{"type":"string"},"job_type":{"type":["string","null"]},"status":{"type":"string"},"quote_value_usd":{"type":["number","null"]},"job_value_usd":{"type":["number","null"]},"payment_status":{"type":["string","null"]},"paid_at":{"type":["string","null"]},"lead_source":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["object","id","contact_id","title","status","created_at","updated_at"]},"Followup":{"type":"object","description":"An entry on the automated chase list.","properties":{"object":{"type":"string","const":"followup"},"id":{"type":"string"},"contact_id":{"type":"string"},"job_id":{"type":["string","null"]},"loop_type":{"type":"string"},"status":{"type":"string"},"reason":{"type":["string","null"]},"opened_at":{"type":"string","format":"date-time"},"due_at":{"type":"string","format":"date-time"},"resolved_at":{"type":["string","null"]},"resolved_reason":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["object","id","contact_id","loop_type","status","opened_at","due_at","created_at","updated_at"]},"Call":{"type":"object","description":"A phone call record. Redacted view — the AI `summary` is exposed, but raw call transcripts, the extracted-info profile, internal routing, delivery flags, and support-escalation fields are never exposed. `recording_url` is null unless a shareable recording exists.","properties":{"object":{"type":"string","const":"call"},"id":{"type":"string"},"from":{"type":"string","description":"The caller's phone number."},"to":{"type":"string","description":"The business line the call reached."},"outcome":{"type":"string","description":"Call outcome, e.g. booked_in_calendar | quoted_price | sms_summary_sent | message_taken | emergency_escalated | no_tenant_match."},"summary":{"type":"string"},"duration_seconds":{"type":"number"},"started_at":{"type":"string","format":"date-time"},"emergency":{"type":"boolean"},"is_test_call":{"type":"boolean"},"priority_level":{"type":["string","null"]},"contact_id":{"type":["string","null"]},"job_id":{"type":["string","null"]},"booking_id":{"type":["string","null"]},"recording_url":{"type":["string","null"]}},"required":["object","id","from","to","outcome","summary","duration_seconds","started_at"]},"Invoice":{"type":"object","description":"A customer-facing invoice the business issued to its customer. Redacted view — money fields are in the invoice's own `currency`; the public bearer link, token hash, accounting-provider ids, PDF/storage refs, seller signature, and delivery/sync internals are never exposed.","properties":{"object":{"type":"string","const":"invoice"},"id":{"type":"string"},"number":{"type":["string","null"]},"contact_id":{"type":"string"},"job_id":{"type":"string"},"status":{"type":"string","description":"draft | sent | viewed | part_paid | paid | overdue | void | credited."},"currency":{"type":"string","description":"ISO currency, e.g. AUD | NZD | USD | GBP | SGD | MYR."},"subtotal":{"type":"number"},"tax":{"type":"number"},"total":{"type":"number"},"deposit_applied":{"type":"number"},"amount_paid":{"type":"number"},"balance_due":{"type":"number"},"line_items":{"type":"array","items":{"type":"object","description":"One invoice line — description and amount only.","properties":{"description":{"type":"string"},"amount":{"type":"number"}},"required":["description","amount"]}},"issued_at":{"type":["string","null"]},"due_at":{"type":["string","null"]},"viewed_at":{"type":["string","null"]},"paid_at":{"type":["string","null"]},"voided_at":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["object","id","contact_id","job_id","status","currency","subtotal","tax","total","line_items","created_at","updated_at"]},"Quote":{"type":"object","description":"A redacted customer quote. Public-link tokens, payment bank details, signatures, IP addresses, and delivery internals are omitted.","properties":{"object":{"type":"string","const":"quote"},"id":{"type":"string"},"number":{"type":"string"},"contact_id":{"type":"string"},"job_id":{"type":"string"},"title":{"type":"string"},"status":{"type":"string"},"currency":{"type":"string"},"subtotal":{"type":"number"},"tax":{"type":"number"},"total":{"type":"number"},"line_items":{"type":"array","items":{"type":"object","additionalProperties":true}},"sent_at":{"type":"string","format":"date-time"},"viewed_at":{"type":["string","null"]},"accepted_at":{"type":["string","null"]},"declined_at":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["object","id","number","contact_id","job_id","status","currency","total","created_at","updated_at"]},"PublicBooking":{"type":"object","description":"A redacted scheduler booking. Capability tokens, calendar-provider ids, notification internals, and site access secrets are omitted.","properties":{"object":{"type":"string","const":"booking"},"id":{"type":"string"},"contact_id":{"type":["string","null"]},"job_id":{"type":["string","null"]},"assigned_member_id":{"type":["string","null"]},"service_id":{"type":"string"},"service_name":{"type":"string"},"customer_name":{"type":"string"},"customer_phone":{"type":["string","null"]},"customer_email":{"type":["string","null"]},"address":{"type":["string","null"]},"start_at":{"type":"string"},"end_at":{"type":"string"},"arrival_window_start":{"type":["string","null"]},"arrival_window_end":{"type":["string","null"]},"status":{"type":"string"},"urgency":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["object","id","service_id","service_name","customer_name","start_at","end_at","status","created_at","updated_at"]},"Availability":{"type":"object","description":"The public booking-window policy without authority receipts, hashes, or internal versions.","properties":{"object":{"type":"string","const":"availability"},"timezone":{"type":"string"},"weekly_hours":{"type":"array","items":{"type":"object","additionalProperties":true}},"date_overrides":{"type":"array","items":{"type":"object","additionalProperties":true}},"blackouts":{"type":"array","items":{"type":"string"}},"min_notice_hours":{"type":"number"},"max_per_day":{"type":"integer"},"default_buffer_minutes":{"type":"integer"},"max_advance_days":{"type":"integer"},"updated_at":{"type":"string","format":"date-time"}},"required":["object","timezone","weekly_hours","date_overrides","blackouts","min_notice_hours","max_per_day","default_buffer_minutes","max_advance_days","updated_at"]},"File":{"type":"object","description":"Redacted file metadata. Storage paths, extracted contents, search vectors, and download credentials are omitted.","properties":{"object":{"type":"string","const":"file"},"id":{"type":"string"},"contact_id":{"type":["string","null"]},"job_id":{"type":["string","null"]},"folder":{"type":["string","null"]},"title":{"type":["string","null"]},"file_name":{"type":"string"},"mime_type":{"type":"string"},"size_bytes":{"type":"number"},"source":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"status":{"type":"string"},"visibility":{"type":"string"},"content_sha256":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["object","id","file_name","mime_type","size_bytes","source","status","visibility","created_at","updated_at"]},"Integration":{"type":"object","description":"Redacted connected-app health. Credentials and provider tokens are never exposed.","properties":{"object":{"type":"string","const":"integration"},"provider":{"type":"string"},"id":{"type":"string"},"status":{"type":"string"},"ownership":{"type":["object","null"],"additionalProperties":{"type":"string"}},"last_sync_at":{"type":["string","null"]},"last_sync_status":{"type":["string","null"],"enum":["completed","partial","failed",null]},"last_success_at":{"type":["string","null"]},"last_error_code":{"type":["string","null"]},"has_credentials":{"type":"boolean"}},"required":["object","provider","id","status","ownership","last_sync_at","last_sync_status","last_success_at","last_error_code","has_credentials"]},"Booking":{"type":"object","description":"A scheduler booking. Documents the stable customer-facing fields; the object may carry additional internal fields not listed here.","properties":{"id":{"type":"string"},"status":{"type":"string"},"jobTypeId":{"type":"string"},"jobTypeName":{"type":"string"},"customerName":{"type":"string"},"customerPhone":{"type":"string"},"customerEmail":{"type":"string"},"contactId":{"type":"string"},"jobId":{"type":"string"},"address":{"type":"string"},"start_at":{"type":"string"},"end_at":{"type":"string"},"waitlist":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","status","start_at"],"additionalProperties":true},"Message":{"type":"object","description":"The delivery outcome of a one-off SMS or email send.","properties":{"object":{"type":"string","const":"message"},"channel":{"type":"string","enum":["sms","email"]},"to":{"type":"string"},"status":{"type":"string","description":"Provider status, or `suppressed` when a marketing send hit an opt-out."},"provider":{"type":"string"},"reason":{"type":"string"},"error":{"type":"string"},"message_id":{"type":"string","description":"Provider message id (SMS)."},"id":{"type":"string","description":"Provider message id (email)."}},"required":["object","channel","to","status"]},"WriteResult":{"type":"object","description":"Result summary from the write engine. Always includes `ok`; on success it also includes the affected resource (e.g. `contact`, `job`, or `followup`).","properties":{"ok":{"type":"boolean"}},"required":["ok"],"additionalProperties":true},"Error":{"type":"object","description":"The uniform error envelope for every 4xx/5xx response.","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"Broad class, e.g. authentication_error | invalid_request_error | idempotency_error | api_error."},"code":{"type":"string","description":"Stable machine code, e.g. invalid_api_key | insufficient_scope | not_found."},"message":{"type":"string","description":"Human-readable explanation."}},"required":["type","code","message"]}},"required":["error"]}},"responses":{"Unauthorized":{"description":"No key, or an invalid/expired/revoked key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"type":"authentication_error","code":"invalid_api_key","message":"The API key is invalid, expired, or revoked."}}}}},"Forbidden":{"description":"The key is valid but lacks a required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"type":"invalid_request_error","code":"insufficient_scope","message":"This key is missing required scope(s)."}}}}},"NotFound":{"description":"No such resource on this tenant.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"type":"invalid_request_error","code":"not_found","message":"No resource found with that id."}}}}},"Conflict":{"description":"Idempotency-Key reuse with a different body, or a scheduling conflict.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"type":"idempotency_error","code":"idempotency_key_reuse","message":"This Idempotency-Key was already used with a different request body."}}}}},"RateLimited":{"description":"Too many requests — back off and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"type":"api_error","code":"rate_limited","message":"Too many requests."}}}}}}},"paths":{"/openapi.json":{"get":{"summary":"This OpenAPI document","description":"The machine-readable spec for this API. Public — no authentication required.","security":[],"x-required-scopes":[],"responses":{"200":{"description":"The OpenAPI 3.1 document.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/me":{"get":{"x-required-scopes":[],"summary":"Connection test / whoami","description":"Returns the authenticated business account and the scopes the key holds. Any valid key works; no specific scope is required.","responses":{"200":{"description":"The authenticated account.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","const":"account"},"tenant_id":{"type":"string"},"name":{"type":["string","null"]},"scopes":{"type":"array","items":{"type":"string"}},"env":{"type":"string","enum":["live","test"]}},"required":["object","tenant_id","scopes","env"]}},"required":["data"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/contacts":{"get":{"x-required-scopes":["contacts:read"],"summary":"List contacts","description":"Contacts (customers and leads), newest first, cursor-paginated.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Page size, 1–100 (default 50)."},{"name":"starting_after","in":"query","required":false,"schema":{"type":"string"},"description":"Cursor — an object id to page after (newest-first order)."}],"responses":{"200":{"description":"A page of contacts.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}},"required":["data","has_more","next_cursor"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"x-required-scopes":["contacts:write"],"summary":"Create or enrich a contact","description":"Create a contact, or enrich an existing one matched by phone/email. Honours an optional `Idempotency-Key` header.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Provide at least a name and/or a phone/email so the contact can be matched or created.","properties":{"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"},"address":{"type":"string"},"status":{"type":"string","description":"e.g. new | active | customer."},"source":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"notes":{"type":"string","description":"Saved profile notes."}}}}}},"responses":{"201":{"description":"The created/enriched contact summary.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WriteResult"}},"required":["data"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/contacts/{id}":{"get":{"x-required-scopes":["contacts:read"],"summary":"Get a contact","description":"Fetch one contact by id.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The contact id."}],"responses":{"200":{"description":"The contact.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Contact"}},"required":["data"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}},"patch":{"x-required-scopes":["contacts:write"],"summary":"Update a contact","description":"Update a contact's identity, status, source, tags, or saved notes.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The contact id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"},"address":{"type":"string"},"status":{"type":"string"},"source":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"replace_tags":{"type":"boolean","description":"Replace tags instead of merging."},"notes":{"type":"string"}}}}}},"responses":{"200":{"description":"The updated contact summary.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WriteResult"}},"required":["data"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/jobs":{"get":{"x-required-scopes":["jobs:read"],"summary":"List jobs","description":"Jobs, tickets, and opportunities, newest first, cursor-paginated.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Page size, 1–100 (default 50)."},{"name":"starting_after","in":"query","required":false,"schema":{"type":"string"},"description":"Cursor — an object id to page after (newest-first order)."}],"responses":{"200":{"description":"A page of jobs.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Job"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}},"required":["data","has_more","next_cursor"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"x-required-scopes":["jobs:write"],"summary":"Create a job","description":"Create a job/opportunity/ticket, linking or creating the customer contact. Honours an optional `Idempotency-Key` header.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","description":"Short job title."},"caseType":{"type":"string","description":"job | ticket | opportunity."},"contactId":{"type":"string"},"contactName":{"type":"string"},"contactPhone":{"type":"string"},"contactEmail":{"type":"string"},"serviceAddress":{"type":"string"},"jobType":{"type":"string"},"jobValueUsd":{"type":"number"},"quoteValueUsd":{"type":"number"},"note":{"type":"string"}},"required":["title"]}}}},"responses":{"201":{"description":"The created job summary.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WriteResult"}},"required":["data"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/jobs/{id}":{"get":{"x-required-scopes":["jobs:read"],"summary":"Get a job","description":"Fetch one job by id.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The job id."}],"responses":{"200":{"description":"The job.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Job"}},"required":["data"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}},"patch":{"x-required-scopes":["jobs:write"],"summary":"Update a job","description":"Update a job's details and/or move its lifecycle status.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The job id."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"jobType":{"type":"string"},"serviceAddress":{"type":"string"},"jobValueUsd":{"type":"number"},"quoteValueUsd":{"type":"number"},"paymentStatus":{"type":"string"},"status":{"type":"string","description":"New lifecycle status, e.g. booked | in_progress | completed | lost. Routed through the validated status writer."},"note":{"type":"string"}}}}}},"responses":{"200":{"description":"The updated job summary.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WriteResult"}},"required":["data"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/jobs/{id}/appointment":{"post":{"x-required-scopes":["jobs:write"],"summary":"Book a job appointment","description":"Book a confirmed Mango schedule appointment on the job. Cross-diary conflicts are rejected with 409.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The job id to book the appointment on."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Book a confirmed appointment on the job in the path (or, with caller details and no job match, create the job). Conflicts across both diaries are rejected.","properties":{"appointmentStartAt":{"type":"string","description":"ISO 8601 start with timezone offset."},"appointmentEndAt":{"type":"string","description":"ISO 8601 end with timezone offset (optional)."},"callerName":{"type":"string"},"callerPhone":{"type":"string"},"callerEmail":{"type":"string"},"contactId":{"type":"string"},"jobTitle":{"type":"string"},"jobType":{"type":"string"},"serviceAddress":{"type":"string"},"summary":{"type":"string"},"source":{"type":"string","description":"call | sms | web_chat | manual."}},"required":["appointmentStartAt"]}}}},"responses":{"201":{"description":"The booked appointment + job summary.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WriteResult"}},"required":["data"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/followups":{"get":{"x-required-scopes":["contacts:read"],"summary":"List followups","description":"The automated chase list — open and resolved followups, newest first. Read under `contacts:read`.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Page size, 1–100 (default 50)."},{"name":"starting_after","in":"query","required":false,"schema":{"type":"string"},"description":"Cursor — an object id to page after (newest-first order)."}],"responses":{"200":{"description":"A page of followups.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Followup"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}},"required":["data","has_more","next_cursor"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"x-required-scopes":["followups:write"],"summary":"Create a follow-up","description":"Add a follow-up (chase) for a contact or job, due on a date or in N days. Honours an optional `Idempotency-Key` header.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"contactId":{"type":"string"},"jobId":{"type":"string"},"dueAt":{"type":"string","description":"ISO 8601 due date."},"dueInDays":{"type":"number"},"loopType":{"type":"string"},"reason":{"type":"string"}}}}}},"responses":{"201":{"description":"The created follow-up summary.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WriteResult"}},"required":["data"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/followups/{id}":{"get":{"x-required-scopes":["contacts:read"],"summary":"Get a follow-up","description":"Fetch one follow-up by id. Read under `contacts:read`.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The follow-up id."}],"responses":{"200":{"description":"The follow-up.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Followup"}},"required":["data"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/followups/{id}/complete":{"post":{"x-required-scopes":["followups:write"],"summary":"Resolve a follow-up","description":"Resolve or dismiss an open follow-up once it has been handled.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The follow-up id."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"resolution":{"type":"string"},"status":{"type":"string","description":"resolved (default) | dismissed."}}}}}},"responses":{"200":{"description":"The resolved follow-up summary.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WriteResult"}},"required":["data"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/calls":{"get":{"x-required-scopes":["calls:read"],"summary":"List calls","description":"The business's phone call records, newest first (by call start time), cursor-paginated.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Page size, 1–100 (default 50)."},{"name":"starting_after","in":"query","required":false,"schema":{"type":"string"},"description":"Cursor — an object id to page after (newest-first order)."}],"responses":{"200":{"description":"A page of calls.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Call"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}},"required":["data","has_more","next_cursor"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/calls/{id}":{"get":{"x-required-scopes":["calls:read"],"summary":"Get a call","description":"Fetch one call record by id.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The call id."}],"responses":{"200":{"description":"The call.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Call"}},"required":["data"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/invoices":{"get":{"x-required-scopes":["invoices:read"],"summary":"List invoices","description":"Customer-facing invoices the business has issued to its customers, newest first, cursor-paginated.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Page size, 1–100 (default 50)."},{"name":"starting_after","in":"query","required":false,"schema":{"type":"string"},"description":"Cursor — an object id to page after (newest-first order)."}],"responses":{"200":{"description":"A page of invoices.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Invoice"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}},"required":["data","has_more","next_cursor"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/invoices/{id}":{"get":{"x-required-scopes":["invoices:read"],"summary":"Get an invoice","description":"Fetch one customer invoice by id.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The invoice id."}],"responses":{"200":{"description":"The invoice.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}},"required":["data"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/quotes":{"get":{"x-required-scopes":["quotes:read"],"summary":"List quotes","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Page size, 1–100 (default 50)."},{"name":"starting_after","in":"query","required":false,"schema":{"type":"string"},"description":"Cursor — an object id to page after (newest-first order)."}],"responses":{"200":{"description":"A page of redacted customer quotes.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Quote"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}},"required":["data","has_more","next_cursor"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/quotes/{id}":{"get":{"x-required-scopes":["quotes:read"],"summary":"Get a quote","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The quote id."}],"responses":{"200":{"description":"The redacted quote.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Quote"}},"required":["data"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/availability":{"get":{"x-required-scopes":["schedule:read"],"summary":"Get booking availability policy","responses":{"200":{"description":"The redacted booking policy.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Availability"}},"required":["data"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/integrations/servicem8":{"get":{"x-required-scopes":["integrations:read"],"summary":"Inspect ServiceM8 connection health","description":"Read redacted ServiceM8 connection status and ownership policy. Provider credentials are never returned.","responses":{"200":{"description":"The tenant's redacted ServiceM8 connections.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Integration"}}},"required":["data"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/files":{"get":{"x-required-scopes":["files:read"],"summary":"List file metadata","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Page size, 1–100 (default 50)."},{"name":"starting_after","in":"query","required":false,"schema":{"type":"string"},"description":"Cursor — an object id to page after (newest-first order)."}],"responses":{"200":{"description":"A page of redacted file metadata.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/File"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}},"required":["data","has_more","next_cursor"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/files/{id}":{"get":{"x-required-scopes":["files:read"],"summary":"Get file metadata","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The file id."}],"responses":{"200":{"description":"The redacted file metadata.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/File"}},"required":["data"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/bookings":{"get":{"x-required-scopes":["schedule:read"],"summary":"List bookings","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Page size, 1–100 (default 50)."},{"name":"starting_after","in":"query","required":false,"schema":{"type":"string"},"description":"Cursor — an object id to page after (newest-first order)."},{"name":"from","in":"query","schema":{"type":"string"}},{"name":"to","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of redacted bookings.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicBooking"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}},"required":["data","has_more","next_cursor"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"x-required-scopes":["schedule:write"],"summary":"Create a booking","description":"Commit a scheduler booking for a customer at a start time. Enforces availability, cross-diary conflicts, travel feasibility, and required intake.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"customerName":{"type":"string"},"startAt":{"type":"string","description":"ISO 8601 start with timezone offset."},"jobTypeId":{"type":"string"},"customerPhone":{"type":"string"},"customerEmail":{"type":"string"},"address":{"type":"string"},"contactId":{"type":"string"},"jobId":{"type":"string"},"answers":{"type":"object","description":"Intake answers keyed by question label.","additionalProperties":true},"contextText":{"type":"string"}},"required":["customerName","startAt"]}}}},"responses":{"201":{"description":"The confirmed booking.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"ok":{"type":"boolean"},"booking":{"$ref":"#/components/schemas/Booking"},"message":{"type":"string"}},"required":["ok"],"additionalProperties":true}},"required":["data"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/bookings/{id}":{"get":{"x-required-scopes":["schedule:read"],"summary":"Get a booking","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The booking id."}],"responses":{"200":{"description":"The redacted booking.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PublicBooking"}},"required":["data"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/bookings/{id}/cancel":{"post":{"x-required-scopes":["bookings:cancel"],"summary":"Cancel a booking","description":"Cancel a scheduler booking. The customer is notified and the freed slot is offered to matching waitlist entries.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The booking id to cancel."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string"}}}}}},"responses":{"200":{"description":"The cancelled booking.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"ok":{"type":"boolean"},"booking":{"$ref":"#/components/schemas/Booking"}},"required":["ok"],"additionalProperties":true}},"required":["data"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/bookings/{id}/reschedule":{"post":{"x-required-scopes":["schedule:write"],"summary":"Reschedule a booking","description":"Move a scheduler booking to a new start time. Rules and conflicts are re-checked; the customer is notified and reminders reset.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The booking id to reschedule."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Move the booking in the path to a new start time. Availability, conflicts, and rules are re-checked; the customer is notified and reminders reset.","properties":{"newStartAt":{"type":"string","description":"New ISO 8601 start with timezone offset."},"newEndAt":{"type":"string","description":"New ISO 8601 end with timezone offset (optional)."}},"required":["newStartAt"]}}}},"responses":{"200":{"description":"The rescheduled booking.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"ok":{"type":"boolean"},"booking":{"$ref":"#/components/schemas/Booking"}},"required":["ok"],"additionalProperties":true}},"required":["data"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/messages":{"post":{"x-required-scopes":["messages:send"],"summary":"Send an SMS or email","description":"Send a purpose-classified SMS or email on the business's behalf. Marketing purposes require positive channel consent evidence (suppressed sends return `status: suppressed`).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","enum":["sms","email"]},"to":{"type":"string","description":"Recipient phone (SMS) or email address."},"purpose":{"type":"string","enum":["compliance_ack","account_activation","missed_call_text_back","conversation_reply","booking_confirmation","booking_update","booking_link_delivery","appointment_reminder","appointment_status","quote_delivery","invoice_delivery","payment_receipt","deposit_request","variation_approval","booking_refund","photo_request","completion_photo_delivery","customer_message","customer_message_marketing","vault_file_delivery","owner_notification","crew_decision","automation_service","quote_followup","review_request","review_followup","no_booking_followup","soft_deflect_followup","repeat_customer_offer","campaign","lead_followup","waitlist_offer"],"description":"Registered business purpose; determines service versus marketing compliance."},"message":{"type":"string","description":"SMS body, or email body (alias of `body`)."},"subject":{"type":"string","description":"Email subject (email only)."},"body":{"type":"string","description":"Email body (email only)."}},"required":["channel","to","purpose"]}}}},"responses":{"200":{"description":"The send outcome.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Message"}},"required":["data"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}