# TrustPager API — full reference Base URL: `https://api.trustpager.com/functions/v1/api/v1` Authentication: `Authorization: Bearer ` (one API key per workspace). Generated 2026-05-21T21:06:12.472Z from the source-of-truth endpoint data. --- ## Universal Search Search across all entity types (contacts, customers, deals) in a single call. Supports fuzzy/typo-tolerant matching on names via pg_trgm. ### POST /search Universal fuzzy search across contacts, customers, and deals. Returns ranked results with entity_type, display_name, subtitle, and relevance rank. Exact substring matches rank highest (1.0), fuzzy matches rank by trigram similarity. Names support typo-tolerant matching; email and phone use exact substring matching. **Scopes:** `contacts:read`, `companies:read`, `opportunities:read` Parameters: - `query` (body, string, required): Search text -- matches names, emails, phone numbers across all entity types - `limit` (body, number): Max results (1-100, default 25) ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/search" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "Sarah", "limit": 10 }' ``` --- ## Contacts Manage individual contacts (people) in the CRM. Supports search, filtering, sub-resources (opportunities, activities, employers), and AI enrichment. ### GET /contacts List all contacts with cursor-based pagination. Supports search, source filter, and date range. Note: contacts do not have tags -- tags live on opportunities only. **Scopes:** `contacts:read` Parameters: - `search` (query, string): Search by first_name, last_name, email, phone (mobile), or landline - `source` (query, string): Filter by lead source - `customer_id` (query, uuid): Filter contacts linked to a specific company / account (param name preserved for backward compatibility) - `created_after` (query, string): ISO date, return contacts created after this date - `created_before` (query, string): ISO date, return contacts created before this date - `limit` (query, number): Max results per page (1-100, default 25) - `cursor` (query, string): Cursor for next page - `fields` (query, string): Comma-separated list of fields to return - `expand` (query, string): Comma-separated expansions: employers - `email_unsubscribed` (query, boolean): Filter to contacts who have opted out of email. true = opted out only, false = opted in only. Omit for all contacts. - `sms_unsubscribed` (query, boolean): Filter to contacts who have opted out of SMS. true = opted out only, false = opted in only. Omit for all contacts. ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/contacts?search=John&limit=10" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### GET /contacts/:id Retrieve a single contact by ID. Supports field selection and employer expansion. **Scopes:** `contacts:read` Parameters: - `id` (path, uuid, required): Contact ID - `fields` (query, string): Comma-separated list of fields - `expand` (query, string): Comma-separated expansions: employers ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/contacts/a1b2c3d4-..." \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### POST /contacts Create a new contact. first_name is required; last_name is optional. Contacts without a last name render cleanly in automation templates via {{contact.display_name}} and {{contact.greeting}}. Empty or whitespace-only last_name values are stored as NULL. Set skip_automations: true to suppress the contact_created trigger -- recommended for historical imports. **Scopes:** `contacts:write` — _write_ Parameters: - `first_name` (body, string, required): Contact first name. Trimmed on save. - `last_name` (body, string): Contact last name (optional). Empty or whitespace-only values are stored as NULL. In merge templates, prefer {{contact.display_name}} (falls back to first name when no last name) and {{contact.greeting}} ("Hi Paul" or "Hi there") over raw {{contact.last_name}}. - `email` (body, string): Email address - `phone` (body, string): Mobile number in E.164 format (e.g. +61412345678). MUST be a mobile number -- landlines will be rejected with a 400 error. Use the landline field for fixed-line numbers. - `landline` (body, string): Landline/fixed-line number in E.164 format (e.g. +61299991234) - `date_of_birth` (body, string): Date of birth in YYYY-MM-DD format (e.g. 1990-03-26). Used by the birthday messaging cron to send automated birthday emails/SMS. - `job_title` (body, string): Job title - `source` (body, string): Lead source (e.g. website, referral, api) - `notes` (body, string): Free-text notes - `metadata` (body, object): Custom field values as { field_id: value } pairs. Use GET /crm-settings to discover available custom fields. Reserved key: "quick_links" stores per-contact Quick Link URLs as { : } -- define types via PATCH /company/crm-settings. UUID-shaped keys at metadata root are rejected (400) -- Quick Link URLs must be nested under metadata.quick_links. - `address_line1` (body, string): Street address line 1 - `city` (body, string): City - `state` (body, string): State/province - `postal_code` (body, string): Postal/zip code - `country` (body, string): Country (default: Australia) - `skip_automations` (body, boolean): Set true to suppress the contact_created trigger. Use for historical imports. Default false. ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/contacts" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "first_name": "Paul", "email": "paul@example.com", "phone": "+61412345678", "source": "api" }' ``` ### PATCH /contacts/:id Update an existing contact. Only include fields you want to change. Every successful PATCH emits a field-level audit row to crm_field_change_log (viewable at /data/crm-logs with the crm_audit:read scope). **Scopes:** `contacts:write` — _write_ Parameters: - `id` (path, uuid, required): Contact ID - `first_name` (body, string): First name. Trimmed on save. - `last_name` (body, string): Last name. Send "" or null to clear a previously set surname -- empty/whitespace values are stored as NULL. - `email` (body, string): Email - `phone` (body, string): Mobile number in E.164 format. Landlines will be rejected -- use the landline field. - `landline` (body, string): Landline/fixed-line number in E.164 format. Set to null to clear. - `date_of_birth` (body, string): Date of birth in YYYY-MM-DD format. Set to null to clear. - `notes` (body, string): Notes - `metadata` (body, object): Custom field values as { field_id: value } pairs. Replaces entire metadata object -- read first with GET /contacts/:id and merge locally. Reserved key: "quick_links" stores per-contact Quick Link URLs as { : }. UUID-shaped keys at metadata root are rejected (400). - `email_unsubscribed` (body, boolean): Set true to mark contact as opted out of email. Set false to re-subscribe. This flag is also updated automatically on hard bounce, spam complaint, or unsubscribe link click. - `sms_unsubscribed` (body, boolean): Set true to mark contact as opted out of SMS. Set false to re-subscribe. This flag is also updated automatically when the contact texts STOP (opt out) or START (opt in). ```bash curl -X PATCH \ "https://api.trustpager.com/functions/v1/api/v1/contacts/a1b2c3d4-..." \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "sms_unsubscribed": false }' ``` ### DELETE /contacts/:id Delete a contact. Returns 204 No Content on success. **Scopes:** `contacts:write` — _write_ Parameters: - `id` (path, uuid, required): Contact ID ### POST /contacts/search Full-text search across contact names, email, and phone. Returns up to 100 results. **Scopes:** `contacts:read` Parameters: - `query` (body, string, required): Search query - `limit` (body, number): Max results (1-100, default 25) ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/contacts/search" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "john", "limit": 10 }' ``` ### GET /contacts/:id/opportunities List all opportunities associated with a contact. Legacy alias: GET /contacts/:id/deals. **Scopes:** `contacts:read`, `opportunities:read` Parameters: - `id` (path, uuid, required): Contact ID ### GET /contacts/:id/activities List all activities (calls, meetings, notes) for a contact. **Scopes:** `contacts:read`, `activities:read` Parameters: - `id` (path, uuid, required): Contact ID ### GET /contacts/:id/employers List company / account links for this contact. **Scopes:** `contacts:read` Parameters: - `id` (path, uuid, required): Contact ID ### POST /contacts/:id/employers/:customerId Link a contact to a company / account (employer relationship). Path param name preserved for backward compatibility. **Scopes:** `contacts:write` — _write_ Parameters: - `id` (path, uuid, required): Contact ID - `customerId` (path, uuid, required): Company ID to link ### DELETE /contacts/:id/employers/:customerId Remove an employer link between a contact and a company. Path param name preserved for backward compatibility. **Scopes:** `contacts:write` — _write_ Parameters: - `id` (path, uuid, required): Contact ID - `customerId` (path, uuid, required): Company ID to unlink ### GET /contacts/:id/birthday-sends Get birthday message send history for a contact. Returns all years birthday emails/SMS were sent, the channels used, and the send date. Useful for auditing birthday message delivery. **Scopes:** `contacts:read` Parameters: - `id` (path, uuid, required): Contact ID ### POST /contacts/bulk-create Create up to 100 contacts in a single request. Built for historical migrations and bulk imports. Each record accepts the same fields as POST /contacts (first_name required). Set skip_automations: true to suppress contact_created triggers across all records. Returns a created array and an errors array. **Scopes:** `contacts:write` — _write_ Parameters: - `records` (body, object[], required): Array of contact objects (max 100). Each requires first_name plus any optional contact fields (last_name, email, phone, landline, job_title, notes, metadata, source, date_of_birth, etc.). - `skip_automations` (body, boolean): Set true to suppress contact_created triggers across all records. Strongly recommended for historical imports. ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/contacts/bulk-create" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "skip_automations": true, "records": [ { "first_name": "Alice", "last_name": "Smith", "email": "alice@example.com" }, { "first_name": "Bob", "last_name": "Jones", "phone": "+61412345678" } ] }' ``` ### POST /contacts/voice/unsubscribe Voice-agent unsubscribe endpoint. Returns text/plain. Called by a Retell voice agent when a caller asks to opt out. Resolves the caller by phone number (from args.phone in the Retell call envelope, with fallback to from_number for inbound or to_number for outbound calls). Unsubscribes ALL contacts in the workspace that share the phone number (Spam Act compliance -- up to 20 matched contacts). Sets email_unsubscribed and sms_unsubscribed to true on each matched contact. Returns a plain-text confirmation string the agent reads aloud. On error the response body begins with "UNSUBSCRIBE NOT COMPLETED -- DO NOT TELL THE CALLER THEY HAVE BEEN REMOVED" so the agent script can instruct the agent to handle the failure gracefully without misleading the caller. **Scopes:** `contacts:write` — _write_ Parameters: - `phone` (body, string): Caller phone in E.164 or local format. If omitted, resolves from the Retell call envelope (from_number for inbound, to_number for outbound). Provide explicitly when testing outside a live Retell call. - `reason` (body, string): Optional reason for the unsubscribe (e.g. "caller requested via voice agent"). Stored as a note on the contact. ```bash # Retell sends the full call envelope. The args wrapper is required. curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/contacts/voice/unsubscribe" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "call": { "from_number": "+61412345678" }, "name": "unsubscribe_caller", "args": { "reason": "caller requested via voice agent" } }' ``` ### POST /contacts/bulk-delete Permanently delete up to 100 contacts in a single request. Returns a count of deleted records. Cannot be undone. **Scopes:** `contacts:delete` — _write_ Parameters: - `ids` (body, uuid[], required): Array of contact UUIDs to delete (max 100) ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/contacts/bulk-delete" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"ids":["uuid-1","uuid-2","uuid-3"]}' ``` --- ## Companies Manage business accounts (companies / organisations) in the CRM. Canonical path is /companies; the legacy /customers path remains supported. Companies can have multiple contacts linked and roll up to opportunities and activities. ### GET /companies List all companies with cursor-based pagination. Legacy alias: GET /customers. **Scopes:** `companies:read` Parameters: - `search` (query, string): Search by name, email, or phone - `limit` (query, number): Max results (1-100, default 25) - `cursor` (query, string): Cursor for next page ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/companies?limit=10" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### GET /companies/:id Retrieve a single company by ID. Legacy alias: GET /customers/:id. **Scopes:** `companies:read` Parameters: - `id` (path, uuid, required): Company ID ### POST /companies Create a new company. name is required. Set skip_automations: true to suppress the customer_created trigger -- recommended for historical imports. Legacy alias: POST /customers. **Scopes:** `companies:write` — _write_ Parameters: - `name` (body, string, required): Company / organisation name - `email` (body, string): Primary email - `phone` (body, string): Mobile number in E.164 format (e.g. +61412345678). MUST be a mobile number -- landlines will be rejected with a 400 error. Use the landline field for fixed-line/office numbers. - `landline` (body, string): Landline / office phone number in E.164 format (e.g. +61299991234) - `industry` (body, string): Industry sector - `website` (body, string): Website URL - `notes` (body, string): Notes - `metadata` (body, object): Custom field values as { field_id: value } pairs. Use GET /crm-settings to discover available custom fields. Reserved key: "quick_links" stores per-company Quick Link URLs as { : } -- define types via PATCH /company/crm-settings. UUID-shaped keys at metadata root are rejected (400). - `skip_automations` (body, boolean): Set true to suppress the customer_created trigger. Use for historical imports. Default false. ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/companies" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Acme Corp", "email": "info@acme.com", "phone": "+61412345678", "landline": "+61290001234" }' ``` ### PATCH /companies/:id Update an existing company. Only include fields you want to change. Every successful PATCH emits a field-level audit row to crm_field_change_log (viewable at /data/crm-logs with the crm_audit:read scope). Legacy alias: PATCH /customers/:id. **Scopes:** `companies:write` — _write_ Parameters: - `id` (path, uuid, required): Company ID - `name` (body, string): Company / organisation name - `email` (body, string): Primary email - `phone` (body, string): Mobile number in E.164 format. Landlines will be rejected -- use the landline field. - `landline` (body, string): Landline / office phone number in E.164 format. Set to null to clear. - `industry` (body, string): Industry sector - `website` (body, string): Website URL - `notes` (body, string): Notes - `metadata` (body, object): Custom field values as { field_id: value } pairs. Replaces entire metadata object -- read first with GET /companies/:id and merge locally. Reserved key: "quick_links" stores per-company Quick Link URLs as { : }. UUID-shaped keys at metadata root are rejected (400). ### DELETE /companies/:id Delete a company. Legacy alias: DELETE /customers/:id. **Scopes:** `companies:write` — _write_ Parameters: - `id` (path, uuid, required): Company ID ### POST /companies/search Search companies by name, email, or phone. Legacy alias: POST /customers/search. **Scopes:** `companies:read` Parameters: - `query` (body, string, required): Search query - `limit` (body, number): Max results (1-100) ### GET /companies/:id/contacts List all contacts linked to this company. Legacy alias: GET /customers/:id/contacts. **Scopes:** `companies:read`, `contacts:read` Parameters: - `id` (path, uuid, required): Company ID ### GET /companies/:id/opportunities List all opportunities for this company. Legacy alias: GET /customers/:id/deals (also still supported). **Scopes:** `companies:read`, `opportunities:read` Parameters: - `id` (path, uuid, required): Company ID ### GET /companies/:id/activities List all activities for this company. Legacy alias: GET /customers/:id/activities. **Scopes:** `companies:read`, `activities:read` Parameters: - `id` (path, uuid, required): Company ID ### POST /companies/bulk-create Create up to 100 companies in a single request. Built for historical migrations and bulk imports. Set skip_automations: true to suppress customer_created triggers across all records. Returns a created array and an errors array. Legacy alias: POST /customers/bulk-create. **Scopes:** `companies:write` — _write_ Parameters: - `records` (body, object[], required): Array of company objects (max 100). Each requires name plus any optional fields (email, phone, landline, industry, website, notes, metadata, etc.). - `skip_automations` (body, boolean): Set true to suppress customer_created triggers across all records. Strongly recommended for historical imports. ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/companies/bulk-create" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "skip_automations": true, "records": [ { "name": "Acme Corp", "email": "info@acme.com", "landline": "+61290001234" }, { "name": "Globex Inc", "email": "contact@globex.com" } ] }' ``` ### POST /companies/bulk-delete Permanently delete up to 100 companies in a single request. Returns a count of deleted records. Cannot be undone. Legacy alias: POST /customers/bulk-delete. **Scopes:** `companies:delete` — _write_ Parameters: - `ids` (body, uuid[], required): Array of company UUIDs to delete (max 100) ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/companies/bulk-delete" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"ids":["uuid-1","uuid-2"]}' ``` --- ## Opportunities Manage sales opportunities through your pipeline. Canonical path is /opportunities; the legacy /deals path remains supported. Sub-resources cover products, product costs, contacts, users, activities, tasks, work orders, pipeline moves, file/document/image/spreadsheet attachments, and invoices. ### GET /opportunities List all opportunities with cursor-based pagination. Supports search, status, contact, company, pipeline, stage, and date filters. Always includes pipeline placements. Response always includes read-only referral attribution fields: primary_referrer_contact_id (UUID of the most-recent referrer, maintained by a Postgres trigger) and primary_referrer_category (workspace category string). Use expand=referrer to inline the full referrer contact object. When using expand=products, payment_status on each product is stripped unless the caller has invoices:read scope. Legacy alias: GET /deals (same response shape). **Scopes:** `opportunities:read` Parameters: - `search` (query, string): Search by opportunity name, contact name/email/phone, or company name/email/phone - `status` (query, string): Filter by status (open, won, lost) - `contact_id` (query, uuid): Filter by primary contact - `customer_id` (query, uuid): Filter by company (formerly "customer_id" -- field name preserved for backward compatibility) - `assigned_to` (query, uuid): Filter by assigned user - `pipeline_id` (query, uuid): Filter by pipeline - `stage_id` (query, uuid): Filter by pipeline stage. Can be combined with pipeline_id or used alone. - `created_after` (query, string): ISO date filter - `created_before` (query, string): ISO date filter - `limit` (query, number): Max results (1-100, default 25) - `cursor` (query, string): Cursor for next page - `expand` (query, string): Expansions: contact, referrer, customer, products, assigned_users. "referrer" inlines the referrer contact for opportunities with primary_referrer_contact_id set. ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/opportunities?status=open&pipeline_id=UUID&stage_id=UUID&limit=25" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### GET /opportunities/:id Retrieve a single opportunity by ID with pipeline placements. Supports expansions. Legacy alias: GET /deals/:id. **Scopes:** `opportunities:read` Parameters: - `id` (path, uuid, required): Opportunity ID - `expand` (query, string): Expansions: contact, referrer, customer, products, assigned_users. "referrer" inlines the referrer contact (id, public_id, first_name, last_name, email, phone, job_title) when primary_referrer_contact_id is set. ### POST /opportunities Create a new opportunity. name is required. Set skip_automations: true to suppress the deal_created trigger -- recommended for historical imports to avoid spamming contacts with automation emails. Legacy alias: POST /deals. **Scopes:** `opportunities:write` — _write_ Parameters: - `name` (body, string, required): Opportunity name - `contact_id` (body, uuid): Primary contact ID - `customer_id` (body, uuid): Company ID (field name preserved for backward compatibility) - `status` (body, string): Status (open, won, lost) - `currency` (body, string): Currency code (default: AUD) - `lead_source` (body, string): Lead source - `tags` (body, object[]): Tags. Each tag is {name: string, color?: string} (hex color, default "#3b82f6"). Plain strings are also accepted and auto-converted. Example: [{"name":"hot-lead","color":"#ef4444"}] - `notes` (body, string): Notes - `metadata` (body, object): Custom field values as { field_id: value } pairs. Use GET /crm-settings to discover available custom fields. Reserved key: "quick_links" stores per-opportunity Quick Link URLs as { : } -- define types via PATCH /company/crm-settings. UUID-shaped keys at metadata root are rejected (400). - `skip_automations` (body, boolean): Set true to suppress the deal_created trigger. Use for historical imports so old opportunities do not trigger automation emails. Default false. ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/opportunities" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Website Redesign", "contact_id": "contact-uuid-...", "customer_id": "company-uuid-...", "currency": "AUD" }' ``` ### PATCH /opportunities/:id Update an existing opportunity. Only include fields you want to change. Every successful PATCH emits a field-level audit row to crm_field_change_log (viewable at /data/crm-logs with the crm_audit:read scope). Legacy alias: PATCH /deals/:id. **Scopes:** `opportunities:write` — _write_ Parameters: - `id` (path, uuid, required): Opportunity ID - `name` (body, string): Opportunity name - `contact_id` (body, uuid): Primary contact ID - `customer_id` (body, uuid): Company ID - `currency` (body, string): Currency code - `lead_source` (body, string): Lead source - `tags` (body, object[]): Tags. Each tag is {name: string, color?: string}. Plain strings are also accepted. Replaces entire tags array. - `notes` (body, string): Notes - `metadata` (body, object): Custom field values as { field_id: value } pairs. Replaces entire metadata object -- read first with GET /opportunities/:id and merge locally. Reserved key: "quick_links" stores per-opportunity Quick Link URLs as { : }. UUID-shaped keys at metadata root are rejected (400). ### DELETE /opportunities/:id Delete an opportunity. Legacy alias: DELETE /deals/:id. **Scopes:** `opportunities:write` — _write_ Parameters: - `id` (path, uuid, required): Opportunity ID ### POST /opportunities/search Search opportunities by name, contact name/email/phone, or company name/email/phone. Matches across opportunity name and linked contact/company fields. Legacy alias: POST /deals/search. **Scopes:** `opportunities:read` Parameters: - `query` (body, string, required): Search query - `limit` (body, number): Max results (1-100) ### POST /opportunities/:id/move Move an opportunity to a pipeline stage. If the opportunity is not in the pipeline, it will be added. If it is in a different pipeline, it will be moved. When the stage actually changes, stage_changed automations fire automatically. Use skip_automations=true to suppress all automation triggers, or pass skip_action_ids with an array of action UUIDs to suppress only specific actions within automations (the automation still runs and is logged, but those actions are bypassed and recorded in the run's skipped_action_ids field). Legacy alias: POST /deals/:id/move. **Scopes:** `opportunities:write` — _write_ Parameters: - `id` (path, uuid, required): Opportunity ID - `pipeline_id` (body, uuid, required): Target pipeline ID - `stage_id` (body, uuid, required): Target stage ID - `position` (body, number): Position within stage (default 0) - `skip_automations` (body, boolean): Set true to suppress ALL stage_changed automation triggers. Default false. Use skip_action_ids for per-action control. - `skip_action_ids` (body, uuid[]): Array of automation action UUIDs to suppress on this move. The automation still fires but these specific actions are bypassed and logged in the run record. Use list_automation_actions to find action IDs. ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/opportunities/opp-uuid/move" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "pipeline_id": "pipe-uuid", "stage_id": "stage-uuid", "skip_action_ids": ["action-uuid-to-skip"] }' ``` ### POST /opportunities/:id/add-card Add an opportunity card to a pipeline WITHOUT removing existing cards in other pipelines. Enables an opportunity to appear in multiple pipelines simultaneously (dual/multi placement). If the opportunity already has a card in the target pipeline, its stage is updated. Stage_changed automations fire on new placements or stage changes. Supports skip_automations and skip_action_ids for fine-grained automation control. Legacy alias: POST /deals/:id/add-card. **Scopes:** `opportunities:write` — _write_ Parameters: - `id` (path, uuid, required): Opportunity ID - `pipeline_id` (body, uuid, required): Target pipeline ID - `stage_id` (body, uuid, required): Target stage ID - `position` (body, number): Position within stage (default 0) - `skip_automations` (body, boolean): Set true to suppress ALL stage_changed automation triggers. Default false. - `skip_action_ids` (body, uuid[]): Array of automation action UUIDs to suppress on this card add. Automations still fire but these actions are bypassed and logged. ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/opportunities/opp-uuid/add-card" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "pipeline_id": "pipe-uuid", "stage_id": "stage-uuid" }' ``` ### GET /opportunities/:id/products List products attached to an opportunity with pricing and payment status. payment_status is only included in the response when the caller has invoices:read scope. Legacy alias: GET /deals/:id/products. **Scopes:** `opportunities:read` Parameters: - `id` (path, uuid, required): Opportunity ID ### POST /opportunities/:id/products Add a product to an opportunity. product_id is required. Optionally set payment_status (requires invoices:write scope). Valid payment_status values: unpaid (default), deposit_invoiced, invoiced, paid. Legacy alias: POST /deals/:id/products. **Scopes:** `opportunities:write` — _write_ Parameters: - `id` (path, uuid, required): Opportunity ID - `product_id` (body, uuid, required): Product ID to add - `quantity` (body, number): Quantity (default: 1) - `unit_price` (body, number): Override price - `discount_percent` (body, number): Discount % - `deposit_percent` (body, number): Deposit % - `payment_status` (body, string): Payment lifecycle stage. Requires invoices:write scope. Values: unpaid (default), deposit_invoiced, invoiced, paid. ### PATCH /opportunities/:id/products/:opportunityProductId Update an opportunity product. Supports quantity, price, discount, deposit, and payment_status. Setting payment_status requires invoices:write scope -- returns 403 otherwise. Valid payment_status values: unpaid, deposit_invoiced, invoiced, paid. Legacy alias: PATCH /deals/:id/products/:dealProductId. **Scopes:** `opportunities:write` — _write_ Parameters: - `id` (path, uuid, required): Opportunity ID - `opportunityProductId` (path, uuid, required): Opportunity product ID (legacy: dealProductId) - `quantity` (body, number): New quantity - `unit_price` (body, number): New unit price - `discount_percent` (body, number): New discount % - `deposit_percent` (body, number): New deposit % - `payment_status` (body, string): Payment lifecycle stage. Requires invoices:write scope. Values: unpaid, deposit_invoiced, invoiced, paid. ### DELETE /opportunities/:id/products/:opportunityProductId Remove a product from an opportunity. Legacy alias: DELETE /deals/:id/products/:dealProductId. **Scopes:** `opportunities:write` — _write_ Parameters: - `id` (path, uuid, required): Opportunity ID - `opportunityProductId` (path, uuid, required): Opportunity product ID ### POST /opportunities/:id/products/reorder Reorder products on an opportunity by providing an ordered array of opportunity product IDs. Legacy alias: POST /deals/:id/products/reorder. **Scopes:** `opportunities:write` — _write_ Parameters: - `id` (path, uuid, required): Opportunity ID - `product_ids` (body, string[], required): Ordered array of opportunity product UUIDs ### GET /opportunities/:id/product-costs/:opportunityProductId List costs for an opportunity product. Legacy alias: GET /deals/:id/product-costs/:dealProductId. **Scopes:** `opportunities:read` Parameters: - `id` (path, uuid, required): Opportunity ID - `opportunityProductId` (path, uuid, required): Opportunity product ID ### POST /opportunities/:id/product-costs/:opportunityProductId Create a cost entry for an opportunity product. Legacy alias: POST /deals/:id/product-costs/:dealProductId. **Scopes:** `opportunities:write` — _write_ Parameters: - `id` (path, uuid, required): Opportunity ID - `opportunityProductId` (path, uuid, required): Opportunity product ID - `label` (body, string): Cost label - `unit_cost` (body, number): Unit cost amount - `quantity` (body, number): Quantity - `currency` (body, string): Currency code - `supplier_id` (body, uuid): Supplier company ID - `supplier_product_id` (body, uuid): Supplier product ID ### PATCH /opportunities/:id/product-costs/:costId Update an opportunity product cost entry. Legacy alias: PATCH /deals/:id/product-costs/:costId. **Scopes:** `opportunities:write` — _write_ Parameters: - `id` (path, uuid, required): Opportunity ID - `costId` (path, uuid, required): Cost entry ID ### DELETE /opportunities/:id/product-costs/:costId Delete an opportunity product cost entry. Legacy alias: DELETE /deals/:id/product-costs/:costId. **Scopes:** `opportunities:write` — _write_ Parameters: - `id` (path, uuid, required): Opportunity ID - `costId` (path, uuid, required): Cost entry ID ### GET /opportunities/:id/contacts List all contacts associated with an opportunity (beyond the primary contact). Legacy alias: GET /deals/:id/contacts. **Scopes:** `opportunities:read`, `contacts:read` Parameters: - `id` (path, uuid, required): Opportunity ID ### POST /opportunities/:id/contacts/:contactId Associate an additional contact with an opportunity. Legacy alias: POST /deals/:id/contacts/:contactId. **Scopes:** `opportunities:write` — _write_ Parameters: - `id` (path, uuid, required): Opportunity ID - `contactId` (path, uuid, required): Contact ID ### DELETE /opportunities/:id/contacts/:contactId Remove a contact association from an opportunity. Legacy alias: DELETE /deals/:id/contacts/:contactId. **Scopes:** `opportunities:write` — _write_ Parameters: - `id` (path, uuid, required): Opportunity ID - `contactId` (path, uuid, required): Contact ID ### GET /opportunities/:id/users List users assigned to an opportunity. Legacy alias: GET /deals/:id/users. **Scopes:** `opportunities:read` Parameters: - `id` (path, uuid, required): Opportunity ID ### POST /opportunities/:id/users/:userId Assign a user to an opportunity. Legacy alias: POST /deals/:id/users/:userId. **Scopes:** `opportunities:write` — _write_ Parameters: - `id` (path, uuid, required): Opportunity ID - `userId` (path, uuid, required): User ID ### DELETE /opportunities/:id/users/:userId Remove a user assignment from an opportunity. Legacy alias: DELETE /deals/:id/users/:userId. **Scopes:** `opportunities:write` — _write_ Parameters: - `id` (path, uuid, required): Opportunity ID - `userId` (path, uuid, required): User ID ### GET /opportunities/:id/activities List all activities for an opportunity. Legacy alias: GET /deals/:id/activities. **Scopes:** `opportunities:read`, `activities:read` Parameters: - `id` (path, uuid, required): Opportunity ID ### GET /opportunities/:id/tasks List all tasks for an opportunity. Legacy alias: GET /deals/:id/tasks. **Scopes:** `opportunities:read`, `tasks:read` Parameters: - `id` (path, uuid, required): Opportunity ID ### GET /opportunities/:id/work-orders List all work orders for an opportunity. Legacy alias: GET /deals/:id/work-orders. **Scopes:** `opportunities:read` Parameters: - `id` (path, uuid, required): Opportunity ID ### GET /opportunities/:id/files List files attached to an opportunity. Returns file metadata for every file linked to the opportunity (any file type accepted by /files -- documents, images, secure). Legacy alias: GET /deals/:id/files. Matching MCP tool: list_opportunity_files. **Scopes:** `opportunities:read`, `files:read` Parameters: - `id` (path, uuid, required): Opportunity ID ### POST /opportunities/:id/files/:fileId Attach an existing file to an opportunity. The file must already exist in the workspace (upload via POST /files first if needed). Legacy alias: POST /deals/:id/files/:fileId. Matching MCP tool: add_opportunity_file. **Scopes:** `opportunities:write`, `files:read` — _write_ Parameters: - `id` (path, uuid, required): Opportunity ID - `fileId` (path, uuid, required): File ID to attach ### DELETE /opportunities/:id/files/:fileId Remove a file attachment from an opportunity. Detaches the link only -- the file itself is not deleted (use DELETE /files/:id to fully delete the file). Legacy alias: DELETE /deals/:id/files/:fileId. Matching MCP tool: remove_opportunity_file. **Scopes:** `opportunities:write` — _write_ Parameters: - `id` (path, uuid, required): Opportunity ID - `fileId` (path, uuid, required): File ID to detach ### GET /opportunities/:id/documents List CRM documents attached to an opportunity. Returns generated documents (proposals, contracts, etc.) linked to the opportunity. Legacy alias: GET /deals/:id/documents. Matching MCP tool: list_opportunity_documents. **Scopes:** `opportunities:read`, `documents:read` Parameters: - `id` (path, uuid, required): Opportunity ID ### POST /opportunities/:id/documents/:documentId Attach an existing CRM document to an opportunity. Legacy alias: POST /deals/:id/documents/:documentId. Matching MCP tool: add_opportunity_document. **Scopes:** `opportunities:write`, `documents:read` — _write_ Parameters: - `id` (path, uuid, required): Opportunity ID - `documentId` (path, uuid, required): Document ID to attach ### DELETE /opportunities/:id/documents/:documentId Remove a document attachment from an opportunity. Detaches the link only -- the document itself is not deleted. Legacy alias: DELETE /deals/:id/documents/:documentId. Matching MCP tool: remove_opportunity_document. **Scopes:** `opportunities:write` — _write_ Parameters: - `id` (path, uuid, required): Opportunity ID - `documentId` (path, uuid, required): Document ID to detach ### GET /opportunities/:id/images List image files attached to an opportunity. Filters /files attachments to type=image. Legacy alias: GET /deals/:id/images. Matching MCP tool: list_opportunity_images. **Scopes:** `opportunities:read`, `files:read` Parameters: - `id` (path, uuid, required): Opportunity ID ### POST /opportunities/:id/images/:imageId Attach an existing image file (type=image) to an opportunity. Legacy alias: POST /deals/:id/images/:imageId. Matching MCP tool: add_opportunity_image. **Scopes:** `opportunities:write`, `files:read` — _write_ Parameters: - `id` (path, uuid, required): Opportunity ID - `imageId` (path, uuid, required): Image file ID to attach ### DELETE /opportunities/:id/images/:imageId Remove an image attachment from an opportunity. Detaches the link only -- the image file itself is not deleted. Legacy alias: DELETE /deals/:id/images/:imageId. Matching MCP tool: remove_opportunity_image. **Scopes:** `opportunities:write` — _write_ Parameters: - `id` (path, uuid, required): Opportunity ID - `imageId` (path, uuid, required): Image file ID to detach ### GET /opportunities/:id/spreadsheets List spreadsheets attached to an opportunity. Returns metadata for every spreadsheet linked to the opportunity (including spreadsheets auto-created by form submissions where the form is linked to the opportunity). Legacy alias: GET /deals/:id/spreadsheets. Matching MCP tool: list_opportunity_spreadsheets. **Scopes:** `opportunities:read`, `spreadsheets:read` Parameters: - `id` (path, uuid, required): Opportunity ID ### POST /opportunities/:id/spreadsheets/:spreadsheetId Attach an existing spreadsheet to an opportunity. Legacy alias: POST /deals/:id/spreadsheets/:spreadsheetId. Matching MCP tool: add_opportunity_spreadsheet. **Scopes:** `opportunities:write`, `spreadsheets:read` — _write_ Parameters: - `id` (path, uuid, required): Opportunity ID - `spreadsheetId` (path, uuid, required): Spreadsheet ID to attach ### DELETE /opportunities/:id/spreadsheets/:spreadsheetId Remove a spreadsheet attachment from an opportunity. Detaches the link only -- the spreadsheet itself is not deleted. Legacy alias: DELETE /deals/:id/spreadsheets/:spreadsheetId. Matching MCP tool: remove_opportunity_spreadsheet. **Scopes:** `opportunities:write` — _write_ Parameters: - `id` (path, uuid, required): Opportunity ID - `spreadsheetId` (path, uuid, required): Spreadsheet ID to detach ### GET /opportunities/:id/invoices List invoices linked to an opportunity. Invoices are read-only here -- this endpoint surfaces every invoice that references the opportunity via trustpager_deal_id, so AI agents and integrations can answer "what has this customer been billed for?" without needing the invoices:write scope. Requires the invoices:read scope. Legacy alias: GET /deals/:id/invoices. Matching MCP tool: list_opportunity_invoices. **Scopes:** `opportunities:read`, `invoices:read` Parameters: - `id` (path, uuid, required): Opportunity ID ### POST /opportunities/bulk-create Create up to 100 opportunities in a single request. Built for historical migrations and bulk data loads. Top-level pipeline_id/stage_id act as defaults inherited by each record unless overridden. Set skip_automations: true (strongly recommended for imports) to suppress deal_created triggers across all records. Returns a created array and an errors array so partial successes can be recovered from without duplicating work on retry. Legacy alias: POST /deals/bulk-create. **Scopes:** `opportunities:write` — _write_ Parameters: - `records` (body, object[], required): Array of opportunity objects (max 100). Each requires name plus any optional fields (contact_id, customer_id, pipeline_id, stage_id, status, value, notes, tags, metadata, etc.). - `pipeline_id` (body, uuid): Default pipeline UUID applied to every record unless the record sets its own. - `stage_id` (body, uuid): Default stage UUID applied to every record unless the record sets its own. - `skip_automations` (body, boolean): Set true to suppress deal_created triggers across all records. Strongly recommended for historical imports. ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/opportunities/bulk-create" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "pipeline_id": "pipe-uuid", "stage_id": "stage-uuid", "skip_automations": true, "records": [ { "name": "Opportunity A", "contact_id": "contact-uuid-1", "value": 1500 }, { "name": "Opportunity B", "contact_id": "contact-uuid-2", "value": 2000 } ] }' ``` ### POST /opportunities/bulk-delete Permanently delete up to 100 opportunities in a single request. Each opportunity is cascade-deleted including its products, pipeline placements, contacts, and users. Returns a count of deleted records and any IDs that failed. Cannot be undone. Legacy alias: POST /deals/bulk-delete. **Scopes:** `opportunities:delete` — _write_ Parameters: - `ids` (body, uuid[], required): Array of opportunity UUIDs to delete (max 100) ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/opportunities/bulk-delete" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"ids":["uuid-1","uuid-2","uuid-3"]}' ``` ### POST /opportunities/bulk-move Move up to 100 opportunities to a pipeline stage in a single request. Set skip_automations=true to suppress all stage_changed automation triggers (recommended for bulk moves to avoid flooding contacts). Alternatively, pass skip_action_ids to suppress only specific automation actions across all records in the batch. Returns a count of moved records and any IDs that failed. Legacy alias: POST /deals/bulk-move. **Scopes:** `opportunities:write` — _write_ Parameters: - `ids` (body, uuid[], required): Array of opportunity UUIDs to move (max 100) - `pipeline_id` (body, uuid, required): Target pipeline UUID - `stage_id` (body, uuid, required): Target stage UUID within the pipeline - `skip_automations` (body, boolean): Suppress ALL stage_changed automation triggers (default false). Strongly recommended for bulk moves. - `skip_action_ids` (body, uuid[]): Array of automation action UUIDs to suppress across all records in this bulk move. Automations still fire but these actions are bypassed on every record. ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/opportunities/bulk-move" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"ids":["uuid-1","uuid-2"],"pipeline_id":"pipe-uuid","stage_id":"stage-uuid","skip_action_ids":["email-action-uuid"]}' ``` --- ## Pipelines Manage sales pipelines and their stages. Opportunities move through pipeline stages to track progress. ### GET /pipelines List all pipelines. **Scopes:** `pipelines:read` ### GET /pipelines/:id Retrieve a pipeline with its stages. **Scopes:** `pipelines:read` Parameters: - `id` (path, uuid, required): Pipeline ID ### POST /pipelines Create a new pipeline. name is required. **Scopes:** `pipelines:write` — _write_ Parameters: - `name` (body, string, required): Pipeline name ### PATCH /pipelines/:id Update a pipeline. **Scopes:** `pipelines:write` — _write_ Parameters: - `id` (path, uuid, required): Pipeline ID ### DELETE /pipelines/:id Delete a pipeline. **Scopes:** `pipelines:write` — _write_ Parameters: - `id` (path, uuid, required): Pipeline ID ### GET /pipelines/:id/stages List stages for a pipeline, ordered by position. Each stage includes a deal_count field (number of opportunities currently in that stage -- field name preserved for backward compatibility) so Kanban column headers can render counts without a second query. **Scopes:** `pipelines:read` Parameters: - `id` (path, uuid, required): Pipeline ID ### POST /pipelines/:id/stages Add a stage to a pipeline. **Scopes:** `pipelines:write` — _write_ Parameters: - `id` (path, uuid, required): Pipeline ID - `name` (body, string, required): Stage name ### PATCH /pipelines/:id/stages/:stageId Update a pipeline stage. **Scopes:** `pipelines:write` — _write_ Parameters: - `id` (path, uuid, required): Pipeline ID - `stageId` (path, uuid, required): Stage ID ### DELETE /pipelines/:id/stages/:stageId Delete a pipeline stage. **Scopes:** `pipelines:write` — _write_ Parameters: - `id` (path, uuid, required): Pipeline ID - `stageId` (path, uuid, required): Stage ID ### GET /pipelines/:id/deals DEPRECATED -- prefer GET /opportunities?pipeline_id=X (legacy alias: GET /deals?pipeline_id=X). The /opportunities endpoint returns the same data plus full pipeline placements on every opportunity and supports additional filters (stage_id, status, search, pagination). This endpoint now also attaches placements for backward compatibility but may be removed in a future version. **Scopes:** `pipelines:read`, `opportunities:read` Parameters: - `id` (path, uuid, required): Pipeline ID ### GET /pipelines/:id/summary Get pipeline summary with opportunity counts and values per stage. **Scopes:** `pipelines:read` Parameters: - `id` (path, uuid, required): Pipeline ID --- ## Products Manage products and services that can be added to deals. ### GET /products List all products. **Scopes:** `products:read` ### GET /products/:id Retrieve a product by ID. **Scopes:** `products:read` Parameters: - `id` (path, uuid, required): Product ID ### POST /products Create a product. name and price are required. **Scopes:** `products:write` — _write_ Parameters: - `name` (body, string, required): Product name - `price` (body, number, required): Unit price - `currency` (body, string): Currency (default: AUD) - `sku` (body, string): SKU code - `images` (body, string[]): Array of product image URLs - `is_active` (body, boolean): Whether the product is active (default: true) - `work_order_config` (body, object): Product-specific work order field configuration (fields + statuses). Null = use company default. ### PATCH /products/:id Update a product. Writable fields include: name, sku, price, currency, category, description, unit, benefits, deposit_percent, default_expiry_days, images, track_inventory, track_batches, low_stock_threshold, is_active, metadata. **Scopes:** `products:write` — _write_ Parameters: - `id` (path, uuid, required): Product ID - `images` (body, string[]): Array of product image URLs (field name is "images", not "image_url") - `work_order_config` (body, object): Product-specific work order field configuration. Set to null to revert to company defaults. ### DELETE /products/:id Delete a product. **Scopes:** `products:write` — _write_ Parameters: - `id` (path, uuid, required): Product ID ### GET /products/:id/costs List cost entries for a product. **Scopes:** `products:read` Parameters: - `id` (path, uuid, required): Product ID ### POST /products/:id/costs Create a cost entry for a product. **Scopes:** `products:write` — _write_ Parameters: - `id` (path, uuid, required): Product ID - `label` (body, string): Cost label - `cost_price` (body, number): Cost price - `quantity` (body, number): Quantity - `supplier_id` (body, uuid): Supplier customer ID - `supplier_product_id` (body, uuid): Supplier product ID ### PATCH /products/:id/costs/:costId Update a product cost entry. **Scopes:** `products:write` — _write_ Parameters: - `id` (path, uuid, required): Product ID - `costId` (path, uuid, required): Cost entry ID ### DELETE /products/:id/costs/:costId Delete a product cost entry. **Scopes:** `products:write` — _write_ Parameters: - `id` (path, uuid, required): Product ID - `costId` (path, uuid, required): Cost entry ID --- ## Supplier Catalog Manage supplier product catalogues for cost tracking on deals. The endpoint path is /supplier-catalog. ### GET /supplier-catalog List all supplier products in the catalog. **Scopes:** `products:read` Parameters: - `supplier_id` (query, uuid): Filter by supplier UUID - `product_id` (query, uuid): Filter by product UUID - `is_active` (query, boolean): Filter by active status ### GET /supplier-catalog/:id Retrieve a supplier product from the catalog. **Scopes:** `products:read` Parameters: - `id` (path, uuid, required): Supplier product ID ### POST /supplier-catalog Create a supplier product in the catalog. supplier_id and supplier_price are required. **Scopes:** `products:write` — _write_ Parameters: - `supplier_id` (body, uuid, required): Supplier (customer) UUID - `supplier_price` (body, number, required): Supplier price - `product_id` (body, uuid): Link to existing product UUID - `product_name` (body, string): New product name if not linking to existing - `supplier_sku` (body, string): Supplier SKU - `lead_time_days` (body, number): Lead time in days - `minimum_order_qty` (body, number): Minimum order quantity ### PATCH /supplier-catalog/:id Update a supplier product in the catalog. **Scopes:** `products:write` — _write_ Parameters: - `id` (path, uuid, required): Supplier product ID ### DELETE /supplier-catalog/:id Delete a supplier product from the catalog. **Scopes:** `products:write` — _write_ Parameters: - `id` (path, uuid, required): Supplier product ID --- ## Activities Log and manage CRM activities (calls, meetings, notes) linked to contacts, deals, and customers. ### GET /activities List all activities with filters. **Scopes:** `activities:read` ### GET /activities/:id Retrieve an activity by ID. **Scopes:** `activities:read` Parameters: - `id` (path, uuid, required): Activity ID ### POST /activities Create an activity. Set activity_type to "call", "meeting", or "note". Link to a contact, deal, or customer. **Scopes:** `activities:write` — _write_ Parameters: - `activity_type` (body, string, required): Type: call, meeting, or note - `subject` (body, string, required): Activity subject - `description` (body, string): Activity details/notes - `contact_id` (body, uuid): Contact ID - `deal_id` (body, uuid): Deal ID - `customer_id` (body, uuid): Customer ID ### PATCH /activities/:id Update an activity. **Scopes:** `activities:write` — _write_ Parameters: - `id` (path, uuid, required): Activity ID - `subject` (body, string): Activity subject - `description` (body, string): Activity details ### DELETE /activities/:id Delete an activity. **Scopes:** `activities:write` — _write_ Parameters: - `id` (path, uuid, required): Activity ID --- ## Tasks Create, assign, and manage tasks linked to deals and contacts. ### GET /tasks List all tasks with filters. **Scopes:** `tasks:read` ### GET /tasks/:id Retrieve a task. **Scopes:** `tasks:read` Parameters: - `id` (path, uuid, required): Task ID ### POST /tasks Create a task. title is required. **Scopes:** `tasks:write` — _write_ Parameters: - `title` (body, string, required): Task title - `deal_id` (body, uuid): Link to deal - `assigned_to` (body, uuid): Assign to user ID ### PATCH /tasks/:id Update a task. **Scopes:** `tasks:write` — _write_ Parameters: - `id` (path, uuid, required): Task ID ### DELETE /tasks/:id Delete a task. **Scopes:** `tasks:write` — _write_ Parameters: - `id` (path, uuid, required): Task ID ### GET /tasks/categories List task categories. **Scopes:** `tasks:read` ### POST /tasks/categories Create a task category. **Scopes:** `tasks:write` — _write_ Parameters: - `name` (body, string, required): Category name - `color` (body, string): Category color ### PATCH /tasks/categories/:id Update a task category. **Scopes:** `tasks:write` — _write_ Parameters: - `id` (path, uuid, required): Category ID ### DELETE /tasks/categories/:id Delete a task category. **Scopes:** `tasks:write` — _write_ Parameters: - `id` (path, uuid, required): Category ID ### POST /tasks/reorder Reorder tasks by providing an array of task IDs in desired order. **Scopes:** `tasks:write` — _write_ Parameters: - `order` (body, uuid[], required): Array of task IDs in desired order --- ## Work Orders Manage work orders linked to deals for tracking project execution. Work orders appear on the CRM Calendar, support team assignment, and scheduled dates. ### GET /work-orders List all work orders. Filter by deal_product_id, status_id, assigned_to (user UUID), or schedule_date (ISO date, exact match). **Scopes:** `work-orders:read` Parameters: - `deal_product_id` (query, uuid): Filter by deal product UUID - `status_id` (query, uuid): Filter by work order status UUID - `assigned_to` (query, uuid): Filter by assigned team member user UUID - `schedule_date` (query, string): Filter by scheduled date (ISO date, e.g. "2026-04-25"). Exact match. - `limit` (query, number): Items per page (default 25, max 100) - `after` (query, string): Cursor for pagination ### GET /work-orders/:id Retrieve a single work order. Response includes schedule_date (ISO date), schedule_time (HH:MM:SS or null for all-day), and assigned_to (user UUID or null). **Scopes:** `work-orders:read` Parameters: - `id` (path, uuid, required): Work order ID ### POST /work-orders Create a work order. Pass "status" (string label, e.g. "Pending") to resolve a status by name, or "status_id" (UUID) directly. "status" performs a case-insensitive match against crm_work_order_statuses labels for the company. Use schedule_date + schedule_time to control when the work order appears on the calendar, and assigned_to to assign a team member. **Scopes:** `work-orders:write` — _write_ Parameters: - `deal_product_id` (body, uuid, required): Deal product UUID (required) - `status` (body, string): Status label (e.g. "Pending"). Resolved case-insensitively to a status_id UUID. Use instead of status_id for human-readable input. - `status_id` (body, uuid): Status UUID. Bypasses label resolution. Use status or status_id, not both. - `schedule_date` (body, string): ISO date (e.g. "2026-04-25"). When the work order is scheduled. Controls calendar placement. Defaults to today. - `schedule_time` (body, string): Wall-clock time in HH:MM:SS format (e.g. "09:00:00"). Omit for an all-day work order. Combined with schedule_date for a specific appointment time. - `assigned_to` (body, uuid): User UUID of the team member assigned to this work order. Must be a member of the company. - `data` (body, object): Work order field data (JSON object) - `sort_order` (body, number): Sort order for display ### PATCH /work-orders/:id Update a work order. Pass "status" (string label) to resolve by name, or "status_id" (UUID) directly. Set assigned_to to null to unassign. Set schedule_time to null to clear a timed slot back to all-day. **Scopes:** `work-orders:write` — _write_ Parameters: - `id` (path, uuid, required): Work order ID - `status` (body, string): Status label (e.g. "Complete"). Resolved case-insensitively to a status_id UUID. - `status_id` (body, uuid): Status UUID. Bypasses label resolution. - `schedule_date` (body, string): ISO date (e.g. "2026-05-01"). Updates the calendar scheduling date. - `schedule_time` (body, string): Wall-clock time in HH:MM:SS format (e.g. "14:00:00"). Pass null to revert to all-day. - `assigned_to` (body, uuid): User UUID of the assigned team member. Pass null to unassign. - `data` (body, object): Work order field data - `sort_order` (body, number): Sort order for display ### DELETE /work-orders/:id Delete a work order. **Scopes:** `work-orders:write` — _write_ Parameters: - `id` (path, uuid, required): Work order ID ### POST /work-orders/send-work-status Send a PIN-protected Work Status Portal link to a client via email. The client receives a unique link where they can view the progress of all work orders on the deal. Requires the deal to have work orders and the company to have email configured. Returns success, portal_id, and token. **Scopes:** `work-orders:write` — _write_ Parameters: - `deal_id` (body, uuid, required): Deal UUID. The portal shows all work orders for this deal. - `recipient_email` (body, string, required): Client email address - `recipient_name` (body, string, required): Client display name - `personal_message` (body, string): Optional personal message included in the email - `expires_in_days` (body, number): Days until the portal link expires (default 30) --- ## Automations Create and manage workflow automations. Includes sub-resources for triggers, actions, and execution runs. Supports enable/disable and manual triggering. ### GET /automations List all automations for the company. **Scopes:** `automations:read` Parameters: - `limit` (query, number): Max results (1-100) - `cursor` (query, string): Cursor for next page ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/automations?limit=10" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### GET /automations/:id Retrieve an automation with its triggers and actions. **Scopes:** `automations:read` Parameters: - `id` (path, uuid, required): Automation ID ### POST /automations Create a new automation. name and trigger_type are required. For stage_changed automations, pass stage_id directly on the automation -- this is the single source of truth for stage matching (do not use automations_triggers for stage_changed). Note: trigger_type "form_submitted" is deprecated and rejected -- use "form_completed" instead. **Scopes:** `automations:write` — _write_ Parameters: - `name` (body, string, required): Automation name - `trigger_type` (body, string, required): Trigger type. Common values: stage_changed, form_completed, call_analyzed, sms_received, email_received, checkout_completed. CRM entity triggers (fired by Portal API on CRUD): contact_created, contact_updated, customer_created, customer_updated, deal_created, deal_updated. ENTITY AXIS RULES: contact_type lives on contacts (use contact_created/updated); account_type lives on customers (use customer_created/updated); opportunity_type lives on deals (use deal_created/updated). Update triggers include _changed_fields and _previous_values in trigger data for field-level condition matching. ALL entity triggers also include _trigger_entity_kind ("contact"|"customer"|"deal") and _trigger_entity_id (the UUID of the triggering row) so CRM Integration actions can match the exact record by id without needing email/phone/name extraction. DEPRECATED: "form_submitted" is rejected with a 400 -- use "form_completed". - `stage_id` (body, uuid): Pipeline stage UUID. Required when trigger_type is "stage_changed". Single source of truth for stage matching. - `description` (body, string): Description - `enabled` (body, boolean): Whether enabled (default: false) - `priority` (body, number): Execution priority - `conditions` (body, object): Conditions that must ALL pass (AND logic) for the automation to fire. Evaluated AFTER CRM enrichment, so CRM fields (tags, deal value, lead source, contact email) are available for ALL trigger types. When conditions are not met the run status is "skipped". Operators: eq, neq, exists, not_exists, gt, gte, lt, lte, contains (tag array), not_contains, contains_any, contains_text, in. Example: { "tags": { "contains": "VIP" }, "deal.value": { "gte": 5000 }, "lead_source": { "in": ["Referral", "Website"] } } ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/automations" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "VIP Lead Welcome", "trigger_type": "stage_changed", "stage_id": "stage-uuid", "conditions": { "tags": { "contains": "VIP" }, "deal.value": { "gte": 5000 }, "lead_source": { "in": ["Referral", "Website"] } }, "description": "Send welcome email to VIP leads only" }' ``` ### PATCH /automations/:id Update an automation. Same writable fields as POST, including conditions. Pass conditions: null to remove all conditions from an automation. **Scopes:** `automations:write` — _write_ Parameters: - `id` (path, uuid, required): Automation ID ### DELETE /automations/:id Delete an automation and all its triggers/actions. **Scopes:** `automations:write` — _write_ Parameters: - `id` (path, uuid, required): Automation ID ### POST /automations/:id/enable Enable an automation. **Scopes:** `automations:write` — _write_ Parameters: - `id` (path, uuid, required): Automation ID ### POST /automations/:id/disable Disable an automation. **Scopes:** `automations:write` — _write_ Parameters: - `id` (path, uuid, required): Automation ID ### POST /automations/:id/trigger Manually trigger an automation with optional test data. Returns a normalized run response with inline action_results[]. **Scopes:** `automations:trigger` — _write_ Parameters: - `id` (path, uuid, required): Automation ID - `trigger_data` (body, object): Test data to pass to the automation ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/automations/auto-uuid/trigger" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "trigger_data": { "deal_id": "deal-uuid", "contact_id": "contact-uuid" } }' ``` ### GET /automations/:id/runs List execution runs for an automation. Run status values: completed, failed, running, skipped. Status "skipped" means conditions were evaluated but not met -- the automation did not execute any actions. **Scopes:** `automations:read` Parameters: - `id` (path, uuid, required): Automation ID ### GET /automations/runs/:runId Get details of a specific run. Returns normalized shape: run_id, status, actions_total, actions_executed, actions_failed, actions_skipped, action_results[] (per-step outcomes with action_type, status, request, response, error, duration_ms), truncated flag. **Scopes:** `automations:read` Parameters: - `runId` (path, uuid, required): Run ID ### GET /automations/:id/triggers List triggers for an automation. **Scopes:** `automations:read` Parameters: - `id` (path, uuid, required): Automation ID ### POST /automations/:id/triggers Add a trigger to an automation. source_type auto-populates from the parent automation trigger_type when omitted -- you only need to pass source_type to override the default. For stage_changed automations, use update_automation with stage_id instead -- adding a trigger row for stage_changed is rejected with a 400. **Scopes:** `automations:write` — _write_ Parameters: - `id` (path, uuid, required): Automation ID - `source_type` (body, string): Trigger source type. Optional -- auto-populates from the parent automation trigger_type. Canonical defaults: deal_created/contact_*/customer_*/api/trustpager_signup -> "any"; form_completed/form_sent -> "form_template"; sms_received -> "sms"; email_received/email_bounced -> "email"; call_* -> "voice_agent"; document_sent -> "document_template"; signature_* -> "signature_template"; webhook/zapier/typeform/generic_webhook -> "webhook"; xero_*/zoom_*/calcom_*/facebook_lead_ad/booking_* -> "platform_integration". - `source_id` (body, string): Trigger source ID (optional). For form_completed: form_template UUID. For sms_received: phone_number UUID. For call_*: voice_agent ID. Omit to match any source of that type. - `config` (body, object): Trigger configuration (varies by type). For form_completed: { form_template_id }. For sms_received/email_received: no config needed. ### PATCH /automations/:id/triggers/:triggerId Update a trigger. **Scopes:** `automations:write` — _write_ Parameters: - `id` (path, uuid, required): Automation ID - `triggerId` (path, uuid, required): Trigger ID ### DELETE /automations/:id/triggers/:triggerId Delete a trigger from an automation. **Scopes:** `automations:write` — _write_ Parameters: - `id` (path, uuid, required): Automation ID - `triggerId` (path, uuid, required): Trigger ID ### GET /automations/:id/actions List actions for an automation, ordered by sequence. **Scopes:** `automations:read` Parameters: - `id` (path, uuid, required): Automation ID ### POST /automations/:id/actions Add an action to an automation. action_type and sequence are required. **Scopes:** `automations:write` — _write_ Parameters: - `id` (path, uuid, required): Automation ID - `action_type` (body, string, required): Action type (send_custom_email, send_gmail_email, send_sms, voice_outbound_call, call_webhook, add_tasks, create_lead, move_deal, attach_to_event_queue, remove_from_event_queue, apply_tags, remove_tags, assign_user, set_custom_field, send_document, send_for_signing, send_form, slack_send_message, trigger_automation, etc.) - `sequence` (body, number, required): Execution order - `config` (body, object): Action configuration (varies by action_type). Key configs: send_custom_email: { greeting, customMessage, showReplyButton?, recipient_target?, replyEmail? } send_gmail_email: { subject, body, recipient_target, sender_mode?, email_config_id?, attachment_ids? } -- body accepts plain text or rich HTML. To embed a clickable image: Alt. sender_mode: "company" (workspace Gmail from /settings/email) or "assignee" (deal primary assignee personal Gmail). recipient_target: "contact", "account_customer", or "account_supplier". email_config_id: UUID of a gmail-provider email_config to pin which alias sends (omit to use default). attachment_ids: array of crm_documents UUIDs to attach -- resolved server-side from the workspace file library, max 25 MB total. subject and body support {{variable}} placeholders. Gmail signature is auto-appended. send_sms: { phone_number_id, message_body, recipient_target?, custom_recipient_phone? } -- recipient_target: "contact" (deal primary contact, default), "account_customer", or "custom". For a fixed external recipient (e.g. forward inbound SMS to a staff mobile that is not a TP user), set recipient_target: "custom" AND provide custom_recipient_phone in E.164 format. to_number_source is a legacy alias for recipient_target -- both work. voice_outbound_call: { voice_agent_outbound_config_id, recipient_target?, variable_mappings?, respect_business_hours? } add_tasks: { tasks: [{ title, category?, due_offset_days? }] } call_webhook: { url, method? } create_lead: { pipeline_id, stage_id } move_deal: { pipeline_id, stage_id, pipeline_name?, stage_name? } - moves the triggering deal to the specified stage apply_tags: { tags: [{ name, color? }] } - merges tags onto the deal. Deduplicates by name. Color falls back to tag palette or #6b7280. remove_tags: { tags: [{ name }] } - removes tags from the deal by name (case-insensitive). assign_user: { user_id } - assigns a team member as primary deal owner. Aliases: assign_deal_user, set_deal_owner. Demotes any existing primary assignee. Use GET /company/users to find user UUIDs. set_custom_field (UI: "Set CRM Field") - writes one or more CRM fields in a single action. Multi-write shape: { writes: [{ target_entity: "contact"|"customer"|"deal", crm_variable: string, value: any, mode?: "write"|"overwrite" }] }. crm_variable is the full dot-path e.g. "contact.relationship_started_at", "account.tax_number", "deal.actual_close_date", "contact.metadata.cf_abc123". mode "write" only sets the field when currently empty. Special value tokens: {{today}} = YYYY-MM-DD in company timezone, {{now}} = full ISO datetime. Legacy single-field shape { target_entity, crm_variable, value, mode } and older { target_entity, field_id, value } still work. Newly writable built-in fields: contact.relationship_started_at, contact.landline, contact.contact_type, contact.timezone, account.relationship_started_at, account.landline, account.account_type, account.timezone, deal.actual_close_date. CROSS-ENTITY WRITES: when the trigger fires on one entity (e.g. contact_updated) but you need to write to a different entity (e.g. deal.metadata.X), add a CRM Integration block on the automation with mode "find" or mode "update" and include "id" in find_match_fields or update_contact_match_fields. The engine resolves the linked deal/customer via the trigger entity id automatically -- no email/phone matching needed. ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/automations/auto-uuid/actions" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "action_type": "send_gmail_email", "sequence": 1, "config": { "subject": "Following up on {{deal.name}}", "body": "Hi {{contact.first_name}},\n\nJust wanted to follow up. Let me know if you have any questions.\n\nBest regards", "recipient_target": "contact", "sender_mode": "company" } }' ``` ### POST /automations/:id/actions/reorder Reorder actions within an automation. **Scopes:** `automations:write` — _write_ Parameters: - `id` (path, uuid, required): Automation ID - `action_ids` (body, string[], required): Ordered array of action UUIDs ### PATCH /automations/:id/actions/:actionId Update an action. **Scopes:** `automations:write` — _write_ Parameters: - `id` (path, uuid, required): Automation ID - `actionId` (path, uuid, required): Action ID ### DELETE /automations/:id/actions/:actionId Delete an action from an automation. **Scopes:** `automations:write` — _write_ Parameters: - `id` (path, uuid, required): Automation ID - `actionId` (path, uuid, required): Action ID ### POST /automations/:id/actions/:actionId/execute Execute a single action in isolation, skipping all other actions in the chain. Reuses the full executor pipeline: variable substitution, recipient resolution, business-hours scheduling. Use for testing one action against real config (e.g. voice_outbound_call dialling a number) or retrying a single failing step without re-firing the entire automation. Cost is the same as the underlying action (e.g. voice_agent_minute credits for voice_outbound_call). Requires automations-trigger:trigger scope. **Scopes:** `automations:write` — _write_ Parameters: - `id` (path, uuid, required): Automation ID - `actionId` (path, uuid, required): Action ID - `trigger_data` (body, object): Optional trigger context for variable substitution. Provide fields the action references via {{variable}} placeholders -- e.g. { contact: { first_name: "Jane", phone: "+61400000001" } }. ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/automations/auto-uuid/actions/action-uuid/execute" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "trigger_data": { "contact": { "first_name": "Jane", "phone": "+61400000001" } } }' ``` --- ## Event Queues Manage event queues for sequenced multi-step workflows (drip campaigns, follow-up sequences). ### GET /event-queues List all event queues. **Scopes:** `automations:read` ### GET /event-queues/:id Retrieve an event queue with its steps. **Scopes:** `automations:read` Parameters: - `id` (path, uuid, required): Event queue ID ### POST /event-queues Create an event queue. **Scopes:** `automations:write` — _write_ Parameters: - `name` (body, string, required): Queue name ### PATCH /event-queues/:id Update an event queue. **Scopes:** `automations:write` — _write_ Parameters: - `id` (path, uuid, required): Event queue ID ### DELETE /event-queues/:id Delete an event queue. **Scopes:** `automations:write` — _write_ Parameters: - `id` (path, uuid, required): Event queue ID ### POST /event-queues/:id/steps Add a step to an event queue. **Scopes:** `automations:write` — _write_ Parameters: - `id` (path, uuid, required): Event queue ID ### PATCH /event-queues/:id/steps/:stepId Update an event queue step. **Scopes:** `automations:write` — _write_ Parameters: - `id` (path, uuid, required): Event queue ID - `stepId` (path, uuid, required): Step ID ### DELETE /event-queues/:id/steps/:stepId Delete an event queue step. **Scopes:** `automations:write` — _write_ Parameters: - `id` (path, uuid, required): Event queue ID - `stepId` (path, uuid, required): Step ID ### GET /event-queues/:id/enrollments List enrollment timer tasks for an event queue. Shows all scheduled step executions with their status, scheduled_for time, enrollment_time (the anchor time used for delay calculation when set via attach_to_event_queue action config), and linked CRM entity IDs (contact, customer, deal). Supports optional status filter and cursor pagination. **Scopes:** `automations:read` Parameters: - `id` (path, uuid, required): Event queue ID - `status` (query, string): Filter by status: pending, processing, completed, cancelled, failed - `limit` (query, number): Max results per page (1-100, default 25) - `after` (query, string): Cursor for next page ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/event-queues/QUEUE_ID/enrollments?status=pending&limit=10" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### POST /event-queues/:id/bulk-enroll Enrol contacts into an event queue in bulk. Requires EXACTLY ONE of three mutually exclusive targeting modes: - contact_ids -- explicit list of contact UUIDs (max 500) - contact_filter -- filter by contact-level fields (individual-person fields: contact_type, source, email, first_name, last_name, job_title, city, state, country) - customer_filter -- filter by company/customer-level fields (account_type, industry, is_customer, is_supplier, city, state, country) -- resolves to all contacts linked to matching customers ENTITY AXIS DISTINCTION: contact_type lives on crm_contacts (use contact_filter). account_type lives on crm_customers (use customer_filter). opportunity_type lives on crm_deals (not filterable here -- use automations with deal triggers instead). Idempotent by default: contacts already enrolled with a pending step are skipped (set skip_if_already_enrolled: false to override). Capped at 500 contacts per call; if the filter would match more, has_more: true is returned and you should call again to page through. **Scopes:** `automations:write` — _write_ Parameters: - `id` (path, uuid, required): Event queue ID - `contact_ids` (body, string[]): Explicit list of contact UUIDs (max 500). Mutually exclusive with contact_filter and customer_filter. - `contact_filter` (body, object): Filter by contact-level fields. Supported: contact_type, source, email, first_name, last_name, job_title, city, state, country. Values can be a single value or an array (IN). Example: { "contact_type": ["Referrer", "Lawyer"] } - `customer_filter` (body, object): Filter by customer/company-level fields -- resolves to all contacts linked to matching customers. Supported: account_type, industry, is_customer, is_supplier, city, state, country. Example: { "account_type": "Accounting Firm" } - `enrollment_time` (body, string): ISO timestamp to use as the anchor for step delay calculations (default: now). Useful for backdating enrolments. - `skip_if_already_enrolled` (body, boolean): Default true. When true, contacts that already have a pending enrolment in this queue are skipped. Set false to force re-enrolment. ```bash # Enrol by contact_type (contact-level field) curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/event-queues/QUEUE_ID/bulk-enroll" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "contact_filter": { "contact_type": ["Referrer", "Lawyer"] } }' # Enrol all contacts linked to accounting firms (customer-level field) curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/event-queues/QUEUE_ID/bulk-enroll" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "customer_filter": { "account_type": "Accounting Firm", "is_customer": true } }' ``` --- ## Event Schedules Create clock-driven automation schedules that fire on a cron expression, fan out to a resolved audience, and trigger one automation run per row. ### GET /event-schedules List all event schedules for the workspace. **Scopes:** `schedules:read` Parameters: - `limit` (query, number): Items per page (default 25, max 100) - `after` (query, string): Cursor for next page ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/event-schedules" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### GET /event-schedules/:id Retrieve a single event schedule. **Scopes:** `schedules:read` Parameters: - `id` (path, uuid, required): Event schedule ID ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/event-schedules/SCHEDULE_ID" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### POST /event-schedules Create a new event schedule. Required fields: name, cron_expression, audience_type, automation_id. **Cron expression** is standard 5-field syntax: "min hour day-of-month month day-of-week". Common patterns: - "*/15 * * * *" -- every 15 minutes - "0 * * * *" -- every hour - "0 9 * * *" -- daily at 9am - "0 9 * * 1-5" -- weekdays at 9am - "0 9 * * 1" -- every Monday at 9am - "0 9,17 * * *" -- 9am and 5pm daily **audience_type** controls who receives a run: - "users" -- one run per staff member (with optional role/user_ids filter) - "tasks_by_assignee" -- one run per assignee who has open tasks (digest pattern) - "contacts" -- one run per matching contact - "deals" -- one run per matching deal **audience_filter** is type-specific JSON: - users: { "roles": ["client_editor"], "user_ids": ["uuid"] } - tasks_by_assignee: { "statuses": ["open"], "include_overdue_only": true, "max_tasks_per_user": 20 } - contacts: { "contact_type": "lead", "has_email": true, "limit": 500 } - deals: { "status": "open", "stale_days": 14, "pipeline_id": "uuid", "stage_ids": ["uuid"], "limit": 200 } Optionally set end_at (ISO timestamp) to stop firing after a date, or max_runs (integer) to auto-deactivate after N fires. **Scopes:** `schedules:write` — _write_ Parameters: - `name` (body, string, required): Schedule name - `cron_expression` (body, string, required): Standard 5-field cron expression - `audience_type` (body, string, required): One of: users, tasks_by_assignee, contacts, deals - `automation_id` (body, uuid, required): UUID of the automation to fire. Must belong to this workspace. - `description` (body, string): Optional description - `is_active` (body, boolean): Whether the schedule fires automatically (default true) - `timezone` (body, string): IANA timezone, e.g. "Australia/Sydney" (default) - `audience_filter` (body, object): Audience-specific filter JSON (see description above) - `end_at` (body, string): ISO timestamp after which the schedule stops firing - `max_runs` (body, number): Auto-deactivate after firing this many times ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/event-schedules" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: unique-key-here" \ -d '{ "name": "Daily Staff Digest", "cron_expression": "0 9 * * 1-5", "timezone": "Australia/Sydney", "audience_type": "users", "automation_id": "YOUR_AUTOMATION_ID" }' ``` ### PATCH /event-schedules/:id Partial update — modify name, description, is_active, cron_expression, timezone, audience_type, audience_filter, automation_id, end_at, or max_runs. Changing cron_expression or timezone automatically recomputes next_run_at. **Scopes:** `schedules:write` — _write_ Parameters: - `id` (path, uuid, required): Event schedule ID - `name` (body, string): Schedule name - `description` (body, string): Schedule description - `is_active` (body, boolean): Enable or disable the schedule - `cron_expression` (body, string): 5-field cron expression - `timezone` (body, string): IANA timezone - `audience_type` (body, string): One of: users, tasks_by_assignee, contacts, deals - `audience_filter` (body, object): Audience-specific filter JSON - `automation_id` (body, uuid): Replace the linked automation - `end_at` (body, string): ISO timestamp after which the schedule stops firing (null to remove) - `max_runs` (body, number): Max fires before auto-deactivation (null to remove) ```bash curl -X PATCH \ "https://api.trustpager.com/functions/v1/api/v1/event-schedules/SCHEDULE_ID" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"is_active": false}' ``` ### DELETE /event-schedules/:id Permanently delete an event schedule. The linked automation is NOT deleted. This cannot be undone. **Scopes:** `schedules:delete` — _write_ Parameters: - `id` (path, uuid, required): Event schedule ID ```bash curl -X DELETE \ "https://api.trustpager.com/functions/v1/api/v1/event-schedules/SCHEDULE_ID" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### POST /event-schedules/preview-cron Validate a cron expression and preview the next N fire times in a specified timezone. Does NOT create any schedule. Use to confirm a pattern before saving. **Scopes:** `schedules:read` Parameters: - `cron_expression` (body, string, required): Standard 5-field cron expression to validate - `timezone` (body, string): IANA timezone (default "Australia/Sydney") - `count` (body, number): Number of future fire times to return (1-20, default 5) ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/event-schedules/preview-cron" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "cron_expression": "0 9 * * 1-5", "timezone": "Australia/Sydney", "count": 3 }' ``` ### POST /event-schedules/:id/fire-now Manually fire an event schedule immediately. Resolves the current audience and triggers one automation run per row. Does NOT advance next_run_at, increment run_count, or respect end_at/max_runs limits. Useful for testing, ad-hoc sends, or recovering from a missed fire. **Scopes:** `schedules:write` — _write_ Parameters: - `id` (path, uuid, required): Event schedule ID ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/event-schedules/SCHEDULE_ID/fire-now" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Idempotency-Key: manual-fire-$(date +%s)" ``` ### GET /event-schedules/:id/audience-preview Preview the audience a schedule WOULD resolve to right now -- returns the total count and a configurable sample of trigger_data rows, without firing anything. Use before enabling a schedule to verify the filter targets the right rows. **Scopes:** `schedules:read` Parameters: - `id` (path, uuid, required): Event schedule ID - `sample` (query, number): Number of sample rows to return (0-50, default 5) ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/event-schedules/SCHEDULE_ID/audience-preview?sample=3" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### GET /event-schedules/:id/runs List historical fire records for a schedule. Shows audience_size, runs_triggered, runs_failed, status (completed/partial/failed), and timing. Use to verify schedules are firing, debug failures, or audit history. Supports cursor pagination ordered by fired_at descending. **Scopes:** `schedules:read` Parameters: - `id` (path, uuid, required): Event schedule ID - `limit` (query, number): Items per page (default 25, max 100) - `after` (query, string): Cursor for next page ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/event-schedules/SCHEDULE_ID/runs" \ -H "Authorization: Bearer YOUR_API_KEY" ``` --- ## Auto Queues Manage Auto Queues for sequenced multi-step workflows (drip campaigns, follow-up sequences). Legacy path /event-queues is also accepted. ### GET /auto-queues List all auto queues. **Scopes:** `automations:read` ### GET /auto-queues/:id Retrieve an auto queue with its steps. **Scopes:** `automations:read` Parameters: - `id` (path, uuid, required): Auto queue ID ### POST /auto-queues Create an auto queue. **Scopes:** `automations:write` — _write_ Parameters: - `name` (body, string, required): Queue name ### PATCH /auto-queues/:id Update an auto queue. **Scopes:** `automations:write` — _write_ Parameters: - `id` (path, uuid, required): Auto queue ID ### DELETE /auto-queues/:id Delete an auto queue. **Scopes:** `automations:write` — _write_ Parameters: - `id` (path, uuid, required): Auto queue ID ### POST /auto-queues/:id/steps Add a step to an auto queue. **Scopes:** `automations:write` — _write_ Parameters: - `id` (path, uuid, required): Auto queue ID ### PATCH /auto-queues/:id/steps/:stepId Update an auto queue step. **Scopes:** `automations:write` — _write_ Parameters: - `id` (path, uuid, required): Auto queue ID - `stepId` (path, uuid, required): Step ID ### DELETE /auto-queues/:id/steps/:stepId Delete an auto queue step. **Scopes:** `automations:write` — _write_ Parameters: - `id` (path, uuid, required): Auto queue ID - `stepId` (path, uuid, required): Step ID ### GET /auto-queues/:id/enrollments List enrollment timer tasks for an auto queue. Shows all scheduled step executions with their status, scheduled_for time, enrollment_time (the anchor time used for delay calculation when set via attach_to_event_queue action config), and linked CRM entity IDs (contact, customer, deal). Supports optional status filter and cursor pagination. **Scopes:** `automations:read` Parameters: - `id` (path, uuid, required): Auto queue ID - `status` (query, string): Filter by status: pending, processing, completed, cancelled, failed - `limit` (query, number): Max results per page (1-100, default 25) - `after` (query, string): Cursor for next page ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/auto-queues/QUEUE_ID/enrollments?status=pending&limit=10" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### POST /auto-queues/:id/bulk-enroll Enrol contacts (or deals) into an auto queue in bulk. Requires EXACTLY ONE of four mutually exclusive targeting modes: - contact_ids -- explicit list of contact UUIDs (max 500) - deal_ids -- explicit list of opportunity/deal UUIDs (max 500). Server resolves each deal to its contacts via crm_deals.contact_id + crm_deal_contacts join, then enrols each unique contact. The originating deal_id is passed as trigger_data.deal_id so downstream automation actions have full deal context. - contact_filter -- filter by contact-level fields (individual-person fields: contact_type, source, email, first_name, last_name, job_title, city, state, country) - customer_filter -- filter by company/customer-level fields (account_type, industry, is_customer, is_supplier, city, state, country) -- resolves to all contacts linked to matching customers ENTITY AXIS DISTINCTION: contact_type lives on crm_contacts (use contact_filter). account_type lives on crm_customers (use customer_filter). opportunity_type lives on crm_deals -- use deal_ids to target specific deals or automations with deal triggers for filter-based targeting. Idempotent by default: contacts already enrolled with a pending step are skipped (set skip_if_already_enrolled: false to override). Capped at 500 contacts per call; if the filter would match more, has_more: true is returned and you should call again to page through. **Scopes:** `automations:write` — _write_ Parameters: - `id` (path, uuid, required): Auto queue ID - `contact_ids` (body, string[]): Explicit list of contact UUIDs (max 500). Mutually exclusive with deal_ids, contact_filter, customer_filter. - `deal_ids` (body, string[]): Explicit list of opportunity/deal UUIDs (max 500). Server resolves each deal to its contacts (crm_deals.contact_id + crm_deal_contacts join) and passes deal_id into trigger_data for downstream actions. Mutually exclusive with contact_ids, contact_filter, customer_filter. - `contact_filter` (body, object): Filter by contact-level fields. Supported: contact_type, source, email, first_name, last_name, job_title, city, state, country. Values can be a single value or an array (IN). Example: { "contact_type": ["Referrer", "Lawyer"] } - `customer_filter` (body, object): Filter by customer/company-level fields -- resolves to all contacts linked to matching customers. Supported: account_type, industry, is_customer, is_supplier, city, state, country. Example: { "account_type": "Accounting Firm" } - `enrollment_time` (body, string): ISO timestamp to use as the anchor for step delay calculations (default: now). Useful for backdating enrolments. - `skip_if_already_enrolled` (body, boolean): Default true. When true, contacts that already have a pending enrolment in this queue are skipped. Set false to force re-enrolment. ```bash # Enrol by contact_type (contact-level field) curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/auto-queues/QUEUE_ID/bulk-enroll" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "contact_filter": { "contact_type": ["Referrer", "Lawyer"] } }' # Enrol contacts linked to specific deals (deal_ids mode -- deal_id passes through to trigger_data) curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/auto-queues/QUEUE_ID/bulk-enroll" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "deal_ids": ["deal-uuid-1", "deal-uuid-2"] }' # Enrol all contacts linked to accounting firms (customer-level field) curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/auto-queues/QUEUE_ID/bulk-enroll" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "customer_filter": { "account_type": "Accounting Firm", "is_customer": true } }' ``` --- ## Auto Schedules Create clock-driven automation schedules that fire on a cron expression, fan out to a resolved audience, and trigger one automation run per row. Legacy path /event-schedules is also accepted. ### GET /auto-schedules List all auto schedules for the workspace. **Scopes:** `schedules:read` Parameters: - `limit` (query, number): Items per page (default 25, max 100) - `after` (query, string): Cursor for next page ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/auto-schedules" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### GET /auto-schedules/:id Retrieve a single auto schedule. **Scopes:** `schedules:read` Parameters: - `id` (path, uuid, required): Auto schedule ID ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/auto-schedules/SCHEDULE_ID" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### POST /auto-schedules Create a new auto schedule. Required fields: name, audience_type, automation_id, and exactly one schedule input (see below). **Specify the schedule in one of two ways:** **Option A -- Structured (recommended for AI agents):** Provide time_of_day ("HH:MM", e.g. "08:00") plus optional days_of_week (array of ints 0-6, where 0=Sunday and 6=Saturday) and timezone. The server derives the cron expression automatically. - Example: time_of_day "08:00", days_of_week [1,2,3,4,5], timezone "Australia/Melbourne" -- stores cron "0 8 * * 1,2,3,4,5" **Option B -- Raw cron:** Provide cron_expression (standard 5-field syntax) and timezone. Use this for patterns that time_of_day cannot express, such as every-N-minutes, twice-daily, or monthly schedules. - Example: cron_expression "0 9,17 * * *", timezone "Australia/Sydney" -- fires 9am and 5pm daily If both are provided, cron_expression wins. Cron is the canonical stored form -- the response always includes cron_expression regardless of which input option was used. **Common cron patterns:** - "*/15 * * * *" -- every 15 minutes - "0 * * * *" -- every hour - "0 9 * * *" -- daily at 9am - "0 9 * * 1-5" -- weekdays at 9am - "0 9 * * 1" -- every Monday at 9am - "0 9,17 * * *" -- 9am and 5pm daily - "0 9 1 * *" -- 1st of every month at 9am **audience_type** controls who receives a run: - "users" -- one run per staff member (with optional role/user_ids filter) - "tasks_by_assignee" -- one run per assignee who has open tasks (digest pattern) - "contacts" -- one run per matching contact - "deals" -- one run per matching deal **audience_filter** is type-specific JSON: - users: { "roles": ["client_editor"], "user_ids": ["uuid"] } - tasks_by_assignee: { "statuses": ["open"], "include_overdue_only": true, "max_tasks_per_user": 20 } - contacts: { "contact_type": "lead", "has_email": true, "limit": 500 } - deals: { "status": "open", "stale_days": 14, "pipeline_id": "uuid", "stage_ids": ["uuid"], "limit": 200 } Optionally set end_at (ISO timestamp) to stop firing after a date, or max_runs (integer) to auto-deactivate after N fires. **Scopes:** `schedules:write` — _write_ Parameters: - `name` (body, string, required): Schedule name - `audience_type` (body, string, required): One of: users, tasks_by_assignee, contacts, deals - `automation_id` (body, uuid, required): UUID of the automation to fire. Must belong to this workspace. - `time_of_day` (body, string): Schedule time in "HH:MM" format (e.g. "08:00"). Use with optional days_of_week and timezone. Server derives cron_expression automatically. Required if cron_expression is not provided. - `days_of_week` (body, array): Array of integers 0-6 (0=Sunday, 6=Saturday). Used with time_of_day. Omit or pass empty array for daily. Example: [1,2,3,4,5] for weekdays. - `cron_expression` (body, string): Standard 5-field cron expression. Use for patterns time_of_day cannot express (every-N-minutes, twice-daily, monthly, etc.). If both time_of_day and cron_expression are provided, cron_expression wins. - `timezone` (body, string): IANA timezone, e.g. "Australia/Sydney" (default). Used with both input options. - `description` (body, string): Optional description - `is_active` (body, boolean): Whether the schedule fires automatically (default true) - `audience_filter` (body, object): Audience-specific filter JSON (see description above) - `end_at` (body, string): ISO timestamp after which the schedule stops firing - `max_runs` (body, number): Auto-deactivate after firing this many times ```bash # Option A -- structured (AI-agent-friendly) curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/auto-schedules" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: unique-key-here" \ -d '{ "name": "Morning Task Digest", "time_of_day": "08:00", "days_of_week": [1, 2, 3, 4, 5], "timezone": "Australia/Melbourne", "audience_type": "tasks_by_assignee", "audience_filter": {}, "automation_id": "YOUR_AUTOMATION_ID" }' # Option B -- raw cron (for complex patterns) curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/auto-schedules" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: unique-key-here" \ -d '{ "name": "Daily Staff Digest", "cron_expression": "0 9 * * 1-5", "timezone": "Australia/Sydney", "audience_type": "users", "automation_id": "YOUR_AUTOMATION_ID" }' ``` ### PATCH /auto-schedules/:id Partial update -- modify any field. Accepts the same two schedule-input options as POST: either time_of_day (+ optional days_of_week) or cron_expression. If cron_expression is provided it takes precedence. Changing either schedule input or timezone automatically recomputes next_run_at. **Scopes:** `schedules:write` — _write_ Parameters: - `id` (path, uuid, required): Auto schedule ID - `name` (body, string): Schedule name - `description` (body, string): Schedule description - `is_active` (body, boolean): Enable or disable the schedule - `time_of_day` (body, string): Update the firing time using "HH:MM" format. Server re-derives cron_expression. Use with optional days_of_week. - `days_of_week` (body, array): Array of integers 0-6. Used with time_of_day to update which days the schedule fires. - `cron_expression` (body, string): 5-field cron expression. Takes precedence over time_of_day if both are provided. - `timezone` (body, string): IANA timezone - `audience_type` (body, string): One of: users, tasks_by_assignee, contacts, deals - `audience_filter` (body, object): Audience-specific filter JSON - `automation_id` (body, uuid): Replace the linked automation - `end_at` (body, string): ISO timestamp after which the schedule stops firing (null to remove) - `max_runs` (body, number): Max fires before auto-deactivation (null to remove) ```bash curl -X PATCH \ "https://api.trustpager.com/functions/v1/api/v1/auto-schedules/SCHEDULE_ID" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"is_active": false}' ``` ### DELETE /auto-schedules/:id Permanently delete an auto schedule. The linked automation is NOT deleted. This cannot be undone. **Scopes:** `schedules:delete` — _write_ Parameters: - `id` (path, uuid, required): Auto schedule ID ```bash curl -X DELETE \ "https://api.trustpager.com/functions/v1/api/v1/auto-schedules/SCHEDULE_ID" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### POST /auto-schedules/preview-cron Validate a cron expression and preview the next N fire times in a specified timezone. Does NOT create any schedule. Use to confirm a pattern before saving. **Scopes:** `schedules:read` Parameters: - `cron_expression` (body, string, required): Standard 5-field cron expression to validate - `timezone` (body, string): IANA timezone (default "Australia/Sydney") - `count` (body, number): Number of future fire times to return (1-20, default 5) ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/auto-schedules/preview-cron" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "cron_expression": "0 9 * * 1-5", "timezone": "Australia/Sydney", "count": 3 }' ``` ### POST /auto-schedules/:id/fire-now Manually fire an auto schedule immediately. Resolves the current audience and triggers one automation run per row. Does NOT advance next_run_at, increment run_count, or respect end_at/max_runs limits. Useful for testing, ad-hoc sends, or recovering from a missed fire. **Scopes:** `schedules:write` — _write_ Parameters: - `id` (path, uuid, required): Auto schedule ID ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/auto-schedules/SCHEDULE_ID/fire-now" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Idempotency-Key: manual-fire-$(date +%s)" ``` ### GET /auto-schedules/:id/audience-preview Preview the audience a schedule WOULD resolve to right now -- returns the total count and a configurable sample of trigger_data rows, without firing anything. Use before enabling a schedule to verify the filter targets the right rows. **Scopes:** `schedules:read` Parameters: - `id` (path, uuid, required): Auto schedule ID - `sample` (query, number): Number of sample rows to return (0-50, default 5) ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/auto-schedules/SCHEDULE_ID/audience-preview?sample=3" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### GET /auto-schedules/:id/runs List historical fire records for a schedule. Shows audience_size, runs_triggered, runs_failed, status (completed/partial/failed), and timing. Use to verify schedules are firing, debug failures, or audit history. Supports cursor pagination ordered by fired_at descending. **Scopes:** `schedules:read` Parameters: - `id` (path, uuid, required): Auto schedule ID - `limit` (query, number): Items per page (default 25, max 100) - `after` (query, string): Cursor for next page ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/auto-schedules/SCHEDULE_ID/runs" \ -H "Authorization: Bearer YOUR_API_KEY" ``` --- ## Scheduled Event Types Configurable event templates that appear in the Add Event dropdown on opportunities. Each type can optionally attach an event queue for auto-enrollment with reminders. ### GET /scheduled-event-types List all scheduled event types for the company with cursor-based pagination. **Scopes:** `company:read` Parameters: - `limit` (query, number): Max results per page (1-100, default 25) - `after` (query, string): Cursor for next page ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/scheduled-event-types" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### POST /scheduled-event-types Create a new scheduled event type template. name is required. **Scopes:** `company:write` — _write_ Parameters: - `name` (body, string, required): Event type name. E.g. "Site Visit", "Follow-Up Call", "Demo" - `description` (body, string): Description shown in the dropdown - `icon` (body, string): Lucide icon name: calendar, video, phone, users, map-pin, coffee, briefcase, file-text, clock, calendar-clock - `color` (body, string): Color theme: primary, emerald, blue, amber, red, purple, pink, indigo - `is_meeting` (body, boolean): If true, uses Google Meet form (duration, invitees). If false, uses Standard Event form. - `default_duration_minutes` (body, number): Default meeting duration in minutes (only applies when is_meeting is true) - `event_queue_id` (body, uuid): Event queue UUID for auto-enrollment when this type is added to an opportunity - `enrollment_offset_minutes` (body, number): How far before the event to start the queue (0 = at event time, 1440 = 1 day before) - `sort_order` (body, number): Display order in the dropdown - `is_active` (body, boolean): Whether this type appears in the dropdown (default true) - `is_bookable` (body, boolean): If true, external bookers can book this type publicly - `slug` (body, string): URL-friendly slug for public booking page (auto-generated from name if omitted) - `buffer_before_minutes` (body, number): Buffer time before booking (minutes) - `buffer_after_minutes` (body, number): Buffer time after booking (minutes) - `min_notice_hours` (body, number): Minimum notice required (hours) - `max_advance_days` (body, number): Max days in advance to book - `slot_interval_minutes` (body, number): Time slot interval (minutes) - `booking_pipeline_id` (body, uuid): Pipeline for auto-created opportunities - `booking_stage_id` (body, uuid): Stage for auto-created opportunities - `booking_auto_create_deal` (body, boolean): Whether to auto-create a CRM opportunity when booked (default true). Param name preserved for backward compatibility. - `booking_lead_source` (body, string): Lead source for auto-created opportunities (default "booking") - `booking_deal_name_template` (body, string): Opportunity name template (param name preserved for backward compatibility). Variables: {event_type}, {customer_name}, {date}, {time} - `booking_assigned_user_ids` (body, array): Team member UUIDs to assign to opportunities and check calendar availability - `booking_default_tags` (body, array): Tags auto-applied to opportunities. Array of {name, color} objects. - `booking_default_products` (body, array): Products auto-attached to opportunities. Array of {id, quantity} objects. - `booking_confirmation_message` (body, string): Custom confirmation message shown after booking - `booking_deal_behavior` (body, string): How bookings interact with CRM opportunities (param name preserved for backward compatibility): "create" (always new), "update_or_create" (match existing or create), "never_create" (match only). Default: "create". - `booking_notifications` (body, object): Booking notifications. Object with keys: pre_meeting, meeting_start, late, no_show, rebooking. Each key is an array of { offset_minutes? (pre_meeting/rebooking), channels: ["email"|"sms"], recipients: "booker"|"booker_and_attendees"|"team"|"all", email_subject, email_body, sms_body, label }. Template vars: {booker_name}, {event_type}, {date}, {time}, {meet_link}, {duration}, {rebook_link} ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/scheduled-event-types" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Site Visit", "icon": "map-pin", "color": "emerald", "is_bookable": true, "default_duration_minutes": 30, "booking_deal_behavior": "update_or_create", "booking_notifications": { "pre_meeting": [ { "offset_minutes": 1440, "channels": ["email"], "recipients": "booker", "email_subject": "See you tomorrow!", "email_body": "Hi {booker_name}, your meeting is tomorrow at {time}.", "label": "24hr reminder" } ] } }' ``` ### GET /scheduled-event-types/:id Retrieve a single scheduled event type by ID. **Scopes:** `company:read` Parameters: - `id` (path, uuid, required): Scheduled event type ID ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/scheduled-event-types/a1b2c3d4-..." \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### PATCH /scheduled-event-types/:id Update a scheduled event type. Only include fields you want to change. **Scopes:** `company:write` — _write_ Parameters: - `id` (path, uuid, required): Scheduled event type ID - `name` (body, string): Event type name - `description` (body, string): Description - `icon` (body, string): Lucide icon name - `color` (body, string): Color theme - `is_meeting` (body, boolean): Meeting type flag - `default_duration_minutes` (body, number): Default duration (meeting types only) - `event_queue_id` (body, uuid): Linked event queue UUID - `enrollment_offset_minutes` (body, number): Minutes before event to start queue - `sort_order` (body, number): Display order - `is_active` (body, boolean): Active/inactive state - `is_bookable` (body, boolean): Whether external bookers can book this type publicly - `slug` (body, string): URL-friendly slug for public booking page - `buffer_before_minutes` (body, number): Buffer time before booking (minutes) - `buffer_after_minutes` (body, number): Buffer time after booking (minutes) - `min_notice_hours` (body, number): Minimum notice required (hours) - `max_advance_days` (body, number): Max days in advance to book - `slot_interval_minutes` (body, number): Time slot interval (minutes) - `booking_pipeline_id` (body, uuid): Pipeline for auto-created opportunities - `booking_stage_id` (body, uuid): Stage for auto-created opportunities - `booking_assigned_user_ids` (body, array): Team member UUIDs to assign to opportunities and check calendar availability - `booking_deal_behavior` (body, string): How bookings interact with CRM opportunities (param name preserved for backward compatibility): "create" (always new), "update_or_create" (match existing or create), "never_create" (match only). Default: "create". - `booking_notifications` (body, object): Booking notifications. Object with keys: pre_meeting, meeting_start, late, no_show, rebooking. Each key is an array of { offset_minutes? (pre_meeting/rebooking), channels: ["email"|"sms"], recipients: "booker"|"booker_and_attendees"|"team"|"all", email_subject, email_body, sms_body, label }. Template vars: {booker_name}, {event_type}, {date}, {time}, {meet_link}, {duration}, {rebook_link} ```bash curl -X PATCH \ "https://api.trustpager.com/functions/v1/api/v1/scheduled-event-types/a1b2c3d4-..." \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Client Site Visit", "color": "blue" }' ``` ### DELETE /scheduled-event-types/:id Permanently delete a scheduled event type. Returns 204 No Content on success. **Scopes:** `company:write` — _write_ Parameters: - `id` (path, uuid, required): Scheduled event type ID ```bash curl -X DELETE \ "https://api.trustpager.com/functions/v1/api/v1/scheduled-event-types/a1b2c3d4-..." \ -H "Authorization: Bearer YOUR_API_KEY" ``` --- ## Scheduling Availability Manage company and per-user working hours, timezone, and date overrides for the booking system. ### GET /scheduling-availability Get scheduling availability config. Returns weekly hours, timezone, and date overrides. Defaults to company-level availability. **Scopes:** `company:read` Parameters: - `user_id` (query, string): "me" for current user, or a user UUID. Omit for company default. ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/scheduling-availability" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### PUT /scheduling-availability Update or create scheduling availability. Upserts -- creates the record if it does not exist. **Scopes:** `company:write` — _write_ Parameters: - `weekly_hours` (body, array): Array of {day: 0-6, start: "HH:MM", end: "HH:MM"}. day 0=Sunday, 6=Saturday. - `timezone` (body, string): IANA timezone (e.g. "Australia/Sydney") - `date_overrides` (body, array): Array of {date: "YYYY-MM-DD", type: "blocked"|"custom", start?, end?, reason?} - `user_id` (body, string): Set user-specific availability instead of company default ```bash curl -X PUT \ "https://api.trustpager.com/functions/v1/api/v1/scheduling-availability" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "weekly_hours": [ {"day": 1, "start": "09:00", "end": "17:00"}, {"day": 2, "start": "09:00", "end": "17:00"}, {"day": 3, "start": "09:00", "end": "17:00"}, {"day": 4, "start": "09:00", "end": "17:00"}, {"day": 5, "start": "09:00", "end": "17:00"} ], "timezone": "Australia/Sydney", "date_overrides": [ {"date": "2026-12-25", "type": "blocked", "reason": "Christmas"} ] }' ``` --- ## Scheduling Bookings Create, manage, and check availability for bookings. Bookings link to an opportunity via deal_id (field name preserved for backward compatibility) and surface in the Meetings card on the opportunity page. Scheduler bookings are separate from the opportunity's next_action_* fields, which are for ad-hoc reminders only. AI-agent-friendly: human-readable responses, self-correcting errors with alternatives, nearest-slot suggestions. Includes voice-optimised endpoints (text/plain responses) designed for Retell AI voice agents. ### GET /scheduling-bookings List bookings with filters. Returns upcoming/past bookings sorted by start time. Includes management_token and management_token_expires_at for each booking (null on bookings created before self-service management was introduced). **Scopes:** `company:read` Parameters: - `status` (query, string): Filter: confirmed, cancelled, attended, late, no_show - `event_type_id` (query, string): Filter by event type UUID - `date_from` (query, string): Filter: bookings on or after YYYY-MM-DD - `date_to` (query, string): Filter: bookings on or before YYYY-MM-DD - `limit` (query, number): Max results (default 25, max 100) - `after` (query, string): Cursor for next page ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/scheduling-bookings?status=confirmed&date_from=2026-03-24" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### GET /scheduling-bookings/:id Get a single booking by ID with full details including linked event type name. Returns management_token (UUID used to construct the self-service management URL: https://app.trustpager.com/book/{company_slug}/manage/{management_token}) and management_token_expires_at (set to 24 hours after the booking end time; null on old bookings). The management token is NOT returned in create responses -- it is delivered to the booker only via the Google Calendar invite description and booking reminder emails using the {management_url} template variable. **Scopes:** `company:read` Parameters: - `id` (path, string, required): Booking UUID ### POST /scheduling-bookings Create a booking. Auto-creates CRM opportunity + contact (response field names like deal_id are preserved for backward compatibility). Flat request body (no nested objects). Email format is validated before slot logic runs -- malformed addresses (missing TLD, no domain) return INVALID_EMAIL; invalid attendee emails return INVALID_ATTENDEE_EMAIL. On slot conflict, returns SLOT_UNAVAILABLE error with nearest alternatives. Accepts event type by ID, slug, or name. **Scopes:** `company:write` — _write_ Parameters: - `event_type_id` (body, string): Event type UUID. Or use event_type_slug or event_type_name. - `event_type_slug` (body, string): Event type slug (e.g. "30-minute-booking"). Alternative to ID. - `event_type_name` (body, string): Event type name (fuzzy matched). Alternative to ID. - `date` (body, string, required): Booking date YYYY-MM-DD - `time` (body, string, required): Booking time HH:MM (24h format) - `timezone` (body, string): Customer (booker) timezone. Authoritative for converting date+time to UTC. Default: Australia/Sydney. Accepts IANA names (e.g. "Australia/Perth") or shorthands (e.g. "Perth", "AWST", "WA"). - `fullName` (body, string, required): Customer full name - `email` (body, string, required): Customer email. Must be a valid format with a complete TLD (e.g. "name@example.com"). Truncated addresses like "doug@resolvency." are rejected with INVALID_EMAIL before slot validation runs. - `phone` (body, string): Customer phone - `message` (body, string): Customer notes - `company_name` (body, string): Customer company (used for CRM account) - `booker_state` (body, string): Customer state/region (e.g. "WA", "NSW"). Written to CRM contact state field if currently empty. - `booker_timezone` (body, string): Explicit CRM timezone to store on the contact (e.g. "Australia/Perth"). Falls back to timezone if omitted. Written to CRM contact timezone field if currently empty. - `attendees` (body, array): Additional attendees: [{ "email": "...", "name": "...", "phone": "..." }]. All receive calendar invites. Each attendee email must also be a valid format; invalid entries return INVALID_ATTENDEE_EMAIL. ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/scheduling-bookings" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "event_type_slug": "30-minute-booking", "date": "2026-04-01", "time": "10:00", "timezone": "Australia/Sydney", "fullName": "John Smith", "email": "john@example.com", "phone": "+61412345678" }' ``` ### POST /scheduling-bookings/:id/cancel Cancel a booking. Updates status, removes Google Calendar event (notifies all attendees), and cancels pending reminders. Does not affect the opportunity's next_action fields (bookings and next_action are independent). **Scopes:** `company:write` — _write_ Parameters: - `id` (path, string, required): Booking UUID - `cancellation_reason` (body, string): Reason for cancellation ### POST /scheduling-bookings/check-availability Quick check if a specific date/time slot is available. Always returns nearest_before and nearest_after slots -- perfect for conversational booking flows. **Scopes:** `company:read` Parameters: - `event_type_id` (body, string): Event type UUID (or slug/name) - `event_type_slug` (body, string): Event type slug - `event_type_name` (body, string): Event type name - `date` (body, string, required): Date to check YYYY-MM-DD - `time` (body, string, required): Time to check HH:MM (24h) - `timezone` (body, string): Timezone. Default: Australia/Sydney ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/scheduling-bookings/check-availability" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "event_type_slug": "30-minute-booking", "date": "2026-04-03", "time": "10:00", "timezone": "Australia/Sydney" }' ``` ### POST /scheduling-bookings/available-slots Get all available time slots for a date range. Returns slots grouped by date with human-readable formatting. Integrates with Google Calendar freebusy to block busy times. **Scopes:** `company:read` Parameters: - `event_type_id` (body, string): Event type UUID (or slug/name) - `event_type_slug` (body, string): Event type slug - `event_type_name` (body, string): Event type name - `date_from` (body, string): Start date YYYY-MM-DD. Default: today. - `date_to` (body, string): End date YYYY-MM-DD. Default: 14 days from date_from. Max 30 days. - `timezone` (body, string): Timezone. Default: Australia/Sydney. Auto-resolves abbreviations. ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/scheduling-bookings/available-slots" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "event_type_slug": "30-minute-booking", "date_from": "2026-04-01", "date_to": "2026-04-03" }' ``` ### POST /scheduling-bookings/:id/mark-attended Mark a booking as attended. Sets status to "attended", clears the linked opportunity's next action, cancels pending reminders, and logs a CRM activity with full booking context. **Scopes:** `company:write` — _write_ Parameters: - `id` (path, string, required): Booking UUID ### POST /scheduling-bookings/:id/mark-late Mark a booking as late. Sets status to "late", sends late notifications to the booker (if configured on the event type), and logs a CRM activity. **Scopes:** `company:write` — _write_ Parameters: - `id` (path, string, required): Booking UUID ### POST /scheduling-bookings/:id/mark-no-show Mark a booking as no-show. Sets status to "no_show", sends no-show notifications, schedules rebooking reminders (if configured), and logs a CRM activity. **Scopes:** `company:write` — _write_ Parameters: - `id` (path, string, required): Booking UUID ### POST /scheduling-bookings/:id/notetaker Attach the TrustPager Notetaker to an existing confirmed booking. The bot joins 60 seconds before the meeting start time, transcribes with speaker attribution, and writes the transcript to the linked CRM opportunity. Idempotent -- safe to call multiple times (returns already_scheduled: true if already registered). Requires: (1) TrustPager Notetaker enabled in company settings, (2) booking must have a google_meet_link, (3) booking must not be cancelled. Credits: 23 credits per recorded minute, billed after the meeting ends. **Scopes:** `company:write` — _write_ Parameters: - `id` (path, string, required): Booking UUID. Must have a google_meet_link and status must not be cancelled. ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/scheduling-bookings/b1c2d3e4-.../notetaker" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### POST /scheduling/voice/:event_type_id/slots Voice-agent slot picker. Returns text/plain — a human-readable listing of available slots with a header block showing event type name, duration, host names, and description. Designed to be consumed verbatim by a Retell AI voice agent as a custom tool response. The AI never sees raw JSON or date ranges — just readable text it can speak directly. Slots are tagged with exact booking keys (slot:"YYYY-MM-DD HH:MM") for use in the /book endpoint. **Scopes:** `scheduling:read` Parameters: - `event_type_id` (path, uuid, required): Event type UUID in the URL path - `preferred_day` (body, string): Natural-language day preference: "monday", "tomorrow", "asap", "this week", or YYYY-MM-DD. Past dates auto-advance to next occurrence of that weekday. Default: "asap". - `preferred_time` (body, string): Time preference: "morning", "afternoon", "evening", "around 2pm", "14:00", or omit for any time. - `state` (body, string): Customer state/territory (e.g. "NSW", "VIC", "QLD"). Used to auto-resolve timezone. Also accepted from Retell call dynamic variables. - `timezone` (body, string): IANA timezone override (e.g. "Australia/Perth"). Fallback if state is not provided. Default: Australia/Sydney. ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/scheduling/voice/{event_type_id}/slots" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "preferred_day": "monday", "preferred_time": "morning", "state": "NSW" }' ``` ### POST /scheduling/voice/:event_type_id/book Voice-agent booking confirmation. Returns text/plain — a plain English sentence the AI reads to the caller to confirm the appointment. On success, includes event type name, duration, formatted start/end time, host names, description, Google Meet link, and booking ID. On failure, always returns HTTP 200 with a bolded warning the AI must not ignore — prevents the AI from falsely confirming a booking that failed. Supports Retell custom tool format: {"call":{...},"name":"tool_name","args":{...}}. **Scopes:** `scheduling:write` — _write_ Parameters: - `event_type_id` (path, uuid, required): Event type UUID in the URL path - `slot` (body, string, required): Exact slot value from the /slots response, e.g. "2026-04-28 10:45". Unsubstituted Retell template variables ({{...}}) are detected and rejected with a fix message. - `name` (body, string, required): Caller full name. Unsubstituted template variables rejected. - `email` (body, string, required): Caller email for confirmation. Unsubstituted template variables rejected. - `phone` (body, string): Caller phone number (mobile, E.164 format) - `notes` (body, string): Any notes the caller provided - `state` (body, string): Customer state for timezone resolution (e.g. "VIC") - `timezone` (body, string): IANA timezone override. Default: Australia/Sydney. ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/scheduling/voice/{event_type_id}/book" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "slot": "2026-04-28 10:00", "name": "Sarah Jones", "email": "sarah@example.com", "phone": "+61412345678", "state": "NSW" }' ``` ### POST /scheduling/voice/cancel-booking Voice-agent booking cancellation. Returns text/plain. Resolves the caller by phone number (from args.phone, or Retell call envelope from_number/to_number as fallback), then cancels the matching upcoming booking. When the caller has multiple upcoming bookings, omit booking_selector on the first call to receive a numbered disambiguation list; re-fire with the caller's choice to execute the cancellation. Always returns HTTP 200 - failures begin with "BOOKING CANCELLATION FAILED:" and must not be read as confirmations. **Scopes:** `scheduling:write` — _write_ Parameters: - `phone` (body, string): Caller phone number (E.164 or AU local format). Falls back to Retell call envelope from_number (inbound) or to_number (outbound). - `booking_selector` (body, string): Which booking to cancel when the caller has multiple upcoming. Accepts: 1-based integer index (from disambiguation list), booking UUID prefix, or partial event type name substring. Omit to auto-select when only one booking exists, or to trigger the disambiguation list when multiple exist. - `reason` (body, string): Cancellation reason stored on the booking record. ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/scheduling/voice/cancel-booking" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "args": { "phone": "+61412345678", "booking_selector": "1", "reason": "caller request" } }' ``` ### POST /scheduling/voice/reschedule-booking Voice-agent booking reschedule. Returns text/plain. Resolves the caller by phone, then moves an existing booking to a new slot. New booking is created first (slot-race protection), then the old booking is cancelled. If the old cancel fails, a rollback is attempted on the new booking. Uses the same two-shot disambiguation pattern as cancel-booking when multiple upcoming bookings exist. Always returns HTTP 200 - failures begin with "RESCHEDULE FAILED:". **Scopes:** `scheduling:write` — _write_ Parameters: - `phone` (body, string): Caller phone number (E.164 or AU local). Falls back to Retell call envelope. - `booking_selector` (body, string): Which booking to reschedule. Same format as cancel-booking selector. Omit for auto-select or to trigger disambiguation. - `new_slot` (body, string, required): New slot in "YYYY-MM-DD HH:MM" format, as returned by the /slots endpoint for this event type. - `timezone` (body, string): IANA timezone name. Defaults to the event type configured timezone. ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/scheduling/voice/reschedule-booking" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "args": { "phone": "+61412345678", "new_slot": "2026-04-30 14:00", "timezone": "Australia/Brisbane" } }' ``` --- ## Email Send and receive emails via TrustPager Mail or Gmail, manage threads, view logs, and configure email settings. Supports provider selection for Gmail integration. ### GET /email/threads List email threads with linked entities (contacts, deals, customers). Supports full-text subject search, entity-based filtering, participant email filtering, and direction filtering. **Scopes:** `email:read` Parameters: - `status` (query, string): Filter by status (open, closed) - `is_read` (query, boolean): Filter by read status - `is_automated` (query, boolean): Filter by automated (true) or manual (false) threads - `direction` (query, string): Filter by last message direction: "inbound" or "outbound" - `search` (query, string): Search by subject (partial match, case-insensitive). Example: ?search=brochure - `contact_id` (query, uuid): Filter threads linked to a specific contact UUID via conversation links - `customer_id` (query, uuid): Filter threads linked to a specific customer/account UUID via conversation links - `deal_id` (query, uuid): Filter threads linked to a specific deal UUID via conversation links - `participant` (query, string): Filter by participant email address (exact match). Finds threads where the given email appears in the participants list. - `limit` (query, number): Max results (1-100) - `cursor` (query, string): Cursor for pagination ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/email/threads?search=proposal&direction=inbound&limit=10" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### GET /email/threads/:id Retrieve a single email thread with linked entities. **Scopes:** `email:read` Parameters: - `id` (path, uuid, required): Thread ID ### GET /email/threads/:id/messages List all messages (inbound and outbound) in a thread, sorted chronologically. Inbound messages include an "attachments" array with attachment metadata (name, mimeType, size). Outbound messages include "has_attachment" (boolean) and "attachment_filename" (string or null). **Scopes:** `email:read` Parameters: - `id` (path, uuid, required): Thread ID - `limit` (query, number): Max messages per page ### POST /email/send Send a new email. Default mode is "company" (uses configured Company Mail provider). Use mode "personal" with sender_user_id to send from a specific user's Gmail. When contact_id or deal_id is provided, an outbound email activity is automatically logged to their CRM timeline. If a contact_id / deal_id / customer_id is provided but does not exist in the workspace, the request is rejected with 400 VALIDATION_ERROR before the email is dispatched; the response body includes details.missing_entities with the offending field names and IDs. Omit the field entirely to send without a CRM link. Rare: if the email is delivered but the internal log row fails, the API returns 422 EMAIL_LOG_FAILED with the delivery message id in message_id so the caller knows the email did reach the recipient. **Scopes:** `email:send` — _write_ Parameters: - `to_email` (body, string, required): Recipient email address - `to_name` (body, string): Recipient name - `subject` (body, string, required): Email subject - `html_body` (body, string, required): HTML email body - `mode` (body, string): Send mode: "company" (default) or "personal" - `sender_user_id` (body, uuid): User UUID whose Gmail to send from (required when mode is "personal") - `from_email` (body, string): Send-as alias when mode is "personal". Must be a valid alias for the sender (use GET /email/capabilities to see available aliases). If omitted, auto-resolves to the user's workspace email if it is a valid alias. - `contact_id` (body, uuid): Link to contact. Auto-logs an outbound email activity on the contact timeline. Must exist in this workspace or the request returns 400 VALIDATION_ERROR. - `deal_id` (body, uuid): Link to deal. Auto-logs an outbound email activity on the deal timeline. Must exist in this workspace or the request returns 400 VALIDATION_ERROR. - `cc` (body, string): CC recipients as comma-separated email addresses (e.g. "alice@example.com, bob@example.com"). Supported on both mode="company" (TrustPager Mail) and mode="personal" (Gmail). CC addresses are merged into the email thread's participant list. - `customer_id` (body, uuid): Link to customer. Must exist in this workspace or the request returns 400 VALIDATION_ERROR. - `email_config_id` (body, uuid): Email config to use (defaults to company default) - `attachments` (body, array): File attachments (Gmail only, max 25MB total). Array of objects with filename, mime_type, and content (base64-encoded file data). ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/email/send" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "to_email": "client@example.com", "to_name": "Jane Doe", "subject": "Your CRM Setup Report", "html_body": "

Hello!

See attached report.

", "mode": "personal", "sender_user_id": "user-uuid-...", "attachments": [ { "filename": "report.pdf", "mime_type": "application/pdf", "content": "JVBERi0xLjQg... (base64-encoded file)" } ] }' ``` ### POST /email/reply Reply to an existing email thread. Default mode is "company" (uses configured provider). Use mode "personal" with sender_user_id for a user's Gmail. **Scopes:** `email:send` — _write_ Parameters: - `thread_id` (body, uuid, required): Thread ID to reply to - `reply_html` (body, string, required): Reply HTML body - `to_email` (body, string, required): Recipient email - `to_name` (body, string): Recipient name - `cc` (body, string): CC recipients as comma-separated email addresses - `in_reply_to` (body, string): Message ID to reply to (for threading) - `mode` (body, string): Send mode: "company" (default) or "personal" - `sender_user_id` (body, uuid): User UUID whose Gmail to reply from (required when mode is "personal") - `from_email` (body, string): Send-as alias when mode is "personal". Must be a valid alias for the sender. Use GET /email/capabilities to see available aliases. - `attachments` (body, array): File attachments (Gmail only, max 25MB total). Array of objects with filename, mime_type, and content (base64-encoded file data). ### GET /email/capabilities Check email sending capabilities. Returns authenticated_user (the API caller's identity), Company Mail provider (TrustPager Mail or Gmail), and which users have personal Gmail connected. Use authenticated_user to identify who "I" is for personal/conversational sends. **Scopes:** `email:read` ### GET /email/logs List email send logs. Each log includes subject, recipient_email, sender_email, status, provider, cc, thread_id, and sender_user_id. Filter by status, contact, deal, type, or provider. **Scopes:** `email:read` Parameters: - `status` (query, string): Filter by status (sent, delivered, bounced, failed, spam_complaint) - `contact_id` (query, uuid): Filter by contact UUID - `deal_id` (query, uuid): Filter by deal UUID - `email_type` (query, string): Filter by type (e.g. form_submission, automation, manual) - `provider` (query, string): Filter by provider: "postmark" or "gmail" ### GET /email/configs List all email configurations for the company. **Scopes:** `email:read` ### GET /email/configs/:id Retrieve a specific email configuration. **Scopes:** `email:read` Parameters: - `id` (path, uuid, required): Email config ID ### POST /email/configs Create an email configuration. Requires from_email_handle, from_name, and staff_email. Pass the local-part only as from_email_handle (e.g. "support") -- the server composes the full address as support@mail.trustpager.net. Passing the legacy from_email field returns error code FROM_EMAIL_NOT_WRITABLE. Invalid handles return INVALID_FROM_EMAIL_HANDLE. gmail_sender_alias must already exist as a "Send mail as" address on the connected Gmail account assigned to this workspace (ALIAS_NOT_CONNECTED otherwise). See GET /email/capabilities for available send_as_aliases. **Scopes:** `email-config:write` — _write_ Parameters: - `from_email_handle` (body, string, required): Local-part of the Postmark sender address (e.g. "support"). The server appends @mail.trustpager.net. Must be 1-64 lowercase chars (a-z, 0-9, dot, underscore, hyphen), starting with a letter or digit. Error code INVALID_FROM_EMAIL_HANDLE if validation fails. Passing the old from_email field returns FROM_EMAIL_NOT_WRITABLE. - `from_name` (body, string, required): Sender display name - `staff_email` (body, string, required): Staff notification email - `config_name` (body, string): Config display name - `logo_url` (body, string): Logo URL for emails - `primary_color` (body, string): Primary brand color hex - `is_default` (body, boolean): Set as default config - `gmail_sender_user_id` (body, uuid): User UUID whose connected Gmail account owns the alias. Required when setting gmail_sender_alias. Error code GMAIL_NOT_CONNECTED if user has no active Gmail connection. - `gmail_sender_alias` (body, string): Gmail send-as alias for send_gmail_email automations. Must already exist as a "Send mail as" address on the connected Gmail account AND be assigned to this workspace at /settings/email. Error code ALIAS_NOT_CONNECTED if the alias is not found. Error code GMAIL_USER_REQUIRED if gmail_sender_user_id is omitted. ### PATCH /email/configs/:id Partial update of an email configuration. Same shape validation as POST: pass from_email_handle (local-part only) to change the Postmark sender address; passing from_email returns FROM_EMAIL_NOT_WRITABLE. gmail_sender_alias must be a registered send-as alias for the connected user (ALIAS_NOT_CONNECTED, GMAIL_NOT_CONNECTED, GMAIL_USER_REQUIRED). **Scopes:** `email-config:write` — _write_ Parameters: - `id` (path, uuid, required): Email config ID - `from_email_handle` (body, string): Local-part of the Postmark sender address (e.g. "support"). Server appends @mail.trustpager.net. Error code INVALID_FROM_EMAIL_HANDLE if regex fails. Passing from_email returns FROM_EMAIL_NOT_WRITABLE. - `from_name` (body, string): Sender display name - `staff_email` (body, string): Staff notification email - `config_name` (body, string): Config display name - `is_default` (body, boolean): Set as default config - `gmail_sender_user_id` (body, uuid): User UUID whose connected Gmail account owns the alias - `gmail_sender_alias` (body, string): Gmail send-as alias. Must already be registered in the connected Gmail account and assigned to this workspace. Use GET /email/capabilities to see valid aliases. ### DELETE /email/configs/:id Delete an email configuration. **Scopes:** `email-config:write` — _write_ Parameters: - `id` (path, uuid, required): Email config ID ### PATCH /email/threads/:id Update an email thread — mark as read/unread or change status (active, archived, spam). **Scopes:** `email:write` — _write_ Parameters: - `id` (path, uuid, required): Email thread UUID - `is_read` (body, boolean): Mark thread as read or unread - `status` (body, string): Thread status: active, archived, or spam ```bash curl -X PATCH \ "https://api.trustpager.com/functions/v1/api/v1/email/threads/THREAD_UUID" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"is_read":true}' ``` ### POST /email/threads/mark-read Bulk mark email threads as read. Use thread_ids for specific threads, or all:true to mark all unread threads read. Returns the count of threads updated. **Scopes:** `email:write` — _write_ Parameters: - `thread_ids` (body, uuid[]): Array of thread UUIDs to mark as read (max 100). Omit if using all:true. - `all` (body, boolean): Set true to mark ALL unread threads as read ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/email/threads/mark-read" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"all":true}' ``` --- ## Email Campaigns Create and send bulk broadcast email campaigns to segmented audiences. Supports tag-based and pipeline-based audience filters, delivery tracking (opens, clicks, bounces), and automatic unsubscribe management. ### GET /email-campaigns List all email campaigns for the company. Filter by status. **Scopes:** `email-campaigns:read` Parameters: - `status` (query, string): Filter by status: draft, scheduled, sending, sent, failed - `limit` (query, number): Max results (1-100) - `after` (query, string): Cursor for pagination ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/email-campaigns?status=sent&limit=10" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### POST /email-campaigns Create a new draft email campaign. Provide a name at minimum. Set subject, body_html, and segment_filter before sending. **Scopes:** `email-campaigns:write` — _write_ Parameters: - `name` (body, string, required): Campaign display name - `subject` (body, string): Email subject line - `intro_text` (body, string): Optional intro text shown above the body - `body_html` (body, string): Email body as HTML - `cta_text` (body, string): Call-to-action button label - `cta_url` (body, string): Call-to-action button URL - `show_reply_button` (body, boolean): Show reply button in footer (default true) - `email_config_id` (body, uuid): Sender identity UUID. If omitted, auto-resolves to the company default Postmark config. - `segment_filter` (body, object): Audience filter. See segment_filter schema below. - `scheduled_for` (body, string): ISO 8601 timestamp to schedule send. Null = manual send. ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/email-campaigns" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "April Newsletter", "subject": "What is new this April", "body_html": "

Hi {{first_name}},

Here is what we have been up to...

", "cta_text": "Read More", "cta_url": "https://example.com/blog", "segment_filter": { "tags": [{ "name": "newsletter" }], "exclude_unsubscribed": true } }' ``` ### GET /email-campaigns/:id Get a single campaign by ID with full content and delivery stats. **Scopes:** `email-campaigns:read` Parameters: - `id` (path, uuid, required): Campaign UUID ### PATCH /email-campaigns/:id Update a draft or scheduled campaign. Only draft and scheduled campaigns can be modified. **Scopes:** `email-campaigns:write` — _write_ Parameters: - `id` (path, uuid, required): Campaign UUID - `name` (body, string): Campaign name - `subject` (body, string): Email subject - `intro_text` (body, string): Intro text - `body_html` (body, string): Email body HTML - `cta_text` (body, string): CTA button label - `cta_url` (body, string): CTA button URL - `show_reply_button` (body, boolean): Show reply button - `email_config_id` (body, uuid): Sender config UUID - `segment_filter` (body, object): Audience filter - `scheduled_for` (body, string): Schedule timestamp (ISO 8601) ### DELETE /email-campaigns/:id Delete a campaign. Only draft or failed campaigns can be deleted. **Scopes:** `email-campaigns:delete` — _write_ Parameters: - `id` (path, uuid, required): Campaign UUID ### POST /email-campaigns/:id/send Send a campaign immediately via Postmark broadcast stream. Campaign must be in draft or scheduled status, must have subject and body_html set, and must match at least one contact. Resolves audience, batches sends (up to 500 per batch), and records per-recipient status. This action cannot be undone. **Scopes:** `email-campaigns:write` — _write_ Parameters: - `id` (path, uuid, required): Campaign UUID ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/email-campaigns/campaign-uuid-here/send" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### GET /email-campaigns/:id/preview-audience Preview the audience that would receive this campaign. Returns total count and up to 50 sample contacts. Use before sending to confirm the segment filter is correct. **Scopes:** `email-campaigns:read` Parameters: - `id` (path, uuid, required): Campaign UUID ### GET /email-campaigns/:id/recipients List per-recipient delivery records for a campaign. Filter by status or search by email. Statuses: queued, sent, delivered, opened, clicked, bounced, failed. **Scopes:** `email-campaigns:read` Parameters: - `id` (path, uuid, required): Campaign UUID - `status` (query, string): Filter by recipient status - `search` (query, string): Search by email address - `limit` (query, number): Max results (1-100) - `after` (query, string): Cursor for pagination ### GET /email-campaigns/unsubscribes List all email unsubscribe records for the company. Includes reason (link_click, hard_bounce, complaint, unsubscribe_header) and the campaign that triggered it. **Scopes:** `email-campaigns:read` Parameters: - `limit` (query, number): Max results - `after` (query, string): Cursor for pagination --- ## SMS Send and receive SMS messages. View conversations and message history. ### GET /sms/conversations List SMS conversations. Returns involved_user_ids showing which team members have interacted. **Scopes:** `sms:read` ### GET /sms/conversations/:id Retrieve an SMS conversation with involved_user_ids and linked entities. **Scopes:** `sms:read` Parameters: - `id` (path, uuid, required): Conversation ID ### GET /sms/conversations/:id/messages List messages in a conversation. **Scopes:** `sms:read` Parameters: - `id` (path, uuid, required): Conversation ID ### POST /sms/send Send an SMS message. If the recipient contact has sms_unsubscribed: true the send is silently suppressed and a 200 response is returned with status: "suppressed". The message is NOT delivered and does NOT appear in the conversation thread. To audit opted-out contacts use GET /contacts?sms_unsubscribed=true. **Scopes:** `sms:send` — _write_ Parameters: - `to_number` (body, string, required): Recipient phone number in E.164 format (e.g. +61412345678) - `message` (body, string, required): SMS message body - `phone_number_id` (body, uuid): Sender phone number ID. Uses company default if omitted. ### PATCH /sms/conversations/:id Update an SMS conversation — set unread_count to 0 to mark as read, or change status. **Scopes:** `sms:write` — _write_ Parameters: - `id` (path, uuid, required): Conversation UUID - `unread_count` (body, number): Set to 0 to mark conversation as read - `status` (body, string): Conversation status (e.g. active, archived) ### POST /sms/conversations/mark-read Bulk mark SMS conversations as read. Use conversation_ids for specific conversations, or all:true to mark all unread conversations read. Returns the count of conversations updated. **Scopes:** `sms:write` — _write_ Parameters: - `conversation_ids` (body, uuid[]): Array of conversation UUIDs to mark as read (max 100). Omit if using all:true. - `all` (body, boolean): Set true to mark ALL unread conversations as read --- ## Phone Manage phone numbers and call logs. Search for available numbers to purchase. ### GET /phone/numbers List company phone numbers. **Scopes:** `phone:read` ### POST /phone/numbers/search Search available phone numbers to purchase. **Scopes:** `phone:read` Parameters: - `country` (body, string): Country code (default: AU) - `area_code` (body, string): Area code filter ### POST /phone/numbers/buy Purchase a phone number. **Scopes:** `phone:write` — _write_ Parameters: - `phone_number` (body, string, required): Phone number to purchase ### DELETE /phone/numbers/:id Release a phone number. **Scopes:** `phone:write` — _write_ Parameters: - `id` (path, uuid, required): Phone number ID ### GET /phone/numbers/:id Get a specific phone number by ID. **Scopes:** `phone:read` Parameters: - `id` (path, uuid, required): Phone number ID ### PATCH /phone/numbers/:id Update phone number settings. Settable fields: friendly_name, transfer_number, inbound_voice_agent_config_id (voice_agent_config UUID or null), outbound_voice_agent_config_id (voice_agent_config UUID or null). When either agent assignment field is included, the API immediately pushes the change to Retell and returns retell_last_synced_at / retell_last_sync_error. Retell sync failure does NOT roll back the DB write. **Scopes:** `phone:write` — _write_ Parameters: - `id` (path, uuid, required): Phone number ID - `friendly_name` (body, string): Display name - `transfer_number` (body, string): Call forwarding number in E.164 format - `inbound_voice_agent_config_id` (body, string|null): voice_agent_config UUID for answering inbound calls. Pass null to clear. - `outbound_voice_agent_config_id` (body, string|null): voice_agent_config UUID for outbound caller ID. Pass null to clear. ### POST /phone/numbers/:id/release Release a phone number. **Scopes:** `phone:write` — _write_ Parameters: - `id` (path, uuid, required): Phone number ID ### GET /phone/call-logs List phone call logs. **Scopes:** `calls:read` ### GET /phone/addresses List regulatory addresses for phone compliance. **Scopes:** `phone:read` ### POST /phone/addresses Create a regulatory address for phone compliance. **Scopes:** `phone:write` — _write_ ### GET /phone/bundles List regulatory bundles for phone compliance. **Scopes:** `phone:read` ### POST /phone/bundles Create a regulatory bundle. **Scopes:** `phone:write` — _write_ ### POST /phone/bundles/:id/submit Submit a regulatory bundle for review. **Scopes:** `phone:write` — _write_ Parameters: - `id` (path, uuid, required): Bundle ID ### GET /phone/bundles/:id Get a single regulatory bundle with live compliance state. Returns the DB record plus live provider detail and evaluation history (per-requirement pass/fail breakdown). Use this to diagnose why a bundle was rejected and see which compliance requirements failed. **Scopes:** `phone:read` Parameters: - `id` (path, uuid, required): Bundle UUID --- ## Agents Manage AI voice agents and text agents under the unified /agents endpoint. Use ?type=voice or ?type=text to filter listings. The type field is required in the create body. ### GET /agents?type=voice|text List agents. type query param is required: "voice" for voice agents, "text" for text agents. **Scopes:** `voice-agents:read` Parameters: - `type` (query, string, required): Agent type to list: "voice" or "text" ### GET /agents/:id Retrieve an agent by ID. Auto-detects type (voice or text) from the ID. **Scopes:** `voice-agents:read` Parameters: - `id` (path, uuid, required): Agent ID ### POST /agents Create an agent. Include "type": "voice" or "type": "text" in the request body. For voice: agent_name is required. For text: name is required. **Scopes:** `voice-agents:write` — _write_ Parameters: - `type` (body, string, required): Agent type: "voice" or "text" - `agent_name` (body, string): Agent name (required for voice agents) - `name` (body, string): Agent name (required for text agents) - `language` (body, string): Language code (voice only) - `voice_id` (body, string): Voice ID (voice only) ### PATCH /agents/:id Update an agent. Auto-detects type from the ID. **Scopes:** `voice-agents:write` — _write_ Parameters: - `id` (path, uuid, required): Agent ID ### DELETE /agents/:id Delete an agent. Auto-detects type from the ID. **Scopes:** `voice-agents:write` — _write_ Parameters: - `id` (path, uuid, required): Agent ID ### POST /agents/:id/sync Sync a voice agent configuration with Retell (voice only). **Scopes:** `voice-agents:write` — _write_ Parameters: - `id` (path, uuid, required): Voice agent ID ### POST /agents/:id/update-flow Save a conversation flow draft to Retell (voice only). Requires the full ConversationFlow object in the "flow" field. Does not go live until /publish is called. **Scopes:** `voice-agents:write` — _write_ Parameters: - `id` (path, uuid, required): Voice agent ID - `flow` (body, object, required): Full ConversationFlow object ```bash {"flow": {"global_prompt": "...", "nodes": [...], "tools": [...]}} ``` ### POST /agents/:id/update-settings Update voice and behavior settings on the voice provider without touching the conversation flow (voice only). Accepted fields: voice_id, voice_model, language, fallback_voice_ids, voice_speed, voice_temperature, volume, responsiveness, interruption_sensitivity, enable_backchannel, backchannel_frequency, normalize_for_speech, max_call_duration_ms, end_call_after_silence_ms, reminder_trigger_ms, reminder_max_count, ambient_sound, ambient_sound_volume, data_storage_setting. **Scopes:** `voice-agents:write` — _write_ Parameters: - `id` (path, uuid, required): Voice agent ID - `settings` (body, object, required): Settings fields to update ```bash {"settings": {"voice_id": "11labs-Adrian", "language": "en-AU", "voice_speed": 1.1, "responsiveness": 0.8}} ``` ### POST /agents/:id/publish Publish the voice agent draft to live on Retell. Makes the current draft (flow + settings) active (voice only). **Scopes:** `voice-agents:write` — _write_ Parameters: - `id` (path, uuid, required): Voice agent ID ```bash {} ``` ### POST /agents/:id/call Initiate an outbound voice call using this agent (voice only). from_number is optional -- if omitted the API falls back to the agent's first configured outbound number (phone_numbers.outbound[0]). Returns a validation error if neither from_number nor an outbound number is configured. Optionally pass dynamic_variables (object) to inject variables into the agent at call start. The response includes a warnings array if expected dynamic variables are missing. **Scopes:** `calls:initiate` — _write_ Parameters: - `id` (path, uuid, required): Voice agent ID - `to_number` (body, string, required): Destination phone number (E.164) - `from_number` (body, string): Caller ID phone number (E.164). If omitted, falls back to agent's phone_numbers.outbound[0]. - `dynamic_variables` (body, object): Key-value pairs injected into the agent at call start (e.g. {"customer_name": "Jane"}). Check agent's default_dynamic_variables for expected keys. ```bash {"to_number": "+61412345678", "dynamic_variables": {"customer_name": "Jane Smith", "email": "jane@example.com"}} ``` ### GET /agents/:id/calls List call logs for a voice agent (voice only). **Scopes:** `calls:read` Parameters: - `id` (path, uuid, required): Voice agent ID ### GET /agents/calls/:callId Get detailed call log including transcript (voice only). **Scopes:** `calls:read` Parameters: - `callId` (path, uuid, required): Call log ID ### GET /agents/:id/website-config List website configs for a voice agent (voice only). **Scopes:** `voice-agents:read` Parameters: - `id` (path, uuid, required): Voice agent ID ### POST /agents/:id/website-config Create a website config for a voice agent. website_id is required (voice only). **Scopes:** `voice-agents:write` — _write_ Parameters: - `id` (path, uuid, required): Voice agent ID - `website_id` (body, uuid, required): Website ID ### PATCH /agents/:id/website-config/:configId Update a voice agent website config (voice only). **Scopes:** `voice-agents:write` — _write_ Parameters: - `id` (path, uuid, required): Voice agent ID - `configId` (path, uuid, required): Config ID ### DELETE /agents/:id/website-config/:configId Delete a voice agent website config (voice only). **Scopes:** `voice-agents:write` — _write_ Parameters: - `id` (path, uuid, required): Voice agent ID - `configId` (path, uuid, required): Config ID ### GET /agents/:id/outbound-config List outbound configs for a voice agent (voice only). **Scopes:** `voice-agents:read` Parameters: - `id` (path, uuid, required): Voice agent ID ### POST /agents/:id/outbound-config Create an outbound config for a voice agent. website_id is required (voice only). **Scopes:** `voice-agents:write` — _write_ Parameters: - `id` (path, uuid, required): Voice agent ID - `website_id` (body, uuid, required): Website ID ### PATCH /agents/:id/outbound-config/:configId Update a voice agent outbound config (voice only). **Scopes:** `voice-agents:write` — _write_ Parameters: - `id` (path, uuid, required): Voice agent ID - `configId` (path, uuid, required): Config ID ### DELETE /agents/:id/outbound-config/:configId Delete a voice agent outbound config (voice only). **Scopes:** `voice-agents:write` — _write_ Parameters: - `id` (path, uuid, required): Voice agent ID - `configId` (path, uuid, required): Config ID ### GET /agents/:id/responses List responses generated by a text agent (text only). **Scopes:** `voice-agents:read` Parameters: - `id` (path, uuid, required): Text agent ID ### POST /agents/:id/provision Recover an orphan voice agent row (created locally with a UUID agent_id before the provisioning fix). Registers the agent with the voice provider and updates the local row with the real agent_id. Guard: rejects if the agent_id already starts with "agent_" (already provisioned). After provisioning, call /update-flow and /publish to go live. **Scopes:** `voice-agents:write` — _write_ Parameters: - `id` (path, uuid, required): Voice agent ID (local row UUID) ```bash curl -X POST "https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/agents/agent-uuid/provision" -H "Authorization: Bearer tp_live_..." ``` ### POST /agents/:id/apply-template Stamp the default FinalPiece voice agent profile (prompt, settings, knowledge base) onto a provisioned agent, OR copy the full configuration from another source agent. Pass source_agent_id to copy from a specific agent; omit it to apply the platform default profile. Overwrites the conversation flow and settings in place -- the agent must be provisioned first (agent_id starts with "agent_"). **Scopes:** `voice-agents:write` — _write_ Parameters: - `id` (path, uuid, required): Target voice agent ID - `source_agent_id` (body, string): UUID of a source voice agent to copy from. Omit to apply the platform default profile. ```bash curl -X POST "https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/agents/agent-uuid/apply-template" -H "Authorization: Bearer tp_live_..." -H "Content-Type: application/json" -d "{}" ``` --- ## Agent Knowledge Bases Manage Retell-backed knowledge bases for voice agents. Each KB stores documents that agents can retrieve during calls. KBs can be seeded from help center articles or populated with custom content. Required scopes: voice-kbs:read, voice-kbs:write, voice-kbs:delete. NOTE: Retell KB write API (create and add-sources) is occasionally unavailable -- if create or add_doc returns a 500, this is a Retell-side outage, not a TrustPager bug. ### GET /voice-agent-kbs List all knowledge bases for the workspace. **Scopes:** `voice-kbs:read` ### GET /voice-agent-kbs/:id Get a single knowledge base by ID, including all document records. **Scopes:** `voice-kbs:read` Parameters: - `id` (path, uuid, required): Knowledge base UUID ### POST /voice-agent-kbs Create a new knowledge base. source="help_center" seeds it with all help center articles automatically. source="custom" (default) starts empty or accepts initial_texts. WARNING: Retell KB create API returns 500 intermittently -- this is a Retell-side outage. **Scopes:** `voice-kbs:write` — _write_ Parameters: - `name` (body, string, required): Knowledge base name - `description` (body, string): Optional description - `source` (body, string): "help_center" to seed from articles, or "custom" (default) for manual content - `initial_texts` (body, array): Initial documents for custom KBs. Each item: {title: string, text: string}. Ignored when source="help_center". ```bash {"name": "Product FAQ", "source": "custom", "initial_texts": [{"title": "Pricing", "text": "Our pricing starts at $49/month..."}]} ``` ### DELETE /voice-agent-kbs/:id Delete a knowledge base. Removes it from Retell (best-effort, tolerates 404) and deletes all local document rows. Does NOT detach from voice agents -- detach first if needed. **Scopes:** `voice-kbs:delete` — _write_ Parameters: - `id` (path, uuid, required): Knowledge base UUID ### GET /voice-agent-kbs/:kb_id/documents List all document records tracked for a knowledge base. Each row has a retell_doc_id, source_type (help_center_article or manual), optional source_id (article UUID), title, and content_hash. **Scopes:** `voice-kbs:read` Parameters: - `kb_id` (path, uuid, required): Knowledge base UUID ### POST /voice-agent-kbs/:kb_id/documents Add a manually written document to a knowledge base. Pushes the text to Retell and stores a local tracking row. WARNING: Retell add-sources API returns 500 intermittently. **Scopes:** `voice-kbs:write` — _write_ Parameters: - `kb_id` (path, uuid, required): Knowledge base UUID - `title` (body, string, required): Document title - `text` (body, string, required): Document content ```bash {"title": "Refund Policy", "text": "We offer a 30-day money-back guarantee..."} ``` ### DELETE /voice-agent-kbs/:kb_id/documents/:doc_id Remove a document from a knowledge base. Deletes the Retell source (best-effort) and the local tracking row. doc_id is the local UUID from the documents list, not the Retell source ID. **Scopes:** `voice-kbs:delete` — _write_ Parameters: - `kb_id` (path, uuid, required): Knowledge base UUID - `doc_id` (path, uuid, required): Document UUID (local ID from list endpoint) ### POST /voice-agent-kbs/:kb_id/sync-help-center Sync all help center articles into a knowledge base. Adds new articles, updates changed articles (by content hash), removes deleted articles, skips unchanged ones. Returns added/updated/unchanged/removed/failed counts. **Scopes:** `voice-kbs:write` — _write_ Parameters: - `kb_id` (path, uuid, required): Knowledge base UUID ```bash {} ``` ### POST /voice-agent-kbs/:kb_id/attach/:agent_id Attach a knowledge base to a voice agent conversation flow. Reads the current flow from Retell, adds the KB ID to knowledge_base_ids (deduped), and saves the updated draft. The agent must be published separately for the change to go live. Returns already_attached:true if already linked. **Scopes:** `voice-kbs:write` — _write_ Parameters: - `kb_id` (path, uuid, required): Knowledge base UUID - `agent_id` (path, uuid, required): Voice agent UUID ```bash {} ``` --- ## Text Agents Text agents are now part of the unified /agents endpoint. Use GET /agents?type=text to list text agents. See the Agents resource group for full documentation. ### GET /agents?type=text List all text agents. Use the unified /agents endpoint with type=text. **Scopes:** `voice-agents:read` ### GET /agents/:id Retrieve a text agent. Auto-detects type. **Scopes:** `voice-agents:read` Parameters: - `id` (path, uuid, required): Text agent ID ### POST /agents Create a text agent. Include "type": "text" and "name" in the request body. **Scopes:** `voice-agents:write` — _write_ Parameters: - `type` (body, string, required): Must be "text" - `name` (body, string, required): Agent name ### PATCH /agents/:id Update a text agent. Auto-detects type. **Scopes:** `voice-agents:write` — _write_ Parameters: - `id` (path, uuid, required): Text agent ID ### DELETE /agents/:id Delete a text agent. Auto-detects type. **Scopes:** `voice-agents:write` — _write_ Parameters: - `id` (path, uuid, required): Text agent ID ### GET /agents/:id/responses List responses generated by a text agent. **Scopes:** `voice-agents:read` Parameters: - `id` (path, uuid, required): Text agent ID --- ## Transcripts Transcribe call recordings and meetings, view results, and access AI coaching. Use POST /transcripts/transcribe to kick off transcription from any audio URL. Coaching results are under /transcripts/coaching. ### POST /transcripts/transcribe Kick off an audio transcription via TrustPager AI (Whisper). Pass a publicly fetchable recording URL or a Twilio recording URL plus recording_auth. The transcript lands in /inbox/phone-calls (type=phone_call or legacy alias type=call) or /inbox/meetings (type=meeting) and auto-links to contacts and opportunities matching participant email or phone number. Files over 25 MB are automatically chunked at MP3 frame boundaries. Requires transcripts:write scope. Costs 12 credits per minute. **Scopes:** `transcripts:write` — _write_ Parameters: - `recording_url` (body, string, required): Publicly fetchable audio URL (https), or a private R2 URL for recordings already rehosted by TrustPager. For raw Twilio recording URLs also pass recording_auth. - `title` (body, string, required): Display title for the transcript, e.g. "Outbound call - Edward Anderson". - `type` (body, string, required): Transcript type: phone_call (or legacy alias call) lands in /inbox/phone-calls; meeting lands in /inbox/meetings; voicemail lands in /inbox. - `source` (body, string): Producer label for filtering (e.g. manual-upload, twilio-call, field-memo). Defaults to api. - `source_id` (body, string): Foreign key in the producer system (Twilio call_sid, bot_id, etc.). Stored on metadata. - `occurred_at` (body, string): ISO timestamp when the audio was captured. Defaults to now. - `recording_auth` (body, string): Basic-auth credentials formatted USER:PASSWORD. Required for raw Twilio recording URLs (pass ACCOUNT_SID:AUTH_TOKEN). - `participants` (body, array): Participant objects with email, phone, name, and optional role (caller or callee). Used for entity matching and activity creation. role=caller/callee is used by the phone_call_groups view to identify the external phone on outbound calls. - `model` (body, string): Override the workspace transcription model: whisper-1, gpt-4o-transcribe, gpt-4o-mini-transcribe. whisper-1 is used internally regardless (required for verbose_json diarization). - `metadata` (body, object): Free-form metadata attached to the transcript row. - `created_by` (body, string): User UUID for activity attribution when calling from a service-role context. - `existing_transcript_id` (body, string): UUID of an existing transcript to overwrite. Use for fallback or retry flows. ### GET /transcripts List all transcripts with optional filters for type, source, transcription_status, date range, participant email, or booking_id. Each record includes transcription_status (not_applicable, pending, complete) and booking_id (populated for TrustPager Notetaker recordings, links the transcript to a scheduling_bookings row). Filter type=phone_call to see browser softphone call transcripts. **Scopes:** `calls:read` Parameters: - `type` (query, string): Filter by type: phone_call (browser softphone + Twilio calls), meeting (Notetaker/Zoom), voicemail. Legacy alias call is equivalent to phone_call. - `source` (query, string): Filter by source (retell, zoom, recall, manual, etc.). Use "recall" for TrustPager Notetaker recordings. - `transcription_status` (query, string): Filter by transcription status: not_applicable, pending, or complete. Use complete to find transcripts with text ready for analysis. - `occurred_after` (query, string): ISO date filter - `occurred_before` (query, string): ISO date filter - `participant_email` (query, string): Filter by participant email. Returns transcripts where any participant object has this email. - `booking_id` (query, uuid): Filter by scheduling booking UUID. Returns the transcript recorded during that specific booking via TrustPager Notetaker. - `limit` (query, number): Max results (1-100, default 25) - `cursor` (query, string): Cursor for next page ### GET /transcripts/:id Retrieve a transcript with full text, participants, linked entities, and transcription_status. Check transcription_status=complete before using transcript_text for AI analysis. **Scopes:** `calls:read` Parameters: - `id` (path, uuid, required): Transcript ID ### DELETE /transcripts/:id Delete a transcript and its linked entity relationships. **Scopes:** `calls:read` — _write_ Parameters: - `id` (path, uuid, required): Transcript ID ### GET /transcripts/:id/coaching Get all coaching results for a specific transcript. **Scopes:** `calls:read` Parameters: - `id` (path, uuid, required): Transcript ID ### GET /transcripts/coaching List all AI coaching results across all transcripts. Filter by framework, source_type, or team_member_id. **Scopes:** `calls:read` Parameters: - `framework` (query, string): Filter by coaching framework (SPIN, BANT, Challenger, MEDDIC) - `source_type` (query, string): Filter by source type - `team_member_id` (query, uuid): Filter by team member UUID ### GET /transcripts/coaching/:id Retrieve a specific AI coaching result by ID. **Scopes:** `calls:read` Parameters: - `id` (path, uuid, required): Coaching result ID --- ## Document Templates Manage document templates with sections for proposals, contracts, and more. ### GET /document-templates List all document templates. **Scopes:** `documents:read` ### GET /document-templates/:id Retrieve a document template with sections. **Scopes:** `documents:read` Parameters: - `id` (path, uuid, required): Template ID ### POST /document-templates Create a document template. **Scopes:** `documents:write` — _write_ Parameters: - `name` (body, string, required): Template name ### PATCH /document-templates/:id Update a document template. **Scopes:** `documents:write` — _write_ Parameters: - `id` (path, uuid, required): Template ID ### DELETE /document-templates/:id Delete a document template. **Scopes:** `documents:write` — _write_ Parameters: - `id` (path, uuid, required): Template ID ### POST /document-templates/:id/duplicate Duplicate a document template. **Scopes:** `documents:write` — _write_ Parameters: - `id` (path, uuid, required): Template ID ### GET /document-templates/:id/sections List sections for a document template. **Scopes:** `documents:read` Parameters: - `id` (path, uuid, required): Template ID ### POST /document-templates/:id/sections Add a section to a template. The "type" field accepts kebab-case (e.g. "signer-input"), PascalCase aliases (e.g. "SignerInput"), and underscore aliases (e.g. "signer_input") -- all normalized server-side. Valid types: cover-page, text-block, two-column, table, product-table, product-showcase, image-block, signature-block, signer-input, divider, page-break, quote-callout, terms-conditions, guarantee, doc-header, doc-footer, dynamic-pricing, dynamic-products, dynamic-timeline, dynamic-executive-summary, dynamic-needs-solutions, dynamic-recommendations, dynamic-callout, dynamic-guarantee, rich-content, product-brochure. **Scopes:** `documents:write` — _write_ Parameters: - `id` (path, uuid, required): Template ID - `type` (body, string, required): Section type. Use "signer-input" for a field the signer fills in at signing time (not the sender). The signer-input content shape: { label: string, fieldType: "text"|"textarea"|"date"|"number", required: boolean, placeholder?: string, signerEmail?: string }. Leave signerEmail blank to assign to the primary signer. - `content` (body, object): Section content JSON. Shape varies by type. - `styling` (body, object): Section styling overrides (backgroundColor, paddingTop, paddingBottom). - `order_index` (body, number): Display order (ascending). - `is_visible` (body, boolean): Whether the section is visible (default true). ### PATCH /document-templates/:id/sections/:sectionId Update a template section. **Scopes:** `documents:write` — _write_ Parameters: - `id` (path, uuid, required): Template ID - `sectionId` (path, uuid, required): Section ID ### DELETE /document-templates/:id/sections/:sectionId Delete a template section. **Scopes:** `documents:write` — _write_ Parameters: - `id` (path, uuid, required): Template ID - `sectionId` (path, uuid, required): Section ID ### POST /document-templates/:id/sections/reorder Reorder sections in a document template. **Scopes:** `documents:write` — _write_ Parameters: - `id` (path, uuid, required): Template ID - `section_ids` (body, string[], required): Ordered array of section UUIDs --- ## Documents Manage CRM documents (uploaded files). Supports folders, downloads via signed URLs, and CRUD operations. ### GET /documents List all documents. Supports folder, document_type, and search filters. **Scopes:** `documents:read` Parameters: - `folder` (query, string): Filter by folder name - `document_type` (query, string): Filter by document type - `search` (query, string): Search by name ### GET /documents/:id Retrieve a document. **Scopes:** `documents:read` Parameters: - `id` (path, uuid, required): Document ID ### POST /documents Create a document record. name is required. **Scopes:** `documents:write` — _write_ Parameters: - `name` (body, string, required): Document name - `folder` (body, string): Folder name - `document_type` (body, string): Document type ### DELETE /documents/:id Delete a document. **Scopes:** `documents:write` — _write_ Parameters: - `id` (path, uuid, required): Document ID ### GET /documents/:id/download Get a signed download URL for a document (expires in 60 seconds). **Scopes:** `documents:read` Parameters: - `id` (path, uuid, required): Document ID ### GET /documents/folders List all unique document folder names. **Scopes:** `documents:read` --- ## Signing Manage e-signature envelopes. Send documents for signing and track completion status. ### GET /signing/envelopes List signing envelopes. Response includes slug for human-readable URLs. **Scopes:** `documents:read` ### GET /signing/envelopes/:id Retrieve a signing envelope with recipients. Each recipient includes input_values (object keyed by signer-input section ID) which is populated once the signer submits their values at signing time. Empty object until signed. **Scopes:** `documents:read` Parameters: - `id` (path, uuid, required): Envelope ID ### POST /signing/send Send a document for signing. Renders the document template sections server-side into a PDF, creates a signing envelope, and emails all signers. If the template contains signer-input sections, signers will see a fillable form panel below the PDF and their typed values are stored on the recipient as input_values after signing. A slug is auto-generated from the document title. **Scopes:** `signing:send` — _write_ Parameters: - `template_id` (body, uuid, required): Document template ID - `deal_id` (body, uuid): Deal ID -- links deal data for variable resolution and dynamic sections - `signers` (body, array, required): Array of signers with name (required), email (required), role (optional), order_index (optional) - `personal_message` (body, string): Personal message included in the signing email ### POST /signing/envelopes/:id/void Void a signing envelope to cancel all pending signatures. **Scopes:** `signing:send` — _write_ Parameters: - `id` (path, uuid, required): Envelope ID ### POST /signing/envelopes/:id/resend Resend signing email to a specific recipient. **Scopes:** `signing:send` — _write_ Parameters: - `id` (path, uuid, required): Envelope ID - `recipient_id` (body, uuid, required): Recipient ID to resend to --- ## Forms Create form templates, manage fields, send forms to contacts, and view submissions. Fields support CRM Variable Injection - map a field to a CRM variable so submitted values are written back to the linked deal, contact, or account before automations run. ### GET /forms/templates List all form templates. **Scopes:** `forms:read` ### GET /forms/templates/:id Retrieve a form template with all fields inline. **Scopes:** `forms:read` Parameters: - `id` (path, uuid, required): Template ID ### POST /forms/templates Create a form template. **Scopes:** `forms:write` — _write_ Parameters: - `name` (body, string, required): Form name - `description` (body, string): Form description ### PATCH /forms/templates/:id Update a form template. Use settings to configure the completion screen, notification addresses, and PDF archive behaviour. **Scopes:** `forms:write` — _write_ Parameters: - `id` (path, uuid, required): Template ID - `name` (body, string): Form name - `settings` (body, object): Template settings JSON. Completion keys: completionMessage (string), completionButtonEnabled (boolean), completionButtonText (string), completionButtonUrl (string), completionButtonNewTab (boolean, default true). Notification key: notifyEmails (string[] - email addresses to notify when this form is completed, overrides workspace default). PDF archive keys: archivePdfOnSubmit (boolean, default false - automatically render a PDF of each completed submission and file it in the Documents library), submissionFolder (string, default "Client Forms" - Documents library folder to archive the PDF into), submissionDocumentType (string, default "Other" - document_type tag on the archived PDF row). Merge with existing settings to avoid overwriting stepCount/stepHeaders. ### DELETE /forms/templates/:id Delete a form template. **Scopes:** `forms:write` — _write_ Parameters: - `id` (path, uuid, required): Template ID ### POST /forms/templates/:id/duplicate Duplicate a form template with all its fields. **Scopes:** `forms:write` — _write_ Parameters: - `id` (path, uuid, required): Template ID to duplicate ### POST /forms/templates/:id/archive Toggle archive status on a form template. **Scopes:** `forms:write` — _write_ Parameters: - `id` (path, uuid, required): Template ID ### GET /forms/templates/:id/fields List all fields for a form template, ordered by order_index. **Scopes:** `forms:read` Parameters: - `id` (path, uuid, required): Template ID ### POST /forms/templates/:id/fields Add a field to a form template. Use the content object to configure CRM Variable Injection. **Scopes:** `forms:write` — _write_ Parameters: - `id` (path, uuid, required): Template ID - `type` (body, string, required): Field type: text, textarea, number, date, select, checkbox, radio, file - `label` (body, string, required): Field label - `placeholder` (body, string): Placeholder text - `is_required` (body, boolean): Whether the field is required - `order_index` (body, number): Display order - `step_number` (body, number): Multi-step form step number - `content` (body, object): Field config JSON. For select/checkbox/radio: { options: [...] }. For CRM Variable Injection: { crm_variable: "contact.email", crm_variable_mode: "write" | "overwrite" }. Standard variables: contact.{email,phone,first_name,last_name,job_title,notes,address_line1,city,state,postal_code,country,date_of_birth}, account.{name,email,phone,website,industry,tax_number,notes,...}, deal.{name,value,currency,notes,lead_source,opportunity_type}. Custom fields: deal.metadata.{id}, account.metadata.{id}, contact.metadata.{id}. Mode "write" only writes if the CRM field is currently empty. Mode "overwrite" always writes. ```bash { "type": "text", "label": "Contact Email", "is_required": true, "content": { "crm_variable": "contact.email", "crm_variable_mode": "write" } } ``` ### PATCH /forms/templates/:id/fields/:fieldId Update a field on a form template. Use content to update CRM Variable Injection mapping. **Scopes:** `forms:write` — _write_ Parameters: - `id` (path, uuid, required): Template ID - `fieldId` (path, uuid, required): Field ID - `label` (body, string): Field label - `content` (body, object): Field config including optional crm_variable and crm_variable_mode ### DELETE /forms/templates/:id/fields/:fieldId Remove a field from a form template. **Scopes:** `forms:write` — _write_ Parameters: - `id` (path, uuid, required): Template ID - `fieldId` (path, uuid, required): Field ID ### POST /forms/templates/:id/fields/reorder Reorder fields within a form template by providing an ordered array of field IDs. **Scopes:** `forms:write` — _write_ Parameters: - `id` (path, uuid, required): Template ID - `field_ids` (body, string[], required): Ordered array of field UUIDs ### POST /forms/send Send a form to a recipient via email. Costs 3 credits. At least one of deal_id, contact_id, or customer_id is required to link the submission to a CRM entity. CRM Variable Injection fires on submission when any linkage is set -- deal.* wirings are skipped if no deal_id is provided, but contact.* and account.* wirings fire against the submission-level contact/customer. Notification cascade: notify_emails (this call) -> template notifyEmails -> workspace form_completion_notify_emails -> sender fallback. **Scopes:** `forms:send` — _write_ Parameters: - `template_id` (body, uuid, required): Form template ID - `recipient_email` (body, string, required): Recipient email address - `recipient_name` (body, string, required): Recipient full name - `deal_id` (body, uuid): Link to an opportunity. Preferred when the form is opportunity-scoped. Enables all CRM variable wirings including deal.* fields. - `contact_id` (body, uuid): Link to a contact. Use for standalone client-profile forms where no opportunity exists. contact.* and account.* wirings still fire. - `customer_id` (body, uuid): Link to a customer (account). account.* wirings fire even without a deal_id. - `personal_message` (body, string): Personal message included in the email - `expires_in_days` (body, number): Days before the form link expires - `notify_emails` (body, string[]): Email addresses to notify when this form is completed. Overrides template-level notifyEmails for this specific submission. Falls back to template notifyEmails, then workspace form_completion_notify_emails, then the sending user. ### POST /forms/templates/:id/wiring Bulk-update CRM variable wiring on a form template's fields in one atomic call. Optionally creates new workspace custom fields on company_settings before applying mappings -- useful for wiring AI-generated templates whose fields need new CRM targets. Fields not listed in mappings are untouched. Pass crm_variable: null to clear an existing wiring. Requires forms:write scope. **Scopes:** `forms:write` — _write_ Parameters: - `id` (path, uuid, required): Form template ID - `mappings` (body, object[]): Array of { field_id (uuid, required), crm_variable (string or null to clear), crm_variable_mode ("write" | "overwrite", default "overwrite") }. At least one of mappings or create_custom_fields must be provided. - `create_custom_fields` (body, object[]): Array of { label (string), type ("text"|"textarea"|"dropdown"|"number"|"datetime"|"checkbox"), entity ("deal"|"account"|"contact"), options (string[], required for dropdown) }. New fields are persisted to company_settings.custom_fields before mappings are applied, so you can immediately wire to them. ```bash { "mappings": [ { "field_id": "uuid-field-1", "crm_variable": "contact.first_name", "crm_variable_mode": "overwrite" }, { "field_id": "uuid-field-2", "crm_variable": "account.tax_number", "crm_variable_mode": "write" }, { "field_id": "uuid-field-3", "crm_variable": "contact.metadata.marital_status", "crm_variable_mode": "overwrite" } ], "create_custom_fields": [ { "label": "Marital Status", "type": "dropdown", "entity": "contact", "options": [ "Single", "Married", "De facto", "Divorced", "Widowed" ] } ] } ``` ### GET /forms/folders List form template folders. **Scopes:** `forms:read` ### POST /forms/folders Create a form template folder. name is required. **Scopes:** `forms:write` — _write_ Parameters: - `name` (body, string, required): Folder name ### PATCH /forms/folders/:id Update a form template folder. **Scopes:** `forms:write` — _write_ Parameters: - `id` (path, uuid, required): Folder ID ### DELETE /forms/folders/:id Delete a form template folder. **Scopes:** `forms:write` — _write_ Parameters: - `id` (path, uuid, required): Folder ID ### GET /forms/prefills List form prefills. Filter by template_id or deal_id. **Scopes:** `forms:read` Parameters: - `template_id` (query, uuid): Filter by template - `deal_id` (query, uuid): Filter by deal ### POST /forms/prefills Create a form prefill. template_id is required. **Scopes:** `forms:write` — _write_ Parameters: - `template_id` (body, uuid, required): Form template ID - `deal_id` (body, uuid): Deal ID - `status` (body, string): Prefill status - `additional_context` (body, string): Additional context for AI prefilling ### GET /forms/prefills/:id Retrieve a form prefill with its values. **Scopes:** `forms:read` Parameters: - `id` (path, uuid, required): Prefill ID ### POST /forms/prefills/:id/values Upsert prefill values for form fields. **Scopes:** `forms:write` — _write_ Parameters: - `id` (path, uuid, required): Prefill ID - `values` (body, object[], required): Array of { field_id, value, confidence?, reasoning? } ### GET /forms/submissions List form submissions. Filter by template_id, deal_id, contact_id, customer_id, or status. **Scopes:** `forms:read` ### GET /forms/submissions/:id Retrieve a form submission with all response data. **Scopes:** `forms:read` Parameters: - `id` (path, uuid, required): Submission ID ### POST /forms/submissions/:id/resend Resend a form submission email to the original recipient. **Scopes:** `forms:write` — _write_ Parameters: - `id` (path, uuid, required): Submission ID ### POST /forms/submissions/:id/void Void (expire) a form submission to prevent further responses. **Scopes:** `forms:write` — _write_ Parameters: - `id` (path, uuid, required): Submission ID ### DELETE /forms/submissions/:id Permanently delete a form submission and any uploaded files attached to its responses. Use delete_archived_pdf=true to also remove the auto-archived PDF from the Documents library (matched via source_form_submission_id). Unlike void, this removes the record entirely and cannot be undone. Requires forms:delete scope. **Scopes:** `forms:delete` — _write_ Parameters: - `id` (path, uuid, required): Submission ID - `delete_archived_pdf` (query, boolean): When true, also deletes the auto-archived PDF in the Documents library generated from this submission. Default false - the PDF is preserved. ### POST /forms/submissions/:id/render-pdf Render a completed form submission to PDF and archive it in the Documents library. The PDF is auto-attached to the linked opportunity, contact, or account. Submission must have status "completed". Costs 1 credit per rendered page (typical intake form = 1-3 pages). All override params fall back to the form template settings, then platform defaults. **Scopes:** `forms:write` — _write_ Parameters: - `id` (path, uuid, required): Submission ID (must be a completed submission) - `folder` (body, string): Documents library folder for the PDF. Overrides the template submissionFolder setting. Default: "Client Forms". - `document_type` (body, string): document_type tag on the resulting Documents row. Common values: "Other", "Invoice", "Proposal", "Agreement", "Report". Default: template submissionDocumentType setting, then "Other". - `name` (body, string): Filename base for the PDF (without .pdf extension). A timestamp suffix is appended automatically. Default: " - ". - `description` (body, string): Description on the Documents row. Default: "Archived form submission - completed ". ```bash {} ``` --- ## Websites Manage TrustPager-hosted websites, pages, and page sections. ### GET /websites List all websites. **Scopes:** `websites:read` ### GET /websites/:id Retrieve a website. **Scopes:** `websites:read` Parameters: - `id` (path, uuid, required): Website ID ### POST /websites Create a website. name is required. **Scopes:** `websites:write` — _write_ Parameters: - `name` (body, string, required): Website name - `domain` (body, string): Custom domain ### PATCH /websites/:id Update a website. **Scopes:** `websites:write` — _write_ Parameters: - `id` (path, uuid, required): Website ID ### DELETE /websites/:id Delete a website. **Scopes:** `websites:write` — _write_ Parameters: - `id` (path, uuid, required): Website ID ### GET /websites/:id/pages List pages for a website. **Scopes:** `websites:read` Parameters: - `id` (path, uuid, required): Website ID ### POST /websites/:id/pages Create a page for a website. title and slug are required. **Scopes:** `websites:write` — _write_ Parameters: - `id` (path, uuid, required): Website ID - `title` (body, string, required): Page title - `slug` (body, string, required): URL slug - `page_type` (body, string): Page type ### GET /websites/:id/pages/:pageId Retrieve a page with its sections. **Scopes:** `websites:read` Parameters: - `id` (path, uuid, required): Website ID - `pageId` (path, uuid, required): Page ID ### PATCH /websites/:id/pages/:pageId Update a website page. **Scopes:** `websites:write` — _write_ Parameters: - `id` (path, uuid, required): Website ID - `pageId` (path, uuid, required): Page ID ### DELETE /websites/:id/pages/:pageId Delete a website page. **Scopes:** `websites:write` — _write_ Parameters: - `id` (path, uuid, required): Website ID - `pageId` (path, uuid, required): Page ID ### GET /websites/:id/pages/:pageId/sections List sections for a page. **Scopes:** `websites:read` Parameters: - `id` (path, uuid, required): Website ID - `pageId` (path, uuid, required): Page ID ### POST /websites/:id/pages/:pageId/sections Create a section on a page. type is required. **Scopes:** `websites:write` — _write_ Parameters: - `id` (path, uuid, required): Website ID - `pageId` (path, uuid, required): Page ID - `type` (body, string, required): Section type ### PATCH /websites/:id/pages/:pageId/sections/:sectionId Update a page section. **Scopes:** `websites:write` — _write_ Parameters: - `id` (path, uuid, required): Website ID - `pageId` (path, uuid, required): Page ID - `sectionId` (path, uuid, required): Section ID ### DELETE /websites/:id/pages/:pageId/sections/:sectionId Delete a page section. **Scopes:** `websites:write` — _write_ Parameters: - `id` (path, uuid, required): Website ID - `pageId` (path, uuid, required): Page ID - `sectionId` (path, uuid, required): Section ID ### POST /websites/:id/pages/:pageId/sections/reorder Reorder sections on a page. **Scopes:** `websites:write` — _write_ Parameters: - `id` (path, uuid, required): Website ID - `pageId` (path, uuid, required): Page ID - `section_ids` (body, string[], required): Ordered array of section UUIDs --- ## Order Forms Manage payment order forms connected to Stripe for product purchases. ### GET /order-forms List all order forms. **Scopes:** `websites:read` ### GET /order-forms/:id Retrieve an order form. **Scopes:** `websites:read` Parameters: - `id` (path, uuid, required): Order form ID ### POST /order-forms Create an order form. **Scopes:** `websites:write` — _write_ Parameters: - `name` (body, string, required): Form name ### PATCH /order-forms/:id Update an order form. **Scopes:** `websites:write` — _write_ Parameters: - `id` (path, uuid, required): Order form ID ### DELETE /order-forms/:id Delete an order form. **Scopes:** `websites:write` — _write_ Parameters: - `id` (path, uuid, required): Order form ID ### GET /order-forms/:id/logs List order form payment logs. **Scopes:** `websites:read` Parameters: - `id` (path, uuid, required): Order form ID --- ## Files Unified file storage endpoint for all file types. Use ?type=document (private PDFs), ?type=image (public CDN/R2 images), or ?type=secure (confidential private files). Single-resource routes (/files/:id) auto-detect the type. ### GET /files?type=document|image|secure List files. The "type" query parameter is required. "document" = private PDFs (25MB max), "image" = public CDN media (10MB max), "secure" = confidential private files (50MB max). **Scopes:** `files:read` Parameters: - `type` (query, string, required): File type: document, image, or secure - `folder` (query, string): Filter by folder name - `search` (query, string): Search by file name - `document_type` (query, string): Filter by document type (type=document only) - `file_category` (query, string): Filter by file category (type=secure only) - `category` (query, string): Filter by file category (type=image only) ### GET /files/:id Get file metadata. Auto-detects the file type (document, image, or secure) from the ID. Returns normalized fields: id, type, name, mime_type, file_size, folder, description, uploaded_by, created_at, plus type-specific fields (public_url for document, download_url for image, file_category for secure). **Scopes:** `files:read` Parameters: - `id` (path, uuid, required): File ID (any type) ### GET /files/:id/download Get a temporary signed download URL for any file type (expires in 60 seconds). Auto-detects type. For public R2 images, returns the permanent r2_url instead. **Scopes:** `files:read` Parameters: - `id` (path, uuid, required): File ID (any type) ### PUT /files/:id Update file metadata (name, description, folder, and type-specific fields). Auto-detects type. For documents: name, description, folder, document_type. For images: name, folder, description. For secure: name, description, folder, file_category. **Scopes:** `files:write` — _write_ Parameters: - `id` (path, uuid, required): File ID (any type) ### DELETE /files/:id Delete a file and remove it from storage. Auto-detects type. For images: removes from R2. For documents and secure files: removes from Supabase private storage. **Scopes:** `files:write` — _write_ Parameters: - `id` (path, uuid, required): File ID (any type) ### POST /files/upload Upload a file via base64 content. "type" determines storage backend: "image" stores on public R2/CDN (returns r2_url), "document" stores in private Supabase bucket, "secure" stores in private Supabase bucket with confidential access. Size limits: image=10MB, document=25MB, secure=50MB. **Scopes:** `files:write` — _write_ Parameters: - `base64` (body, string, required): Base64-encoded file content. Data URI prefix (e.g. "data:image/png;base64,") is accepted and auto-stripped. - `name` (body, string, required): Filename with extension (e.g. "report.pdf") - `type` (body, string, required): Storage type: "document", "image", or "secure" - `mime_type` (body, string): MIME type (default: application/octet-stream) - `folder` (body, string): Folder name to organise the file - `description` (body, string): Optional description - `category` (body, string): Category for image files (e.g. images, documents, videos) - `document_type` (body, string): Document type category (type=document only) - `file_category` (body, string): File category (type=secure only) ### POST /files/:id/publish Publish a private document to a public URL via R2. Creates a publicly accessible copy. Returns public_url. **Scopes:** `files:write` — _write_ Parameters: - `id` (path, uuid, required): Document file ID (type=document only) ### POST /files/:id/unpublish Remove the public URL for a previously published document. The file remains in private storage. **Scopes:** `files:write` — _write_ Parameters: - `id` (path, uuid, required): Document file ID (type=document only) ### POST /files/:id/make-public Move an image or secure file from private storage to the public CDN. For image files (type=image, is_private=true): the file is moved from private R2 to the public bucket and is_private is set to false -- the original private copy is removed. For secure files (type=secure): the file is copied from Supabase private storage to the public CDN and public_url is set -- the original private copy is retained. Returns the public URL of the published file. Use POST /files/:id/publish for documents (type=document). **Scopes:** `files:write` — _write_ Parameters: - `id` (path, uuid, required): File UUID (company_files row with is_private=true, or company_secure_files row) ### POST /files/:id/make-private Move an image or secure file from the public CDN back to private storage. For image files (type=image, is_private=false): the file is moved from the public bucket to private R2 and is_private is set to true -- the public URL is no longer accessible. For secure files (type=secure): the public CDN copy is deleted and public_url is cleared -- the original private copy is retained. Use POST /files/:id/unpublish for documents (type=document). **Scopes:** `files:write` — _write_ Parameters: - `id` (path, uuid, required): File UUID (company_files row with is_private=false, or company_secure_files row with a public_url) ### GET /files/:id/signed-url Generate a short-lived signed URL for a private image file stored in private R2. Only applicable to image files (type=image) with is_private=true. The signed URL expires after 600 seconds (10 minutes) and allows temporary read access without making the file permanently public. Returns the signed URL, expiry timestamp, and MIME type. **Scopes:** `files:read` Parameters: - `id` (path, uuid, required): File UUID of a private image (company_files row with is_private=true) ### GET /files/folders?type=document|image|secure List distinct folder names for a given file type. Use type="document", "image", or "secure". **Scopes:** `files:read` Parameters: - `type` (query, string, required): File type to list folders for: document, image, or secure ### POST /files/folders?type=document|image|secure Create a new folder for a given file type. Pass type as a query parameter and name in the body. **Scopes:** `files:write` — _write_ Parameters: - `type` (query, string, required): File type: document, image, or secure - `name` (body, string, required): Folder name ### POST /files/bundle Bundle up to 50 files (any mix of document / image / secure types -- auto-detected) into a ZIP archive. Default mode ("binary") streams the ZIP back directly as application/zip (suitable for browser save dialogs). Pass ?response=url (or "response": "url" in the body) to stage the ZIP and return a 10-minute signed download URL instead -- use this mode with API agents that cannot consume a large binary response. Cap: 50 files / 200MB total decompressed. Files that cannot be fetched from storage are listed in a _manifest.txt included in the ZIP and in the skipped[] field of the url-mode response. Requires files:read scope (no write scope needed). **Scopes:** `files:read` Parameters: - `ids` (body, string[], required): Array of 1-50 file UUIDs to bundle. Mix of document, image, and secure types is fine -- each is auto-detected. - `filename` (body, string): Optional ZIP filename without extension. Defaults to "attachments_YYYY-MM-DD". A .zip suffix is always appended. - `response` (body, string): Response mode. "binary" (default) streams the ZIP directly. "url" returns a JSON body with a signed URL good for 10 minutes. Also accepted as a query parameter: ?response=url. ### POST /images/optimize Generate web-optimized WebP variants (mobile 640px, tablet 1024px, desktop 1920px) from an existing image file or a public image URL. RECOMMENDED WORKFLOW: generate an image with POST /ai/generate-image (returns lossless PNG) then call this endpoint to produce lean WebP variants ready for web delivery. Pass file_id to optimize an existing company_files entry -- variants are persisted to R2 and website_images rows are created, and the original file's is_optimized_image metadata is updated. Pass image_url for ad-hoc optimization of an external image -- variants are uploaded to R2 but no website_images rows are written. Idempotent: re-optimizing a file_id replaces previous variants. Costs credits when the platform prices the "image_optimize" feature; otherwise free. **Scopes:** `files:write` — _write_ Parameters: - `file_id` (body, uuid): UUID of an existing company_files row (preferred). The original image is fetched from its R2 URL, three WebP variants are generated, persisted, and the original's is_optimized_image metadata is updated. Provide this OR image_url -- not both. - `image_url` (body, string): Public http(s) URL of an image to optimize ad-hoc. Variants are uploaded to R2 and returned, but no website_images rows are written (no company_files entry to link to). Use file_id when possible. Provide this OR file_id -- not both. - `variants` (body, string[]): Subset of variants to generate. Default: all three -- ["mobile", "tablet", "desktop"]. Pass a subset to skip unneeded sizes. - `quality` (body, number): WebP quality override 1-100. When set, applies the same value to all requested variants (overrides per-variant defaults: mobile=80, tablet=85, desktop=90). --- ## Notepads Manage rich-text notepads organized in folders. Supports markdown input, iterative editing (append/prepend/section patches), per-record visibility, and ACL-based access control for restricted notes. ### GET /notepads List all notepads. Supports filters for folder_id, folder (legacy name), visibility, is_favorite, and title search. **Scopes:** `notepads:read` Parameters: - `folder_id` (query, uuid): Filter by folder UUID - `folder` (query, string): Filter by folder name (legacy) - `visibility` (query, string): Filter by visibility: all_users, admins_only, creator_only, restricted - `is_favorite` (query, boolean): Filter by favourite status - `search` (query, string): Search by title (case-insensitive) ### GET /notepads/:id Retrieve a notepad including full content and visibility. **Scopes:** `notepads:read` Parameters: - `id` (path, uuid, required): Notepad ID ### POST /notepads Create a notepad. Content accepts plain text or markdown (auto-converted to rich editor format) or a { html, tiptapJson } object. Defaults to visibility="all_users" -- pass "restricted" to restrict access. For "restricted" notes, add ACL entries via POST /notepads/:id/acl. Pass customer_id or contact_id to automatically link the notepad to a Company or Contact page (appends its ID to that entity's notepads array). folder_id and folder string are synced bidirectionally -- set either one and the other is auto-populated. Response is slim by default (id, title, folder, folder_id, visibility, is_favorite, timestamps) -- pass return_content: true to receive the full rendered HTML. **Scopes:** `notepads:write` — _write_ Parameters: - `title` (body, string, required): Notepad title - `content` (body, string): Content as plain text/markdown (auto-converted) or { html, tiptapJson } object - `folder_id` (body, uuid): Folder UUID (preferred over folder name). Auto-populates the folder string field. - `folder` (body, string): Folder name (resolved to existing folder or created if missing). Auto-populates folder_id. - `visibility` (body, string): all_users (default) or restricted - `is_favorite` (body, boolean): Mark as favourite - `customer_id` (body, uuid): Company/account UUID. The new notepad is automatically linked to this company -- its ID is appended to the company notepads array. Makes the note appear in the Notepads section of the company page. - `contact_id` (body, uuid): Contact UUID. The new notepad is automatically linked to this contact -- its ID is appended to the contact notepads array. Makes the note appear in the Notepads section of the contact page. - `return_content` (body, boolean): Set true to include full rendered HTML in response. Default false. ### PATCH /notepads/:id Update a notepad. Three editing modes: (1) replace (default) -- pass content to overwrite; (2) append/prepend -- set mode plus content to grow the document without re-sending existing text; (3) patches -- pass a patches array to replace or append to specific sections by heading text. Can also update visibility or folder_id. Response is slim by default -- pass return_content: true for full HTML. **Scopes:** `notepads:write` — _write_ Parameters: - `id` (path, uuid, required): Notepad ID - `title` (body, string): Notepad title - `content` (body, string): Content as plain text/markdown. Used by modes replace, append, and prepend. - `folder_id` (body, uuid): Folder UUID (pass null to unfile) - `folder` (body, string): Folder name (legacy) - `visibility` (body, string): all_users, admins_only, creator_only, or restricted - `is_favorite` (body, boolean): Favourite status - `mode` (body, string): "replace" (default), "append", or "prepend". Append/prepend require content. Cannot combine with patches. - `patches` (body, array): Array of { match_heading, new_content, mode? } objects. match_heading is case-insensitive. Per-patch mode: "replace" (default, keeps heading), "replace_including_heading", "append_to_section". - `return_content` (body, boolean): Set true to include full rendered HTML in response. Default false. ### DELETE /notepads/:id Delete a notepad. **Scopes:** `notepads:delete` — _write_ Parameters: - `id` (path, uuid, required): Notepad ID ### GET /notepads/folders List notepad folders. Each folder has id, name, parent_id, visibility, created_by, and timestamps. **Scopes:** `notepads:read` ### POST /notepads/folders Create a notepad folder. **Scopes:** `notepads:write` — _write_ Parameters: - `name` (body, string, required): Folder name - `parent_id` (body, uuid): Parent folder UUID for nesting - `visibility` (body, string): all_users (default), admins_only, creator_only, or restricted ### GET /notepads/folders/:id Get a single notepad folder by ID. **Scopes:** `notepads:read` Parameters: - `id` (path, uuid, required): Folder ID ### PATCH /notepads/folders/:id Update a notepad folder -- rename, re-parent, or change visibility. **Scopes:** `notepads:write` — _write_ Parameters: - `id` (path, uuid, required): Folder ID - `name` (body, string): New folder name - `parent_id` (body, uuid): New parent folder UUID (null for root) - `visibility` (body, string): all_users, admins_only, creator_only, or restricted ### DELETE /notepads/folders/:id Delete a notepad folder. Notepads inside are unfiled (folder_id cleared) -- not deleted. **Scopes:** `notepads:delete` — _write_ Parameters: - `id` (path, uuid, required): Folder ID ### GET /notepads/:id/acl List ACL entries for a restricted notepad. Returns user_id and role_name grants. **Scopes:** `notepads:read` Parameters: - `id` (path, uuid, required): Notepad ID ### POST /notepads/:id/acl Grant a user or role access to a restricted notepad. Set visibility to "restricted" first, then add ACL entries. **Scopes:** `notepads:write` — _write_ Parameters: - `id` (path, uuid, required): Notepad ID - `principal_type` (body, string, required): "user" or "role" - `principal_id` (body, string, required): User UUID (when principal_type=user) or role name (when principal_type=role) ### GET /notepads/folders/:id/acl List ACL entries for a restricted notepad folder. **Scopes:** `notepads:read` Parameters: - `id` (path, uuid, required): Folder ID ### POST /notepads/folders/:id/acl Grant a user or role access to a restricted notepad folder. **Scopes:** `notepads:write` — _write_ Parameters: - `id` (path, uuid, required): Folder ID - `principal_type` (body, string, required): "user" or "role" - `principal_id` (body, string, required): User UUID or role name ### DELETE /notepads/acl/:id Revoke an ACL entry by its ID. Works for both notepad and folder ACL entries. **Scopes:** `notepads:write` — _write_ Parameters: - `id` (path, uuid, required): ACL entry ID --- ## Spreadsheets Manage spreadsheet templates (column definitions) and populated spreadsheets (row data). Spreadsheets can be linked to opportunities and are also created automatically when a form with a spreadsheet field is submitted. Scopes: spreadsheets:read, spreadsheets:write, spreadsheets:delete. ### GET /spreadsheets/templates List all spreadsheet templates for this workspace. **Scopes:** `spreadsheets:read` Parameters: - `search` (query, string): Filter by name (partial match) - `include_archived` (query, boolean): Include archived templates (default false) - `limit` (query, number): Items per page (default 25, max 100) - `after` (query, string): Cursor: last ID from previous page ### GET /spreadsheets/templates/:id Get a spreadsheet template with full column definitions. Use this to resolve column IDs to headers before writing row data. **Scopes:** `spreadsheets:read` Parameters: - `id` (path, uuid, required): Template ID ### POST /spreadsheets/templates Create a new spreadsheet template with column definitions. Each column needs header + data_type. Valid data_type values: text, long_text, number, date, boolean, dropdown, user_ref, opportunity_ref. dropdown requires options[] with at least 2 entries (plain strings or {value, color?} objects for coloured chips). default_sort sets the row display order. If columns are omitted, two default columns (Item and Notes) are created. **Scopes:** `spreadsheets:write` — _write_ Parameters: - `name` (body, string, required): Template name - `description` (body, string): Optional description - `columns` (body, array): Column definitions. Each: { header: string, data_type: "text"|"long_text"|"number"|"date"|"boolean"|"dropdown"|"user_ref"|"opportunity_ref", description?: string, options?: Array, width?: number, archived?: true }. dropdown requires options with >= 2 non-empty entries. - `default_sort` (body, array): Optional default ordering. Each entry: { column: "
", direction: "asc"|"desc" }. Rows display in this order and imports are sorted the same way. Pass null to clear. - `merge_rules` (body, object): Optional merge-on-insert config. Shape: { group_by: string[], period_start: string, period_end: string, tolerance_days?: number, concat_columns?: { [header]: { separator: string, order_by: string, direction?: "asc"|"desc" } } }. Pass null to clear. ```bash { "name": "Project Checklist", "columns": [ { "header": "Task", "data_type": "text" }, { "header": "Notes", "data_type": "long_text" }, { "header": "Assigned To", "data_type": "user_ref" }, { "header": "Linked Opportunity", "data_type": "opportunity_ref" }, { "header": "Due Date", "data_type": "date" }, { "header": "Complete", "data_type": "boolean" }, { "header": "Status", "data_type": "dropdown", "options": [ { "value": "Open", "color": "green" }, { "value": "In Progress", "color": "amber" }, { "value": "Done", "color": "slate" } ] } ], "default_sort": [ { "column": "Status", "direction": "asc" } ] } ``` ### PATCH /spreadsheets/templates/:id Update a spreadsheet template. columns is a FULL REPLACEMENT -- include all existing columns with their current id to preserve them. To remove a column without losing row data, set archived: true. Changing columns does not modify existing row data. Same data_type values and option shapes as POST. **Scopes:** `spreadsheets:write` — _write_ Parameters: - `id` (path, uuid, required): Template ID - `name` (body, string): Template name - `description` (body, string): Template description - `columns` (body, array): FULL REPLACEMENT column set. Include id on existing columns to update them; omit id to create a new column. Each: { id?: string, header: string, data_type: "text"|"long_text"|"number"|"date"|"boolean"|"dropdown"|"user_ref"|"opportunity_ref", options?: Array, archived?: true }. - `default_sort` (body, array): Default ordering. Array of { column: string, direction: "asc"|"desc" }. Pass null to clear. - `merge_rules` (body, object): Merge-on-insert config (or null to clear). - `is_archived` (body, boolean): Archive or unarchive ### DELETE /spreadsheets/templates/:id Delete a spreadsheet template and cascade-delete all spreadsheets derived from it. **Scopes:** `spreadsheets:delete` — _write_ Parameters: - `id` (path, uuid, required): Template ID ### GET /spreadsheets List all populated spreadsheets. Filter by template or opportunity. **Scopes:** `spreadsheets:read` Parameters: - `template_id` (query, uuid): Filter by template - `opportunity_id` (query, uuid): Filter by linked opportunity - `include_archived` (query, boolean): Include archived spreadsheets (default false) - `limit` (query, number): Items per page (default 25, max 100) - `after` (query, string): Cursor: last ID from previous page ### GET /spreadsheets/:id Get a populated spreadsheet with metadata, parent template column definitions (parent_columns), and all rows ordered by row_index. Each row cells object is keyed by column ID. **Scopes:** `spreadsheets:read` Parameters: - `id` (path, uuid, required): Spreadsheet ID ### POST /spreadsheets Create a new populated spreadsheet from a template. Starts with no rows. Optionally link to an opportunity. **Scopes:** `spreadsheets:write` — _write_ Parameters: - `template_id` (body, uuid, required): Template to base this spreadsheet on - `name` (body, string, required): Spreadsheet name - `opportunity_id` (body, uuid): Link to an opportunity ```bash { "template_id": "tmpl-uuid", "name": "Project Alpha Checklist", "opportunity_id": "deal-uuid" } ``` ### PATCH /spreadsheets/:id Update a spreadsheet name, linked opportunity, or archived status. **Scopes:** `spreadsheets:write` — _write_ Parameters: - `id` (path, uuid, required): Spreadsheet ID - `name` (body, string): Spreadsheet name - `opportunity_id` (body, uuid): Link to a different opportunity, or null to unlink - `is_archived` (body, boolean): Archive or unarchive ### DELETE /spreadsheets/:id Delete a populated spreadsheet and all its rows. Does not affect the parent template. **Scopes:** `spreadsheets:delete` — _write_ Parameters: - `id` (path, uuid, required): Spreadsheet ID ### GET /spreadsheets/:id/rows List all rows in a spreadsheet ordered by row_index. Each row has a cells object keyed by column ID. **Scopes:** `spreadsheets:read` Parameters: - `id` (path, uuid, required): Spreadsheet ID ### POST /spreadsheets/:id/rows Append a new row to a spreadsheet. Provide cells as an object keyed by column ID (not header name). Retrieve column IDs from GET /spreadsheets/:id (parent_columns) or GET /spreadsheets/templates/:id. **Scopes:** `spreadsheets:write` — _write_ Parameters: - `id` (path, uuid, required): Spreadsheet ID - `cells` (body, object): Cell values keyed by column ID ```bash { "cells": { "col-uuid-1": "Send proposal", "col-uuid-2": false } } ``` ### PATCH /spreadsheets/:id/rows/:rowId Update the cells or row_index of an existing row. cells replaces the entire cells object -- include all column values you want to retain. **Scopes:** `spreadsheets:write` — _write_ Parameters: - `id` (path, uuid, required): Spreadsheet ID - `rowId` (path, uuid, required): Row ID - `cells` (body, object): Replacement cells object keyed by column ID - `row_index` (body, number): New row position (0-based) ### DELETE /spreadsheets/:id/rows/:rowId Delete a single row from a spreadsheet. **Scopes:** `spreadsheets:delete` — _write_ Parameters: - `id` (path, uuid, required): Spreadsheet ID - `rowId` (path, uuid, required): Row ID ### POST /spreadsheets/:id/rows/bulk Append up to 100 rows in a single request. Rows are inserted in input order. Returns created[] with original input index on each entry, and errors[] for any rows that failed. Use this instead of looping POST /spreadsheets/:id/rows for imports larger than ~10 rows. **Scopes:** `spreadsheets:write` — _write_ Parameters: - `id` (path, uuid, required): Spreadsheet ID - `rows` (body, array, required): Array of row objects (max 100). Each item: { cells: { "": } }. cells may be omitted to insert an empty row. ```bash curl -X POST "https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/spreadsheets/spr-uuid/rows/bulk" \ -H "Authorization: Bearer tp_live_..." \ -H "Content-Type: application/json" \ -d '{ "rows": [ { "cells": { "col-uuid-1": "Widget A", "col-uuid-2": "First item note" } }, { "cells": { "col-uuid-1": "Widget B", "col-uuid-2": "Second item note" } }, { "cells": { "col-uuid-1": "Widget C" } } ] }' ``` --- ## Reports Query engine, dashboard CRUD, card CRUD, and funnel configuration for the workspace reporting system. Dashboards support per-record visibility (all_users or restricted) with ACL-based access grants. ### POST /reports/query Run a report query. Returns aggregated rows for charts, or individual rows for drilldowns. Sources: "deals" (pipeline performance, revenue, win/loss) and "tasks" (open/overdue/by-assignee). In drilldown mode, the dimensions[] array controls which columns are returned and in what order (up to 8 columns via display_config.columns on a saved card). **Scopes:** `opportunities:read` Parameters: - `source` (body, string, required): Data source: "deals" or "tasks". - `measures` (body, array): Array of { field, aggregation, alias }. deals fields: id (count), value, products_total_value, product_count. tasks fields: id (count). Aggregations: count, sum, avg, min, max. - `dimensions` (body, array): In aggregate mode: group-by fields. In drilldown mode: also sets which columns are returned in order. deals fields: status, pipeline_name, stage_name, assigned_user_name, lead_source, currency, won_reasons, lost_reasons, product_names, product_categories. tasks fields: status, priority, category, assignee_name, deal_name, contact_name, is_overdue, client (virtual -- display only, resolves to contact_name falling back to deal_name; cannot be filtered or sorted). Virtual dimensions (virtual:true in /reports/sources) are computed in JS post-fetch and cannot appear in filters or order_by. - `filters` (body, array): Array of { field, operator, value/values }. Operators: eq, neq, gt, gte, lt, lte, in, not_in, like, is_null, is_not_null, contains. Do not filter on virtual dimensions. - `time_dimension` (body, object): { field, granularity }. deals: field is deal_created_at, won_at, lost_at, placed_at. tasks: field is due_date, task_created_at, completed_at. granularity: day, week, month, quarter, year. - `mode` (body, string): "aggregate" (default) returns grouped/summed rows. "drilldown" returns individual rows from the source; dimensions[] controls which columns are returned. - `order_by` (body, array): Sort order. Array of { field, direction } objects where direction is "asc" or "desc". Can also be a single { field, direction } object. "sort" is accepted as an alias. Cannot sort on virtual dimensions. - `drilldown_dimension` (body, string): For drilldown mode: dimension field to filter by. - `drilldown_value` (body, string): For drilldown mode: value to match on drilldown_dimension. - `limit` (body, number): Max rows (default 100, max 1000). ### GET /reports/sources List available data sources ("deals", "tasks") with supported measures, dimensions, and filter fields. Dimensions with virtual:true are computed in JS post-fetch -- they cannot be used in filters or order_by, only in dimensions[] for display column selection. **Scopes:** `opportunities:read` ### GET /reports/templates List available dashboard templates: sales_overview, staff_accountability, pipeline_health, marketing_roi. **Scopes:** `opportunities:read` ### POST /report-dashboards Create a report dashboard. Supports template expansion with optional pipeline_id scoping. Set visibility to "restricted" then use POST /report-dashboards/:id/acl to grant per-user/role access. **Scopes:** `opportunities:read` — _write_ Parameters: - `name` (body, string): Dashboard name. Required if no template. - `description` (body, string): Dashboard description. - `template` (body, string): Template key: sales_overview, staff_accountability, pipeline_health, marketing_roi. Auto-populates cards. - `pipeline_id` (body, uuid): Pipeline UUID. Scopes all template cards to this pipeline. - `visibility` (body, string): Who can see this dashboard. Valid values: "all_users" (default -- everyone in the workspace) or "restricted" (only users/roles added via the ACL endpoint). Use "restricted" + POST /report-dashboards/:id/acl to control access precisely. ### GET /report-dashboards List all report dashboards for the company. **Scopes:** `opportunities:read` ### GET /report-dashboards/:id Get a dashboard with all its cards. Each card includes title, visualization_type, size, position, and full query_spec. **Scopes:** `opportunities:read` Parameters: - `id` (path, uuid, required): Dashboard UUID. ### PATCH /report-dashboards/:id Partial update -- rename, re-describe, or change visibility of a dashboard. Changing visibility to "all_users" removes the restriction but does not delete existing ACL entries. **Scopes:** `opportunities:read` — _write_ Parameters: - `id` (path, uuid, required): Dashboard UUID. - `name` (body, string): New name. - `description` (body, string): New description. - `visibility` (body, string): all_users or restricted. ### DELETE /report-dashboards/:id Delete a dashboard and all its cards permanently. **Scopes:** `opportunities:read` — _write_ Parameters: - `id` (path, uuid, required): Dashboard UUID. ### GET /report-dashboards/:id/acl List ACL entries for a restricted dashboard. Each entry has user_id or role_name indicating who has access. **Scopes:** `opportunities:read` Parameters: - `id` (path, uuid, required): Dashboard UUID. ### POST /report-dashboards/:id/acl Grant a user or role access to a restricted dashboard. Set visibility="restricted" on the dashboard first. **Scopes:** `opportunities:read` — _write_ Parameters: - `id` (path, uuid, required): Dashboard UUID. - `principal_type` (body, string, required): "user" or "role". - `principal_id` (body, string, required): User UUID (when principal_type=user) or role name (when principal_type=role, e.g. "client_admin", "client_editor", "client_viewer"). ### POST /report-dashboards/:id/cards Add a chart or table card to a dashboard. Use display_config.columns to control which columns appear in email digest renders for table/drilldown cards. **Scopes:** `opportunities:read` — _write_ Parameters: - `id` (path, uuid, required): Dashboard UUID. - `title` (body, string, required): Card title. - `visualization_type` (body, string): Chart type: stat, bar, horizontal_bar, line, area, donut, pie, table, composed. - `query_spec` (body, object): Query specification (same format as POST /reports/query). For drilldown table cards set mode:"drilldown" and optionally dimensions[] to control columns. order_by accepts an array of { field, direction } objects for multi-key sorting; "sort" is accepted as alias. - `display_config` (body, object): Display overrides for email digest rendering. display_config.columns: array of column key strings or { key, label? } objects (up to 8 columns). When set, overrides dimensions[] as the column source for table cards. Use to show different columns in the email vs the underlying data query. - `size` (body, string): Card size: sm, md, lg. - `position` (body, number): Zero-based position in the dashboard. ### PATCH /report-cards/:id Partial update on a card -- change title, visualization type, query spec, display_config, size, or position. **Scopes:** `opportunities:read` — _write_ Parameters: - `id` (path, uuid, required): Card UUID. - `title` (body, string): Card title. - `visualization_type` (body, string): Visualization type (bar, line, pie, etc.). - `query_spec` (body, object): Query specification object. Supports order_by as array of { field, direction } for multi-key sorting; "sort" is accepted as alias for order_by. - `display_config` (body, object): Display overrides. display_config.columns sets an explicit column list for table/drilldown email digest rendering (string[] or { key, label? }[], up to 8 columns). - `size` (body, string): Card size (sm, md, lg). - `position` (body, number): Sort order position. ### DELETE /report-cards/:id Remove a card from its dashboard. Dashboard itself is not affected. **Scopes:** `opportunities:read` — _write_ Parameters: - `id` (path, uuid, required): Card UUID. ### PUT /report-dashboards/:id/reorder Reorder cards by providing an ordered array of card UUIDs. **Scopes:** `opportunities:read` — _write_ Parameters: - `id` (path, uuid, required): Dashboard UUID. - `card_ids` (body, array, required): Ordered array of card UUIDs. ### POST /reports/send Send a report dashboard as an email digest. Dashboard cards render server-side and are inlined into the email body. Each recipient is scoped via current_user_id so cards filtered to "the current user" naturally show per-recipient data. Returns per-recipient send status with skipped_reason when a send is suppressed. **Scopes:** `opportunities:read` — _write_ Parameters: - `dashboard_id` (body, uuid, required): UUID of the dashboard to send. - `recipients` (body, array, required): Array of recipients. Each item is a plain email string or { email, name?, user_id? }. user_id scopes the rendered report to that workspace user. - `subject` (body, string): Email subject line. Defaults to the dashboard name. - `intro_text` (body, string): Plain-text intro paragraph above the report block. Supports {{variable}} placeholders. Ignored when intro_html is also set. - `outro_text` (body, string): Plain-text outro paragraph below the report block. Supports {{variable}} placeholders. Ignored when outro_html is also set. - `intro_html` (body, string): Pre-authored HTML intro (e.g. from the rich-text wizard). Takes precedence over intro_text when both are provided. - `outro_html` (body, string): Pre-authored HTML outro. Takes precedence over outro_text when both are provided. - `sender_name` (body, string): Display name shown above the signature line. - `suppress_if_empty` (body, boolean): When true (default), skip sending to any recipient whose dashboard renders zero rows. Skipped sends are reported as success with skipped_reason: "empty_dashboard". - `email_config_id` (body, uuid): Optional email config UUID to pin the sender alias or provider. - `email_provider` (body, string): Provider to use: "postmark" (TrustPager Mail, default) or "gmail". Gmail requires the workspace email_config to have a Gmail sender connected; returns an error otherwise. - `contact_id` (body, uuid): Optional CRM contact UUID to associate with the email send log. - `customer_id` (body, uuid): Optional CRM company UUID to associate with the email send log. - `deal_id` (body, uuid): Optional CRM opportunity UUID to associate with the email send log. ### GET /report-funnels Get funnel step configuration for a pipeline. **Scopes:** `opportunities:read` Parameters: - `pipeline_id` (query, uuid, required): Pipeline UUID. ### PUT /report-funnels Create or update funnel step configuration for a pipeline. **Scopes:** `opportunities:read` — _write_ Parameters: - `pipeline_id` (body, uuid, required): Pipeline UUID. - `steps` (body, array, required): Array of { name, stage_ids[] }. Each step groups one or more pipeline stages. ### DELETE /report-funnels/:id Delete a funnel config. **Scopes:** `opportunities:read` — _write_ Parameters: - `id` (path, uuid, required): Funnel config UUID. --- ## AI Knowledge Manage company AI Knowledge entries with semantic search powered by Voyage AI embeddings. Use for policies, FAQs, agent instructions, and product documentation. ### GET /knowledge List all knowledge base entries. Filter by category or tag. **Scopes:** `knowledge:read` Parameters: - `category` (query, string): Filter by category: general, agent, faq, policy, procedure, product - `tag` (query, string): Filter entries that include this tag - `limit` (query, number): Items per page (default 25, max 100) - `after` (query, string): Cursor for pagination ### GET /knowledge/:id Retrieve a single knowledge base entry by ID. The id must be a complete UUID (e.g. "203cf566-22ff-4dd3-87c7-22983d630a31"). Passing a truncated UUID returns a VALIDATION_ERROR with a clear message. **Scopes:** `knowledge:read` Parameters: - `id` (path, uuid, required): Knowledge entry full UUID - do not abbreviate or truncate ### POST /knowledge Create a knowledge base entry. An embedding is automatically generated for semantic search. Valid categories: general (default), agent, faq, policy, procedure, product. **Scopes:** `knowledge:write` — _write_ Parameters: - `title` (body, string, required): Entry title - `content` (body, string, required): Entry content - the full knowledge text - `category` (body, string): Category: general (default), agent, faq, policy, procedure, product - `tags` (body, string[]): Array of tag strings for filtering - `source_type` (body, string): Source type (e.g. manual, transcript, document) - `source_id` (body, uuid): UUID of the source record ### PATCH /knowledge/:id Update a knowledge base entry. If title or content changes, the embedding is automatically regenerated. The id must be a complete UUID - truncated values return VALIDATION_ERROR. **Scopes:** `knowledge:write` — _write_ Parameters: - `id` (path, uuid, required): Knowledge entry full UUID - do not abbreviate or truncate - `title` (body, string): Updated title - `content` (body, string): Updated content - `category` (body, string): Category: general, agent, faq, policy, procedure, product - `tags` (body, string[]): Updated tags array (replaces existing) - `source_type` (body, string): Source type - `source_id` (body, uuid): Source record UUID ### DELETE /knowledge/:id Delete a knowledge base entry permanently. The id must be a complete UUID - truncated values return VALIDATION_ERROR. **Scopes:** `knowledge:write` — _write_ Parameters: - `id` (path, uuid, required): Knowledge entry full UUID - do not abbreviate or truncate ### POST /knowledge/search Semantic search across the knowledge base using natural language. Powered by Voyage AI embeddings and pgvector. Returns entries ranked by cosine similarity. Usage count is automatically incremented for returned entries. **Scopes:** `knowledge:read` Parameters: - `query` (body, string, required): Natural language search query - `category` (body, string): Restrict search to a category: general, agent, faq, policy, procedure, product - `limit` (body, number): Max results (default 5, max 20) - `threshold` (body, number): Minimum similarity 0.0-1.0 (default 0.3) --- ## Agent Memory Cross-run persistent state for managed agents. Each row records what an agent learned, decided, or intends to revisit about a subject. Supports semantic search via vector embeddings. Required scopes: memory:read (GET/search), memory:write (POST/PATCH/DELETE). ### GET /memory List agent memories for this workspace. Filter by subject_type, subject_id, kind, agent_registry_id, visibility, tag, linked entity, or time. Cursor-paginated (newest first). **Scopes:** `memory:read` Parameters: - `subject_type` (query, string): Filter by subject type: "contact", "opportunity", "company", "self", "campaign", or any agent-defined value. - `subject_id` (query, uuid): Filter by subject id (use with subject_type). - `kind` (query, string): Filter by memory kind: "pitch", "objection", "preference", "observation", "next_steps", "fact", or any agent-defined value. - `agent_registry_id` (query, uuid): Filter by owning agent. Omit to see all memories visible in the workspace. - `visibility` (query, string): Filter by visibility: "private" or "shared". - `tag` (query, string): Tag filter (any-of). Repeat param for multiple tags: ?tag=objection:pricing&tag=competitor:Twilio. - `linked_entity_type` (query, string): Find memories linked to entities of this type. Must be combined with linked_entity_id. - `linked_entity_id` (query, string): Find memories linked to this entity id (combine with linked_entity_type). - `since` (query, string): ISO 8601 timestamp -- only memories created after this time. - `limit` (query, number): Items per page (default 25, max 100). - `after` (query, string): Cursor: return items after this id. ### POST /memory Write an agent memory. If `key` is provided, behaves as upsert on (agent_registry_id, subject_type, subject_id, kind, key) -- re-using the same key UPDATES the existing memory rather than creating a duplicate. A soft-deleted memory with the same key is revived. Embedding auto-generated for semantic search. **Scopes:** `memory:write` — _write_ Parameters: - `subject_type` (body, string, required): What the memory is about: "contact", "opportunity", "company", "self", "campaign", or any agent-defined value. - `kind` (body, string, required): The memory category. Common: "pitch", "objection", "preference", "observation", "next_steps", "fact". - `content` (body, string, required): The memory in natural language. Write it as a narration to your future self. - `agent_registry_id` (body, uuid): The owning agent UUID. Pass the agent's own id so the memory is private to that agent. - `subject_id` (body, uuid): The subject UUID (contact, opportunity, or company UUID). Omit for global/self memories. - `key` (body, string): Optional unique key within (agent_registry_id, subject_type, subject_id, kind). Enables upsert. Example: an article_slug for a pitch memory. - `visibility` (body, string): "private" (default) or "shared" (readable by all agents in the workspace). - `metadata` (body, object): Structured payload alongside the natural-language content. Free-form keys. - `linked_entities` (body, array): Other entities this memory references. Each entry: {type: string, id: string}. - `tags` (body, string[]): Semantic facets. Convention: ":". Examples: ["competitor:Twilio", "objection:pricing"]. - `confidence` (body, number): 0.0-1.0. Default 1.0. Lower for inferences vs stated facts. - `source_kind` (body, string): Where this memory came from: "email_send", "transcript", "tool_observation", "manual". - `source_id` (body, uuid): Pointer to the source event (e.g. email_logs.id). - `parent_memory_id` (body, uuid): Link to an earlier memory this one updates or continues. - `expires_at` (body, string): ISO timestamp after which this memory becomes inactive. ### GET /memory/:id Fetch a single agent memory by its UUID. Returns NOT_FOUND if the memory is soft-deleted. **Scopes:** `memory:read` Parameters: - `id` (path, uuid, required): Memory UUID -- full UUID, do not truncate. ### PATCH /memory/:id Partial update on a memory. Can change content, metadata, tags, linked_entities, confidence, expires_at, or visibility. Re-embeds automatically if content changes. **Scopes:** `memory:write` — _write_ Parameters: - `id` (path, uuid, required): Memory UUID. - `content` (body, string): Updated content. Triggers re-embedding. - `metadata` (body, object): Replaces existing metadata in full. - `linked_entities` (body, array): Replaces existing linked_entities in full. - `tags` (body, string[]): Replaces existing tags array in full. - `confidence` (body, number): 0.0-1.0. - `expires_at` (body, string): ISO timestamp. - `visibility` (body, string): "private" or "shared". ### DELETE /memory/:id Soft-delete a memory. Sets deleted_at; the row is preserved for audit but no longer surfaces in list/search/get. Reversible only via admin SQL. **Scopes:** `memory:write` — _write_ Parameters: - `id` (path, uuid, required): Memory UUID. ### POST /memory/search Semantic search over agent memories. Embeds the query and returns the top-K most similar memories by cosine similarity. Use for fuzzy/topic-based lookup ("have I discussed pricing with this contact?"). Use GET /memory for exact key lookup. **Scopes:** `memory:read` Parameters: - `query` (body, string, required): Natural language query. - `subject_type` (body, string): Restrict search to a subject type. - `subject_id` (body, uuid): Restrict to a specific subject. - `kind` (body, string): Restrict to a memory kind. - `agent_registry_id` (body, uuid): Restrict to a specific agent. - `visibility` (body, string): "private" or "shared". - `limit` (body, number): Max results (default 10, max 50). - `threshold` (body, number): Cosine similarity floor 0.0-1.0 (default 0.15). Most useful results fall in 0.1-0.4 with Voyage-3 embeddings. --- ## Company View and manage company settings, users, and CRM configuration. ### GET /company Retrieve company details including slug for public-facing URLs. **Scopes:** `company:read` ### PATCH /company Update company settings. Writable fields include: name, slug, description, contact_name, contact_email, phone, logo_url, primary_color, secondary_color, address, timezone, industry, website_url, abn. (The workspace from-address — formerly exposed as email_handle — is now managed via the email_config endpoints.) **Scopes:** `company:write` — _write_ Parameters: - `slug` (body, string): URL-friendly slug for public pages (forms, signing, bookings). Auto-generated from name on company creation. ### GET /company/users List all team members. Returns membership role, status, and workspace-enriched profile fields (full_name, email, phone, job_title, department). Workspace-scoped fields from company_user_settings take priority over global user profile values. **Scopes:** `users:read` ### GET /company/users/:userId Get a single team member with workspace-enriched profile. Returns membership role, status, and profile fields overlaid with any workspace-scoped overrides from company_user_settings. **Scopes:** `users:read` Parameters: - `userId` (path, uuid, required): User ID ### POST /company/users/invite Invite a user to the company. They receive an invitation email. **Scopes:** `users:write` — _write_ Parameters: - `email` (body, string, required): Email to invite - `role` (body, string, required): Role: client_admin, client_editor, or client_viewer - `full_name` (body, string): Full name of the invitee ### GET /company/users/:userId/personal-user-profile Get the global (platform-wide) identity record for a team member from the users table -- full_name, email, phone, avatar_url, status, job_title, department, created_at. Returns 404 if the user is not a member of this workspace. **Scopes:** `users:read` Parameters: - `userId` (path, uuid, required): User ID ### GET /company/users/:userId/workspace-user-profile Get workspace-scoped contact info for a team member from company_user_settings -- phone, job_title, department. Returns null values if not set. These fields take priority over the global profile when displayed in the platform. **Scopes:** `users:read` Parameters: - `userId` (path, uuid, required): User ID ### PATCH /company/users/:userId/workspace-user-profile Set workspace-scoped contact info for a team member. Accepts phone, job_title, department. Values are stored in company_user_settings and take priority over the global user profile within this workspace. **Scopes:** `users:write` — _write_ Parameters: - `userId` (path, uuid, required): User ID - `phone` (body, string): Workspace phone number override - `job_title` (body, string): Workspace job title override - `department` (body, string): Workspace department override ### GET /company/users/:userId/workspace-user-connections Get the integrations a team member has shared with this workspace -- Gmail, calendar sync, etc. Returns an array of connection objects with platform_type, platform_account_name, status, calendar_synced, and gmail_aliases. **Scopes:** `users:read` Parameters: - `userId` (path, uuid, required): User ID ### GET /company/users/:userId/workspace-user-roles Get the company role and join date for a team member. Returns role (client_admin, client_editor, or client_viewer) and created_at. **Scopes:** `users:read` Parameters: - `userId` (path, uuid, required): User ID ### PATCH /company/users/:userId/workspace-user-roles Change a team member's workspace role. Valid values: client_admin (full access), client_editor (edit access), client_viewer (read-only). **Scopes:** `users:write` — _write_ Parameters: - `userId` (path, uuid, required): User ID - `role` (body, string, required): New role: client_admin, client_editor, or client_viewer ### GET /company/users/:userId/workspace-user-preferences Get workspace-level preferences for a team member. Currently returns default_pipeline_id (the pipeline shown by default when they open the CRM). Returns null if not set. **Scopes:** `users:read` Parameters: - `userId` (path, uuid, required): User ID ### PATCH /company/users/:userId/workspace-user-preferences Set workspace-level preferences for a team member. Accepted field: default_pipeline_id (UUID of the pipeline to show by default, or null to clear). **Scopes:** `users:write` — _write_ Parameters: - `userId` (path, uuid, required): User ID - `default_pipeline_id` (body, uuid | null): Pipeline to show by default in this workspace, or null to clear ### DELETE /company/users/:userId Remove a user from the company -- revoke their access permanently. **Scopes:** `users:delete` — _write_ Parameters: - `userId` (path, uuid, required): User ID ### GET /company/crm-settings Get CRM settings including custom field definitions, lead sources, lost/won reasons, type option lists (opportunity, account, contact), transcript settings, feature toggles (accounts_enabled, contacts_enabled, enable_work_orders), form_completion_notify_emails, automation_error_notify_emails, and automation_error_notify_phones. All fields are stored in the company_settings table. **Scopes:** `company:read` ### PATCH /company/crm-settings Partial update of CRM settings. Only include fields you want to change; unspecified fields are left unchanged. All fields are stored in company_settings (NOT companies.crm_settings which no longer exists). **Scopes:** `company:write` — _write_ Parameters: - `opportunity_type_options` (body, string[]): Opportunity type dropdown options (e.g. ["New Business", "Upsell", "Renewal", "Referral"]) - `account_type_options` (body, string[]): Account/company type dropdown options (e.g. ["Client", "Supplier", "Partner", "Referrer"]) - `contact_type_options` (body, string[]): Contact type dropdown options (e.g. ["Decision Maker", "Influencer", "Champion"]) - `transcript_types` (body, string[]): Transcript type categories (e.g. ["Sales", "Fulfilment", "Support"]) - `transcript_sources` (body, string[]): Transcript source options (e.g. ["Zoom", "Loom", "Manual"]) - `custom_fields_title` (body, string): Custom label for the "Additional Information" section on detail pages - `accounts_enabled` (body, boolean): Enable/disable the Accounts feature - `contacts_enabled` (body, boolean): Enable/disable the Contacts feature - `enable_work_orders` (body, boolean): Enable/disable Work Orders on deals - `lead_sources` (body, string[]): Available lead source options for deals - `lost_reasons` (body, string[]): Available reasons when marking a deal lost - `won_reasons` (body, string[]): Available reasons when marking a deal won - `form_completion_notify_emails` (body, string[]): Workspace-wide default email addresses notified when any form is completed. Falls back to the sending user if empty. - `automation_error_notify_emails` (body, string[]): Email addresses notified when an automation action fails. Must be valid email format. Alerts are throttled to one per error signature per hour per company. - `automation_error_notify_phones` (body, string[]): Phone numbers notified via SMS when an automation action fails. Must be in E.164 format (e.g. +61431377068). Requires an active phone number in the workspace. Alerts are throttled to one per error signature per hour per company. - `custom_fields` (body, object): Custom field definitions keyed by entity: { deal: [...], account: [...], contact: [...] }. Each field: { id, label, type, options?, show_on_detail, show_in_table, required?, fill_with_ai?, hidden?, conditional_logic? }. Supported types: text, textarea, number, datetime, checkbox, dropdown (requires options[]), url (renders as clickable link with open-in-new-tab icon -- use for Drive folders, portal links, signed agreement URLs). conditional_logic (optional) -- controls two behaviours on deal fields: (1) visibility: { mode: "always" | "when" | "never", conditions?: [{ field_id, operator: "is" | "is_not" | "is_set" | "is_empty", value? }] } -- hides or shows the field on the detail view based on the values of other fields. Conditions are ANDed. (2) default_rules: array of { conditions: [...], action } evaluated top-to-bottom on every deal create or update via API, MCP, form submission, or UI field commit; first matching rule wins. Supported actions: - { type: "today_plus", amount: number, unit: "days"|"weeks"|"months", skip_saturday?: boolean, skip_sunday?: boolean } -- writes today + offset as ISO 8601. - { type: "field_plus", source_field_id: string, amount: number, unit: "days"|"weeks"|"months", skip_saturday?: boolean, skip_sunday?: boolean } -- writes source datetime field + offset. No-op if source is empty. - { type: "constant", value: string|number|boolean } -- writes a fixed value. Rules only write to a field when it is empty or was last set by a rule. Manual edits are never overwritten. Rule-managed status is tracked internally in metadata._rule_managed -- do not set this key manually. - `needs_analysis_config` (body, object): Discovery/needs-analysis question definitions shown during deal qualification. Structure: { questions: [{ key, label, type, show_on_detail, show_in_table, ai_fill }] }. Pass { questions: [] } to clear all questions. Configurable in Settings > CRM > Needs Analysis. - `quick_links` (body, object[]): Workspace-wide Quick Link type definitions. Each entry: { id: string (stable UUID you generate), label: string (e.g. "Google Drive"), icon: string }. Valid icons: link, google-drive, dropbox, notion, slack, docs, folder. These define the Quick Link buttons shown in the right-rail sidebar of opportunity, company, and contact detail pages. Per-record URLs are stored in each entity's metadata.quick_links keyed by the type UUID. Pass [] to remove all types. ### GET /company/birthday-messages Get the birthday message configuration -- array of templates used by the birthday cron to send automated birthday emails and SMS to contacts. **Scopes:** `company:read` ### PUT /company/birthday-messages Replace the entire birthday messages array. Send an array of message objects -- one per year of relationship. The cron picks the entry matching the contact's year count (Year 1 on first birthday, etc.). Supported merge tags: {first_name}, {last_name}, {company_name}, {age}. **Scopes:** `company:write` — _write_ Parameters: - `(body)` (body, object[], required): Array of birthday message objects. Each must have: channels (array of "email" and/or "sms"), email_subject (string), email_body (string), sms_body (string). Optional: label (string). ```bash curl -X PUT \ "/company/birthday-messages" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "label": "Year 1", "channels": ["email", "sms"], "email_subject": "Happy Birthday {first_name}!", "email_body": "Hi {first_name}, best wishes from {company_name}!", "sms_body": "Happy Birthday {first_name}! From {company_name}." } ]' ``` ### GET /company/settings/tag-palette Get the company-wide tag palette -- the list of pre-defined tags shown as quick-picks in the Add Tag modal on opportunity cards. Returns an array of {name, color} objects stored in company_settings.deal_tag_options. **Scopes:** `company:read` ### PATCH /company/settings/tag-palette Replace the company-wide tag palette. Accepts an array of {name, color} objects directly (or wrapped in a "tags" key). Duplicates with the same name (case-insensitive) are deduplicated -- last entry wins. Replaces the entire palette. **Scopes:** `company:write` — _write_ Parameters: - `(body)` (body, object[] | { tags: object[] }, required): Array of tag objects. Each tag needs name (string) and color (hex string, e.g. "#ef4444"). ```bash curl -X PATCH \ "${API_BASE_URL}/company/settings/tag-palette" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "name": "Hot Lead", "color": "#ef4444" }, { "name": "Priority", "color": "#f97316" }, { "name": "Follow Up", "color": "#3b82f6" } ]' ``` --- ## CRM Templates Manage reusable CRM templates for emails, messages, and automated communications. ### GET /crm-templates List all CRM templates. **Scopes:** `crm-templates:read` ### GET /crm-templates/:id Retrieve a CRM template. **Scopes:** `crm-templates:read` Parameters: - `id` (path, uuid, required): Template ID ### POST /crm-templates Create a CRM template. **Scopes:** `crm-templates:write` — _write_ Parameters: - `name` (body, string, required): Template name ### PATCH /crm-templates/:id Update a CRM template. **Scopes:** `crm-templates:write` — _write_ Parameters: - `id` (path, uuid, required): Template ID ### DELETE /crm-templates/:id Delete a CRM template. **Scopes:** `crm-templates:write` — _write_ Parameters: - `id` (path, uuid, required): Template ID --- ## Integrations Read, query, and execute actions on native integrations (Xero, MYOB, etc.). Connecting and disconnecting integrations is done via the platform UI (OAuth browser flows). ### GET /integrations List all integrations. **Scopes:** `integrations:read` ### GET /integrations/:id Retrieve an integration. **Scopes:** `integrations:read` Parameters: - `id` (path, uuid, required): Integration ID ### POST /integrations/:id/query Query data from an integration (read-only). Supported Xero query_types: xero_contacts (params: page?, search?), xero_contact_detail (params: contact_id), xero_accounts (params: type?), xero_tax_rates (params: status?), xero_invoices (params: page?, statuses?, date_from?, date_to?), xero_invoices_by_contact (params: contact_id, statuses?), xero_profit_and_loss (params: from_date?, to_date?, periods?, timeframe?, tracking_category_id?, tracking_option_id?, standard_layout?, payments_only?), xero_balance_sheet (params: date?, periods?, timeframe?, tracking_option_id_1?, tracking_option_id_2?, standard_layout?, payments_only?), xero_aged_receivables (params: contact_id REQUIRED, date?, from_date?, to_date?, periods?, timeframe? -- per-contact Xero report; returns Xero report structure), xero_aged_payables (params: contact_id REQUIRED, date?, from_date?, to_date?, periods?, timeframe? -- per-contact Xero report), xero_aged_receivables_summary (params: date? -- aggregated across all contacts by paging open AUTHORISED ACCREC invoices; returns {as_at, totals:{total, current, days_1_30, days_31_60, days_61_90, days_90_plus}, contacts:[{contact_id, contact_name, total_outstanding, current, days_1_30, days_31_60, days_61_90, days_90_plus, invoice_count}]} sorted desc by total_outstanding), xero_aged_payables_summary (params: date? -- same shape as receivables summary but for ACCPAY). All date params accept both snake_case (from_date, to_date) and camelCase (fromDate, toDate). Other platforms: Slack (slack_channels, slack_users), Facebook (facebook_pages, facebook_leadgen_forms, facebook_ad_accounts), Calcom (calcom_event_types, calcom_bookings), Zoom (zoom_users, zoom_meetings), Portant (portant_templates), ActiveCampaign (activecampaign_lists, activecampaign_tags, activecampaign_automations). **Scopes:** `integrations:read` Parameters: - `id` (path, uuid, required): Integration ID - `query_type` (body, string, required): Query type (see description for full list per platform) - `params` (body, object): Query-specific parameters. See description for each query_type's accepted params. ### POST /integrations/:id/action Execute an action on an integration (e.g. create invoice in Xero). The integration must be status "active". For xero_create_invoice and xero_create_contact_and_invoice: contact resolution is automatic -- the system finds or creates a Xero contact from the deal's linked customer or CRM contact. You do NOT need to call xero_create_contact first or set customer_id on the deal; xero_create_contact_and_invoice is now a behavioral alias for xero_create_invoice (both auto-resolve the contact). Xero invoice actions (xero_create_invoice, xero_update_invoice) support email_mode ("none" | "xero" | "trustpager"), email_subject, email_body, email_attach_pdf (boolean), email_recipient_source, email_recipient_contact_id, and email_recipient inside params. email_mode "trustpager" sends a branded Postmark email with PDF attached and a Xero pay-now link; requires a default email_config. email_mode "xero" uses Xero native /Email endpoint (no customisation). Default is "none". email_recipient_source controls which contact receives the email: "primary_contact" (deal primary contact, default), "xero_contact" (Xero contact EmailAddress), or "secondary_contact" (a specific CRM contact -- requires email_recipient_contact_id set to a crm_contacts.id). Merge tags supported in subject/body: {{xero.invoice_number}}, {{xero.due_date}}, {{xero.total}}, {{company.name}}, {{contact.first_name}}. **Scopes:** `integrations:write` — _write_ Parameters: - `id` (path, uuid, required): Integration ID - `action_type` (body, string, required): Action type. Xero: xero_create_contact, xero_create_invoice, xero_update_invoice, xero_create_contact_and_invoice (alias for xero_create_invoice -- contact is auto-resolved in both), xero_create_repeating_invoice. Other platforms: slack_send_message, facebook_conversion, portant_generate_document, activecampaign_add_to_automation, calcom_create_booking, calcom_add_guests, zoom_create_meeting. - `params` (body, object, required): Action-specific parameters. For Xero invoice actions: email_mode, email_subject, email_body, email_attach_pdf, email_recipient_source, email_recipient_contact_id, and email_recipient are optional email config fields. For xero_create_repeating_invoice: contact_id (required), schedule_period (required), schedule_unit ("WEEKLY" | "MONTHLY" | "YEARLY", required), account_code (required), tax_type (required), approve_for_sending (boolean, default true -- controls whether Xero auto-emails the contact on each invoice cycle), deal_product_ids (array of crm_deal_products.id -- use when creating from the opportunity page modal against a known deal; requires data.deal.id), product_ids (array of crm_products.id -- use from the automation wizard where deal context is resolved at runtime). Provide exactly one of deal_product_ids or product_ids. Usage-Based category products are automatically skipped. - `params.email_recipient_source` (body, "primary_contact" | "xero_contact" | "secondary_contact"): Who receives the invoice email. "primary_contact" uses the deal primary contact email (default). "xero_contact" uses the EmailAddress on the Xero contact record. "secondary_contact" uses the CRM contact specified by email_recipient_contact_id. Only applies when email_mode is "trustpager" or "xero". - `params.email_recipient_contact_id` (body, uuid): crm_contacts.id of the secondary contact to email. Required when email_recipient_source is "secondary_contact". Only available via the opportunity-page modal or direct API calls (not the automation wizard). - `params.deal_product_ids` (body, uuid[]): For xero_create_repeating_invoice: array of crm_deal_products.id to include on the repeating invoice. Use when creating from the opportunity-page modal. Requires a deal ID to be present in the action data context. Mutually exclusive with product_ids. - `params.approve_for_sending` (body, boolean): For xero_create_repeating_invoice: whether Xero auto-emails the invoice to the contact on each billing cycle. Defaults to true. Set to false to suppress automatic emails (the invoice will still be created in Xero and marked as sent). --- ## Webhooks Manage incoming and outgoing webhooks for external system integration. ### GET /webhooks/incoming List incoming webhooks. **Scopes:** `webhooks:read` ### GET /webhooks/incoming/:id Retrieve an incoming webhook. **Scopes:** `webhooks:read` Parameters: - `id` (path, uuid, required): Webhook ID ### POST /webhooks/incoming Create an incoming webhook. **Scopes:** `webhooks:write` — _write_ ### PATCH /webhooks/incoming/:id Update an incoming webhook. **Scopes:** `webhooks:write` — _write_ Parameters: - `id` (path, uuid, required): Webhook ID ### DELETE /webhooks/incoming/:id Delete an incoming webhook. **Scopes:** `webhooks:write` — _write_ Parameters: - `id` (path, uuid, required): Webhook ID ### GET /webhooks/outgoing List outgoing webhooks. **Scopes:** `webhooks:read` ### GET /webhooks/outgoing/:id Retrieve an outgoing webhook. **Scopes:** `webhooks:read` Parameters: - `id` (path, uuid, required): Webhook ID ### POST /webhooks/outgoing Create an outgoing webhook. **Scopes:** `webhooks:write` — _write_ ### PATCH /webhooks/outgoing/:id Update an outgoing webhook. **Scopes:** `webhooks:write` — _write_ Parameters: - `id` (path, uuid, required): Webhook ID ### DELETE /webhooks/outgoing/:id Delete an outgoing webhook. **Scopes:** `webhooks:write` — _write_ Parameters: - `id` (path, uuid, required): Webhook ID ### POST /webhooks/outgoing/:id/test Send a test event to an outgoing webhook. **Scopes:** `webhooks:write` — _write_ Parameters: - `id` (path, uuid, required): Webhook ID ### GET /webhooks/outgoing/:id/logs View delivery logs for an outgoing webhook. **Scopes:** `webhooks:read` Parameters: - `id` (path, uuid, required): Webhook ID ### GET /webhooks/subscriptions List webhook event subscriptions. **Scopes:** `webhooks:read` ### POST /webhooks/subscriptions Subscribe to webhook events. url, events array, and secret are required. **Scopes:** `webhooks:write` — _write_ Parameters: - `url` (body, string, required): Delivery URL - `events` (body, string[], required): Event types to subscribe to - `secret` (body, string, required): Signing secret for payload verification ### PATCH /webhooks/subscriptions/:id Update a webhook subscription. **Scopes:** `webhooks:write` — _write_ Parameters: - `id` (path, uuid, required): Subscription ID ### DELETE /webhooks/subscriptions/:id Delete a webhook subscription. **Scopes:** `webhooks:write` — _write_ Parameters: - `id` (path, uuid, required): Subscription ID ### GET /webhooks/subscriptions/:id/logs List delivery logs for a webhook subscription. **Scopes:** `webhooks:read` Parameters: - `id` (path, uuid, required): Subscription ID --- ## AI AI-powered features including text editing, call coaching, needs analysis, form filling, pipeline generation, image generation, reference-guided image editing, text-to-speech generation, and AI tool generators (form, document, whiteboard, website page) that create fully-structured workspace assets from a plain-language prompt. ### POST /ai/edit-text Rewrite, improve, translate, summarize, or edit text using AI. Costs 1 credit. Accepts both "text"/"instruction" or "originalText"/"editInstructions" field names. **Scopes:** `ai:use` — _write_ Parameters: - `text` (body, string, required): Text to edit (also accepted as "originalText") - `instruction` (body, string, required): Editing instruction, e.g. "make it more professional", "translate to French" (also accepted as "editInstructions") - `writingStyle` (body, string): Writing style ID (optional) - `formContext` (body, object): Form context for inline editing (optional) ### POST /ai/call-coaching Analyze a call, meeting, or SMS transcript for coaching insights. Returns performance scores, strengths, improvements, and coaching summary. Costs 3 credits. Provide transcript_id (preferred, auto-resolves text and metadata) or transcript_text (raw text). **Scopes:** `ai:use` — _write_ Parameters: - `transcript_id` (body, uuid): Transcript UUID (preferred -- auto-resolves transcript text, source type, and context) - `transcript_text` (body, string): Raw transcript text (use only if no transcript_id). Lines formatted as "Speaker Name: text..." - `team_member_name` (body, string, required): Full name of the team member to coach. Must match a company user. - `source_type` (body, string): Type of interaction: zoom_call (sales), zoom_meeting (team), sms_conversation. Auto-detected from transcript_id. Defaults to zoom_call. ### POST /ai/generate-pipeline Auto-generate a pipeline structure with stages from a text description. Costs 3 credits. **Scopes:** `ai:use` — _write_ Parameters: - `description` (body, string, required): Description of the business/sales process - `business_type` (body, string): Type of business (e.g. "B2B SaaS", "retail") - `industry` (body, string): Industry for tailored stage suggestions - `success_criteria` (body, string): What makes a deal "won" - `lost_criteria` (body, string): What makes a deal "lost" - `additional_context` (body, string): Any other relevant context ### POST /ai/needs-analysis Run AI needs analysis on a deal -- extracts client requirements and recommendations from all linked transcripts and activities. Provide deal_id; the system fetches all related data automatically. **Scopes:** `ai:use` — _write_ Parameters: - `deal_id` (body, uuid, required): Deal ID. The system fetches all related transcripts, contacts, and activities. - `master_prompt` (body, string): Optional custom instructions to guide the analysis focus ### POST /ai/fill-form Use AI to pre-fill form fields based on a prompt. template_id and prompt are required. The system fetches form fields automatically. **Scopes:** `ai:use` — _write_ Parameters: - `template_id` (body, uuid, required): Form template ID - `prompt` (body, string, required): Instructions for filling the form, e.g. "Fill this incident report for a slip-and-fall at the warehouse on 28 March 2026" ### POST /ai/generate-image Generate an AI image from a text prompt using the NanoBanana model (google:4@1). Returns a CDN image URL, seed, and dimensions. Costs credits. The image is automatically saved to company files. IMPORTANT: Only 11 specific dimension pairs are accepted -- invalid dimensions return a VALIDATION_ERROR with the full list. Default: 1024x1024. Default output format is lossless PNG; pass output_format to override. **Scopes:** `ai:use` — _write_ Parameters: - `prompt` (body, string, required): Text description of the image to generate (2-3000 chars). Be descriptive for best results. - `width` (body, number): Image width in pixels. Must be part of a valid pair. Default 1024. Valid pairs: 1024x1024, 1248x832, 832x1248, 1184x864, 864x1184, 896x1152, 1152x896, 768x1344, 1344x768, 1536x672, 672x1536. - `height` (body, number): Image height in pixels. Must be part of a valid pair with width. See width description for all valid pairs. Default 1024. - `seed` (body, number): Random seed for reproducible results. Omit for random. - `output_format` (body, string): Output image format. Values: "PNG" (default, lossless), "WEBP", "JPEG". PNG is recommended when you plan to run optimize_image afterwards (PNG -> WebP variants). WEBP/JPEG are smaller for direct use. ### POST /ai/edit-image Edit an existing image using AI reference guidance. Provide 1-3 reference image URLs (the subject to preserve) and a text prompt describing the new surroundings, style, or lighting. Uses the NanoBanana model (google:4@1, identity-preserving). Returns a CDN image URL, seed, and dimensions. Costs credits. The result is automatically saved to company files. IMPORTANT: Only 11 specific dimension pairs are accepted -- same constraints as generate-image. Reference URLs must be public http(s) URLs. **Scopes:** `ai:use` — _write_ Parameters: - `prompt` (body, string, required): Text description of the desired output (2-3000 chars). Describe the new setting, style, or lighting around the preserved subject. - `reference_image_url` (body, string): Single reference image URL (public http(s) URL). Use this OR reference_image_urls -- not both. - `reference_image_urls` (body, string[]): Array of 1-3 reference image URLs (public http(s) URLs). Use this OR reference_image_url -- not both. - `width` (body, number): Output width in pixels. Must be part of a valid pair. Default 1024. Valid pairs: 1024x1024, 1248x832, 832x1248, 1184x864, 864x1184, 896x1152, 1152x896, 768x1344, 1344x768, 1536x672, 672x1536. - `height` (body, number): Output height in pixels. Must be part of a valid pair with width. Default 1024. - `seed` (body, number): Random seed for reproducible results. Omit for random. - `output_format` (body, string): Output image format. Values: "PNG" (default, lossless), "WEBP", "JPEG". PNG is recommended when planning to run optimize_image afterwards (PNG -> WebP variants). WEBP/JPEG are smaller for direct use. ### POST /ai/upscale-image Upscale an image using AI (Runware imageUpscale). Multiplies the source image dimensions by the given factor (2x, 3x, or 4x). Accepts any public http(s) image URL -- including URLs returned by generate-image or edit-image. The upscaled result is automatically saved to R2 and company files. Costs the same credits as generate-image (ai_image feature cost). Requires scope: ai:use. **Scopes:** `ai:use` — _write_ Parameters: - `image_url` (body, string, required): Public http(s) URL of the image to upscale. - `upscale_factor` (body, number): Multiplication factor: 2, 3, or 4. Default 2. A 1024x1024 source at 2x becomes 2048x2048. - `output_format` (body, string): Output format: "PNG" (default, lossless), "WEBP" (recommended -- ~10x smaller than PNG at same quality), "JPEG". Use WEBP if you plan to pass the result to optimize_image -- it stays within the 3 MB optimizer ceiling. ### POST /ai/generate-speech Convert text to speech audio using ElevenLabs. Uploads the audio to R2 and saves to company files. Returns an audio URL, duration, file size, and credits charged. Costs 50 credits per 100 characters (minimum 50 credits). Voice resolution order: workspace voices by name -> workspace default -> platform voices -> presets (jessica/rachel/adam/sam) -> raw ElevenLabs voice ID. Optionally returns timed transcript segments (sentences, phrases, or words) for animation timing (e.g. Remotion video sync). **Scopes:** `ai:use` — _write_ Parameters: - `text` (body, string, required): Text to convert to speech (2-5000 characters) - `voice` (body, string): Voice name, preset (jessica/rachel/adam/sam), or raw ElevenLabs voice ID. Default: jessica. - `model` (body, string): ElevenLabs model: eleven_multilingual_v2 (default), eleven_monolingual_v1, eleven_turbo_v2_5 - `stability` (body, number): Voice stability 0-1 (default 0.6). Higher = more consistent, lower = more expressive. - `similarity_boost` (body, number): Voice clarity/similarity 0-1 (default 0.75) - `style` (body, number): Speaking style intensity 0-1 (default 0.4) - `output_format` (body, string): Audio output format (default: mp3_44100_128) - `name` (body, string): Custom file name for the audio (auto-generated if omitted) - `transcript` (body, string): Request timed transcript segments alongside audio. Values: "none" (default, no transcript), "sentences" (sentence-level timestamps), "phrases" (comma/clause-level timestamps), "words" (word-level timestamps). When set (not "none"), response includes transcript array of {text, start, end, frame_start, frame_end} objects and transcript_mode. frame_start/frame_end are at 30fps for Remotion animation. ### POST /ai/generate-form Generate a complete form template with AI, auto-wired to CRM variables. The generator maps fields whose labels unambiguously match a writable CRM variable (e.g. Email -> contact.email, ABN -> account.tax_number). Workspace custom fields are included in the wiring vocabulary. By default (persist=true), creates the form_templates row and all form_template_fields rows and returns the new template_id. Costs credits (ai_tool_generate). **Scopes:** `ai:use` — _write_ Parameters: - `prompt` (body, string, required): Plain-language description of the form to generate. Be specific about fields needed. - `persist` (body, boolean): If true (default), creates the form template and fields in the workspace. If false, returns only the generated structure without persisting. - `name` (body, string): Override the AI-generated form name. - `folder` (body, string): Folder to place the template in. ### POST /ai/generate-document Generate a document template with AI -- proposals, contracts, reports, invoices, letters. Produces structured sections (cover page, text blocks, tables, signature blocks) with {{variable}} tokens for auto-filling. By default (persist=true), creates the document_templates row and all document_template_sections rows and returns the new template_id. Costs credits (ai_tool_generate). **Scopes:** `ai:use` — _write_ Parameters: - `prompt` (body, string, required): Plain-language description of the document. Be specific about purpose, tone, and major sections. - `persist` (body, boolean): If true (default), creates the document template and its sections in the workspace. If false, returns only the generated structure. - `name` (body, string): Override the AI-generated document name. ### POST /ai/generate-whiteboard Generate a whiteboard/diagram template with AI -- process flows, org charts, decision trees, customer journeys, system architectures. Produces Excalidraw-compatible shapes with coordinated colour themes. By default (persist=true), creates whiteboard_templates + whiteboard_snapshots rows. Costs credits (ai_tool_generate). **Scopes:** `ai:use` — _write_ Parameters: - `prompt` (body, string, required): Plain-language description of the diagram to generate. - `persist` (body, boolean): If true (default), creates the whiteboard template and snapshot. If false, returns only the generated structure. - `name` (body, string): Override the AI-generated whiteboard name. ### POST /ai/generate-website-page Generate a complete website landing page with AI -- picks industry-appropriate sections (hero, social proof, features, pricing, FAQ, CTA, etc.) from the business description. Creates website + homepage + all page sections in one call. By default (persist=true), inserts rows into websites, website_pages, and website_page_sections. Costs credits (ai_tool_generate). **Scopes:** `ai:use` — _write_ Parameters: - `prompt` (body, string, required): Business description. Be specific about industry, tone, and target audience. - `persist` (body, boolean): If true (default), creates the website, homepage, and sections. If false, returns only the generated structure. - `name` (body, string): Override the AI-generated website name. - `page_type` (body, string): homepage (default) or page (for secondary pages like services, about, pricing). ### POST /ai/transcript-summary Server-side AI intelligence extraction from a transcript. Runs Claude on the full transcript text server-side (no transport size limits) and returns a compact structured summary. Ideal for large transcripts that exceed MCP context limits, or when you need intelligence rather than raw text. Costs 1 credit. **Scopes:** `ai:use` — _write_ Parameters: - `transcript_id` (body, uuid, required): Transcript UUID. Use GET /transcripts to find IDs. - `focus` (body, string): Optional focus area for the extraction (e.g. "product feedback", "pricing objections", "competitor comparison") --- ## Billing View billing plans, credit balance, and pricing. ### GET /billing/plan Get the company billing plan, subscription status, seat count, trial window, and plan gating flags. **Scopes:** `billing:read` ### GET /billing/balance Get the current API credit balance. **Scopes:** `billing:read` ### GET /billing/usage List credit transactions and usage history. **Scopes:** `billing:read` Parameters: - `limit` (query, number): Max results per page - `after` (query, string): Cursor for pagination ### GET /billing/pricing Get the full credit pricing table. Returns all active feature keys with credits_per_use, category, and description. Free endpoint (does not consume credits). **Scopes:** `billing:read` --- ## Service Requests Submit and manage platform improvement requests. Designed for AI agents to report missing capabilities, suggest new features, and flag issues with the MCP, API, or platform. Supports listing, filtering, note threads, inline note editing, and two-way ticket linking. ### GET /service-requests List service requests for the authenticated company. Supports cursor pagination and multiple filters. Returns requests in reverse-chronological order with full note threads and related_ids. **Scopes:** `service-requests:read` Parameters: - `status` (query, string): Filter by status: pending, in_progress, resolved, closed - `category` (query, string): Filter by category stored in context JSON: missing_tool, missing_field, missing_filter, better_errors, new_capability, workflow_improvement, documentation, bug_report, other - `priority` (query, string): Filter by priority stored in context JSON: low, medium, high, critical - `search` (query, string): Case-insensitive search across title and description fields - `affected_tool` (query, string): Filter to requests that list this tool name in context.affected_tools - `submitted_by_me` (query, boolean): If true, return only requests submitted by the API key owner (user_id match) - `cursor` (query, string): Pagination cursor from a previous response - `limit` (query, number): Page size (default 25, max 100) ```bash curl https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/service-requests?status=pending&priority=high \ -H "Authorization: Bearer tp_live_..." ``` ### GET /service-requests/:id Get a single service request by ID. Returns the full record including notes array, related_ids, context JSON, and slack_message_ts. **Scopes:** `service-requests:read` Parameters: - `id` (path, string, required): Service request UUID ```bash curl https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/service-requests/f112de7b-c7df-4193-bacd-2d43c31c1f11 \ -H "Authorization: Bearer tp_live_..." ``` ### POST /service-requests Submit a platform improvement request. Stores the request, sends a Slack notification to the engineering team, and returns the created record. Now accepts an optional related_ids array to link this request to existing requests at creation time (two-way symmetry is maintained automatically). **Scopes:** `service-requests:write` — _write_ Parameters: - `title` (body, string, required): Brief summary of the improvement needed (max 200 chars) - `description` (body, string, required): Detailed explanation: what you tried, what happened, what should happen instead (max 50000 chars) - `category` (body, string): Category: missing_tool, missing_field, missing_filter, better_errors, new_capability, workflow_improvement, documentation, bug_report, other - `affected_tools` (body, string[]): Array of MCP tool names or API endpoints affected, e.g. ["list_opportunities", "GET /opportunities"] - `suggested_solution` (body, string): What you wish existed from the API/MCP consumer perspective - `use_case` (body, string): The real-world user task that triggered this request - `priority` (body, string): Priority: low, medium, high, critical - `related_ids` (body, string[]): UUIDs of existing service requests to link to this one. Must belong to the same company. Two-way links are created automatically. ```bash curl -X POST https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/service-requests \ -H "Authorization: Bearer tp_live_..." \ -H "Content-Type: application/json" \ -d '{ "title": "Add status filter to list_opportunities", "description": "Cannot filter opportunities by status (won/lost/open). Had to fetch all opportunities and filter client-side.", "category": "missing_filter", "affected_tools": ["list_opportunities"], "suggested_solution": "Add a status query parameter to GET /opportunities", "use_case": "User asked for a report of won deals from last quarter", "priority": "medium", "related_ids": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"] }' ``` ### POST /service-requests/search Semantic search across the workspace's service requests using vector embeddings (Voyage voyage-3, 1024 dimensions). Returns results ranked by cosine similarity. Useful for: finding duplicate requests before filing, grouping related open tickets, retrieving historical resolutions. Scoped per-workspace -- cross-company search is only available via the internal agent-write webhook. **Scopes:** `service-requests:read` Parameters: - `query` (body, string, required): Natural-language description of what you are looking for (max 5000 chars) - `status` (body, string): Optional: restrict results to a single status: pending, in_progress, completed, rejected, cancelled, backlog - `limit` (body, number): Max results to return, 1-50 (default 10) - `threshold` (body, number): Minimum cosine similarity 0-1 (default 0.5). Lower = broader recall. ```bash curl -X POST https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/service-requests/search \ -H "Authorization: Bearer tp_live_..." \ -H "Content-Type: application/json" \ -d '{ "query": "missing status filter on list opportunities", "status": "pending", "limit": 5 }' ``` ### POST /service-requests/:id/notes Add a note to an existing service request. Notes are appended to a JSONB array on the record. Each note stores a UUID, the user_id of the API key owner, the content text, and a created_at timestamp. Does NOT change the request status. Max content length raised to 20000 chars. **Scopes:** `service-requests:write` — _write_ Parameters: - `id` (path, string, required): Service request UUID - `content` (body, string, required): Text content of the note (max 20000 chars) ```bash curl -X POST https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/service-requests/f112de7b-c7df-4193-bacd-2d43c31c1f11/notes \ -H "Authorization: Bearer tp_live_..." \ -H "Content-Type: application/json" \ -d '{ "content": "Investigated the issue -- confirmed the filter parameter is missing from the query builder. Scheduled for sprint 14." }' ``` ### PATCH /service-requests/:id/notes/:noteId Edit an existing note on a service request. Only the original note author (matched by user_id of the API key owner) can edit their own notes. Adds an edited_at timestamp to the note object on success. Does NOT change the request status. **Scopes:** `service-requests:write` — _write_ Parameters: - `id` (path, string, required): Service request UUID - `noteId` (path, string, required): Note UUID (from the id field inside the notes array) - `content` (body, string, required): Replacement content for the note (max 20000 chars) ```bash curl -X PATCH https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/service-requests/f112de7b-c7df-4193-bacd-2d43c31c1f11/notes/572f46fd-a78b-4115-8636-215cdd5c204a \ -H "Authorization: Bearer tp_live_..." \ -H "Content-Type: application/json" \ -d '{ "content": "Confirmed the filter parameter is missing. Now affects list_companies too. Escalating priority." }' ``` ### POST /service-requests/:id/links Add or remove related service request links. Both directions are maintained automatically (two-way symmetry). A request cannot link to itself. All IDs must belong to the same company -- cross-company linking is rejected. **Scopes:** `service-requests:write` — _write_ Parameters: - `id` (path, string, required): Service request UUID to update links on - `add` (body, string[]): UUIDs of service requests to link to this one. Each must exist in this company. - `remove` (body, string[]): UUIDs of service requests to unlink from this one. Two-way link is also removed. ```bash curl -X POST https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/service-requests/f112de7b-c7df-4193-bacd-2d43c31c1f11/links \ -H "Authorization: Bearer tp_live_..." \ -H "Content-Type: application/json" \ -d '{ "add": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"], "remove": [] }' ``` --- ## Approvals Manage API actions queued for human approval. When an API key has a "with Approval" permission level, write operations are held here until a team member reviews them. ### GET /approvals List pending (or all) approval requests for the company. **Scopes:** _none_ Parameters: - `status` (query, string): Filter by status: pending (default), approved, rejected, expired, or all - `limit` (query, number): Max results per page (default 25, max 100) ### GET /approvals/:id Get a single approval request by ID. **Scopes:** _none_ Parameters: - `id` (path, string, required): Approval UUID ### POST /approvals/:id/approve Approve and execute a queued API action. The stored operation is run immediately and the result is saved on the approval record. **Scopes:** _none_ — _write_ Parameters: - `id` (path, string, required): Approval UUID ### POST /approvals/:id/reject Reject a queued API action. The action is NOT executed. An optional rejection reason can be provided. **Scopes:** _none_ — _write_ Parameters: - `id` (path, string, required): Approval UUID - `reason` (body, string): Rejection reason (optional) ```bash { "reason": "Duplicate contact already exists" } ``` ### POST /approvals/:id/assign Reassign the primary or secondary reviewer for a pending approval. Useful for routing approvals to the right team member. Only works on approvals with status "pending". Requires the approvals:reassign scope. **Scopes:** `approvals:reassign` — _write_ Parameters: - `id` (path, string, required): Approval UUID - `primary_assignee_id` (body, string, required): User UUID of the new primary reviewer. Must be a member of the company. - `secondary_assignee_id` (body, string): User UUID of secondary reviewer. Pass null to clear. Must be a member of the company if provided. ```bash { "primary_assignee_id": "user-uuid", "secondary_assignee_id": null } ``` --- ## AI Instructions Structured behavioral instructions for AI agents. Call once per session, cache the result. Free (0 credits). ### GET /ai-instructions Get behavioral instructions for AI agents using the API. Returns structured guidance on email sending, automation setup, deal management, scheduling, document workflows, formatting rules, and common mistakes to avoid. Free (0 credits). **Scopes:** _none_ --- ## Voices Manage saved workspace voices and browse platform-recommended voices. Workspace voices are saved ElevenLabs voices scoped to your company. Platform voices are pre-seeded recommended voices available to all workspaces (read-only). Use these with POST /ai/generate-speech. ### GET /voices List all voices -- workspace-saved voices (source: "workspace") and platform-recommended voices (source: "platform"). Workspace voices appear first, then platform voices sorted by sort_order. **Scopes:** `voices:read` ### POST /voices Save an ElevenLabs voice to the workspace voice library. provider_voice_id is required. Name and preview URL are auto-resolved from ElevenLabs if not provided. Each voice ID can only be saved once per workspace. **Scopes:** `voices:write` — _write_ Parameters: - `provider_voice_id` (body, string, required): ElevenLabs voice ID. Find IDs via the ElevenLabs voice library or from platform voices in GET /voices. - `name` (body, string): Display name for the voice. Auto-resolved from ElevenLabs if omitted. - `description` (body, string): Description of the voice and its best use cases - `model` (body, string): Preferred ElevenLabs model. Default: eleven_multilingual_v2. - `settings` (body, object): Voice settings: { stability: 0-1, similarity_boost: 0-1, style: 0-1 } - `is_default` (body, boolean): Set as workspace default voice (unsets any previous default) - `provider` (body, string): Voice provider. Default: elevenlabs. ```bash { "provider_voice_id": "cgSgspJ2msm6clMCkdW9", "name": "Jessica", "description": "Warm, friendly voice for customer-facing audio" } ``` ### GET /voices/:id Get a single workspace-saved voice by ID. Only returns workspace voices (not platform voices). **Scopes:** `voices:read` Parameters: - `id` (path, uuid, required): Voice UUID ### PATCH /voices/:id Update a workspace-saved voice. Can update name, description, model, settings, or set as default. Only workspace voices can be updated. **Scopes:** `voices:write` — _write_ Parameters: - `id` (path, uuid, required): Voice UUID - `name` (body, string): New display name - `description` (body, string): New description - `model` (body, string): Preferred ElevenLabs model - `settings` (body, object): Voice settings: { stability, similarity_boost, style } (all 0-1) - `is_default` (body, boolean): Set as workspace default (unsets previous default) ### DELETE /voices/:id Remove a saved voice from the workspace. Only workspace voices can be deleted. Platform voices are read-only. **Scopes:** `voices:delete` — _write_ Parameters: - `id` (path, uuid, required): Voice UUID to delete ### GET /voices/provider-catalog List the upstream voice provider catalogue. Returns each voice with its namespaced provider voice_id (e.g. "11labs-Noah"), name, accent, gender, and preview URL. Use this to discover valid values for set_provider_voice_mapping, or to troubleshoot voice rejections when creating or provisioning voice agents. **Scopes:** `voices:read` ### POST /voices/:id/provider-mapping Map a local voice (workspace or platform) to a provider-format voice ID so voice agent creation and provisioning use it instead of the fallback voice. Pass a namespaced voice_id (e.g. "11labs-Noah") from GET /voices/provider-catalog. Pass null to clear an existing mapping. **Scopes:** `voices:write` — _write_ Parameters: - `id` (path, uuid, required): Local voice UUID (workspace or platform voice) - `retell_voice_id` (body, string|null, required): Provider-format voice ID (e.g. "11labs-Noah"). Pass null to clear. ```bash curl -X POST "https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/voices/voice-uuid/provider-mapping" \ -H "Authorization: Bearer tp_live_..." \ -H "Content-Type: application/json" \ -d '{"retell_voice_id": "11labs-Noah"}' ``` ### POST /voices/sync-provider-catalog Idempotent sync: pulls the upstream provider catalogue, finds same-provider name matches for every local voice (workspace + platform) missing a mapping, and writes the provider voice ID. Already-mapped voices are skipped unless force=true. Returns matched/skipped/unmatched counts per voice. **Scopes:** `voices:write` — _write_ Parameters: - `force` (body, boolean): re-evaluate voices that already have a mapping (default false) - `voice_id` (body, string): Scope sync to a single local voice UUID ```bash curl -X POST "https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/voices/sync-provider-catalog" \ -H "Authorization: Bearer tp_live_..." \ -H "Content-Type: application/json" \ -d '{}' ``` --- ## Agent Proposals Agent action proposals submitted by AI agents for human review and approval. Agents submit proposals when they identify an action that should be taken but requires a human decision. Proposals surface in the Agent Hub at /auto/agent-hub?tab=proposals. ### GET /agent-proposals List agent proposals. Defaults to pending. Use status=all to see the full history. **Scopes:** `agent-ops:read` Parameters: - `status` (query, string): Filter by status: pending (default), approved, rejected, expired, executed, failed, or all - `agent_name` (query, string): Filter by the agent that created the proposal - `category` (query, string): Filter by category: enable, fix, config, strategy, diagnose, learn - `limit` (query, number): Max results per page (default 25, max 100) ### GET /agent-proposals/:id Get a single agent proposal by ID. **Scopes:** `agent-ops:read` Parameters: - `id` (path, string, required): Proposal UUID ### POST /agent-proposals Create an agent proposal. Submitted by AI agents when they identify an action that requires human approval. The proposal is surfaced in the Agent Hub for review. **Scopes:** `agent-ops:write` — _write_ Parameters: - `agent_name` (body, string, required): Name of the agent submitting the proposal - `category` (body, string, required): Category: enable, fix, config, strategy, diagnose, learn - `title` (body, string, required): Short action-oriented title - `proposed_action` (body, object, required): Action to execute on approval: { "tool": "", "params": { ... } }. Use tool: "custom" for manual actions. - `description` (body, string): Explanation of why this action is needed - `context` (body, object): Supporting evidence as key-value pairs (e.g. { "days_stale": 22 }) - `priority` (body, string): Priority: low, medium (default), high, critical - `options` (body, array): List of choices for the reviewer. Each item: { "label": "...", "action": { "tool": "...", "params": {...} } } - `expires_at` (body, string): ISO datetime when this proposal expires (default: 7 days) ```bash { "agent_name": "project-manager", "category": "enable", "priority": "high", "title": "Enable welcome automation", "description": "The welcome sequence automation has been disabled for 5 days. 3 new clients received no onboarding email.", "context": { "affected_deals": 3, "pipeline": "Fulfillment Kickoff", "days_disabled": 5 }, "proposed_action": { "tool": "enable_automation", "params": { "id": "auto-uuid" } } } ``` ### POST /agent-proposals/:id/approve Approve a pending proposal and execute its proposed action immediately. The action is resolved via a tool-to-API mapping and executed server-side. The result is stored on the proposal record (status becomes "executed" on success, "failed" on error). **Scopes:** `agent-ops:write` — _write_ Parameters: - `id` (path, string, required): Proposal UUID - `answer` (body, string): Optional approval note (stored for audit trail) - `selected_option` (body, number): If the proposal has options, the 0-based index of the chosen option ```bash { "answer": "Approved - proceed with enabling the automation." } ``` ### POST /agent-proposals/:id/reject Reject a pending proposal. The proposed action is NOT executed. The rejection reason is stored and visible to the agent. **Scopes:** `agent-ops:write` — _write_ Parameters: - `id` (path, string, required): Proposal UUID - `reason` (body, string): Reason for rejection (shown to the agent for learning) ```bash { "reason": "Automation is intentionally paused during Q2 migration." } ``` --- ## Lead Generation Search for businesses via Google Maps (powered by Apify), match against existing CRM records, save search configurations, and import results as contacts, customers, and deals. Requires lead-gen:read, lead-gen:write, or lead-gen:delete scopes. ### POST /lead-gen/search Start a new Google Maps business search via Apify. For max_results <= 100 the search runs synchronously and returns results immediately. For 101-500 results, the search runs asynchronously -- poll GET /lead-gen/searches/:id until status is "completed". When scrape_contacts=true, the search always runs asynchronously (website scraping exceeds sync timeout), website_filter is forced to "withWebsite", and results without a valid email are dropped before saving (email guarantee) -- result_count reflects only kept rows. Results are automatically deduplicated and matched against existing CRM contacts (by phone) and customers (by website domain). Credits are charged based on max_results tier. **Scopes:** `lead-gen:write` — _write_ Parameters: - `query` (body, string, required): Business type or search term, e.g. "electricians", "dentists", "restaurants" - `location` (body, string, required): Location to search in, e.g. "Sydney, NSW", "Melbourne, VIC, Australia" - `max_results` (body, number): Max results to fetch (default 100, max 500). Credit tiers: 1-50, 51-100, 101-200, 201-500 - `radius_km` (body, number): Search radius in kilometres from the location centre - `saved_search_id` (body, uuid): UUID of a saved search to link this run to (updates run_count and last_run_at) - `place_minimum_stars` (body, string): Minimum Google rating: "two", "twoAndHalf", "three", "threeAndHalf", "four", "fourAndHalf" - `website_filter` (body, string): Filter by website presence: "allPlaces" (default), "withWebsite", "withoutWebsite" - `skip_closed_places` (body, boolean): Skip permanently closed businesses (default true) - `category_filter_words` (body, string[]): Only return results whose Google category contains one of these words - `scrape_contacts` (body, boolean): Scrape email addresses from business websites. When true: always runs async (returns immediately, poll for completion), forces website_filter to "withWebsite", drops results without a valid email (email guarantee), result_count reflects only kept rows. Use lead_gen_get_search to poll until status="completed". - `language` (body, string): Language code for results (default "en") ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/lead-gen/search" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "electricians", "location": "Sydney, NSW", "max_results": 50, "place_minimum_stars": "three", "website_filter": "withWebsite" }' ``` ### GET /lead-gen/searches List past lead generation searches with cursor-based pagination, most recent first. Filter by status or saved search. **Scopes:** `lead-gen:read` Parameters: - `status` (query, string): Filter by status: "running", "completed", "failed" - `saved_search_id` (query, uuid): Filter to runs linked to a specific saved search - `limit` (query, number): Max results per page (default 25, max 100) - `after` (query, string): Cursor for next page ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/lead-gen/searches?status=completed&limit=10" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### GET /lead-gen/searches/:id Get a single search by ID including all results. For async searches (max_results > 100) still in "running" status, this endpoint automatically polls Apify and processes results if the run has completed. Poll every 30-60 seconds until status is "completed" or "failed". Results are sorted by Google rating (highest first). **Scopes:** `lead-gen:read` Parameters: - `id` (path, uuid, required): Search UUID ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/lead-gen/searches/8685d0d4-..." \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### POST /lead-gen/import Import selected search results into the CRM. Each result creates one contact AND one customer, linked together via crm_contact_customers. A note activity is automatically logged recording the Google Maps source. Optionally creates a deal placed into a specified pipeline stage via crm_deal_pipeline_placements. Only results with imported=false are imported -- already-imported results are filtered out in code (not via a PostgREST filter, to correctly handle null values). Each imported record returns contact_id, customer_id, and deal_id (null if no pipeline provided). Partial failures are non-fatal: a failed individual record returns an error field instead of IDs and is excluded from imported_count. **Scopes:** `lead-gen:write` — _write_ Parameters: - `result_ids` (body, uuid[], required): Array of result UUIDs to import (from the results array of a search) - `pipeline_id` (body, uuid): UUID of pipeline to create deals in (requires stage_id) - `stage_id` (body, uuid): UUID of stage to create deals in (requires pipeline_id) - `tags` (body, string[]): Tags to apply to imported contacts and customers - `lead_source` (body, string): Lead source label for imported records (default "Lead Generation") ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/lead-gen/import" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "result_ids": ["a1b2c3d4-...", "b2c3d4e5-..."], "pipeline_id": "pipe-uuid-...", "stage_id": "stage-uuid-...", "tags": ["lead-gen", "electricians"], "lead_source": "Google Maps" }' ``` ### POST /lead-gen/saved-searches Create a saved search configuration for recurring use. Saved searches store the query, location, and default import settings. Run a saved search by calling POST /lead-gen/search with saved_search_id -- the run_count and last_run_at fields update automatically. **Scopes:** `lead-gen:write` — _write_ Parameters: - `name` (body, string, required): Human-readable name, e.g. "Sydney Electricians" - `search_query` (body, string, required): Business type or search term - `location` (body, string, required): Location to search in - `max_results` (body, number): Default max results per run (1-500) - `radius_km` (body, number): Search radius in kilometres - `default_pipeline_id` (body, uuid): Default pipeline for deal creation on import - `default_stage_id` (body, uuid): Default stage for deal creation on import - `default_tags` (body, string[]): Default tags to apply on import - `enrich_emails` (body, boolean): When true, all runs of this saved search automatically set scrape_contacts=true: always async, forces website_filter to "withWebsite", drops results without a valid email (email guarantee). Saved searches with enrich_emails=true will never return email-less rows. ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/lead-gen/saved-searches" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Sydney Electricians", "search_query": "electricians", "location": "Sydney, NSW", "max_results": 100, "default_tags": ["lead-gen", "electricians"] }' ``` ### GET /lead-gen/saved-searches List all active (non-archived) saved search configurations. Returns the most recently updated first. **Scopes:** `lead-gen:read` Parameters: - `limit` (query, number): Max results per page (default 25, max 100) - `after` (query, string): Cursor for next page ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/lead-gen/saved-searches" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### GET /lead-gen/saved-searches/:id Get a single saved search configuration by ID. **Scopes:** `lead-gen:read` Parameters: - `id` (path, uuid, required): Saved search UUID ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/lead-gen/saved-searches/7d05612a-..." \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### PUT /lead-gen/saved-searches/:id Partial update a saved search configuration. Only provided fields are updated. **Scopes:** `lead-gen:write` — _write_ Parameters: - `id` (path, uuid, required): Saved search UUID - `name` (body, string): New name - `search_query` (body, string): Updated search query - `location` (body, string): Updated location - `max_results` (body, number): Updated max results - `radius_km` (body, number): Updated radius in km - `default_pipeline_id` (body, uuid): Updated default pipeline UUID - `default_stage_id` (body, uuid): Updated default stage UUID - `default_tags` (body, string[]): Updated default tags - `enrich_emails` (body, boolean): Updated email enrichment default. When true, all runs auto-set scrape_contacts=true (always async, forces website="withWebsite", email guarantee). ```bash curl -X PUT \ "https://api.trustpager.com/functions/v1/api/v1/lead-gen/saved-searches/7d05612a-..." \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "Sydney Electricians (Updated)", "max_results": 200}' ``` ### DELETE /lead-gen/saved-searches/:id Archive (soft-delete) a saved search. The saved search will no longer appear in list results. Past searches linked to it are preserved. Returns 204 No Content on success. **Scopes:** `lead-gen:delete` — _write_ Parameters: - `id` (path, uuid, required): Saved search UUID to archive ```bash curl -X DELETE \ "https://api.trustpager.com/functions/v1/api/v1/lead-gen/saved-searches/7d05612a-..." \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### POST /lead-gen/results Record a manual result for a lead-gen search result (e.g. mark as contacted, won, not interested). Creates an activity log entry on the linked contact if one exists. **Scopes:** `lead-gen:write` — _write_ Parameters: - `search_id` (body, uuid, required): Search UUID this result belongs to - `place_id` (body, string, required): External place identifier from the search result - `result_type` (body, string, required): Outcome type: contacted, not_interested, won, lost - `notes` (body, string): Optional notes about the outcome ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/lead-gen/results" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"search_id":"7d05612a-...","place_id":"ChIJ...","result_type":"contacted","notes":"Left voicemail"}' ``` ### POST /lead-gen/initiatives Create a new outreach initiative (multi-step automated sequence). An initiative defines the name, goal, and optional daily send cap. Add steps via POST /lead-gen/initiatives/:id/steps, then enrol leads via POST /lead-gen/initiatives/:id/enrol. **Scopes:** `lead-gen:write` — _write_ Parameters: - `name` (body, string, required): Initiative display name - `description` (body, string): Internal description of this outreach goal - `daily_send_cap` (body, integer): Max outreach actions per day across all enrolments (default: no cap beyond per-user limits) - `status` (body, string): Initial status: draft (default) or active ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/lead-gen/initiatives" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Q3 Roofing Outreach","description":"Multi-touch email + SMS sequence","status":"draft"}' ``` ### GET /lead-gen/initiatives List all outreach initiatives for your workspace. Returns initiatives ordered by creation date descending, with step and enrolment counts. **Scopes:** `lead-gen:read` Parameters: - `status` (query, string): Filter by status: draft, active, paused, completed - `limit` (query, integer): Max results to return (default: 50) - `after` (query, string): Pagination cursor from previous response ```bash curl \ "https://api.trustpager.com/functions/v1/api/v1/lead-gen/initiatives?status=active" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### GET /lead-gen/initiatives/:id Retrieve a single outreach initiative including its full step list and summary enrolment counts. **Scopes:** `lead-gen:read` Parameters: - `id` (path, uuid, required): Initiative UUID ```bash curl \ "https://api.trustpager.com/functions/v1/api/v1/lead-gen/initiatives/a1b2c3d4-..." \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### PATCH /lead-gen/initiatives/:id Update an outreach initiative's name, description, status, or daily send cap. Set status to "active" to begin processing enrolments, "paused" to temporarily halt sends. **Scopes:** `lead-gen:write` — _write_ Parameters: - `id` (path, uuid, required): Initiative UUID - `name` (body, string): Updated name - `description` (body, string): Updated description - `status` (body, string): New status: draft, active, paused, completed - `daily_send_cap` (body, integer): Updated daily send cap (null to remove cap) ```bash curl -X PATCH \ "https://api.trustpager.com/functions/v1/api/v1/lead-gen/initiatives/a1b2c3d4-..." \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"status":"active"}' ``` ### DELETE /lead-gen/initiatives/:id Delete an outreach initiative. All associated steps and enrolments are also removed. Active enrolments in progress will be cancelled. Returns 204 No Content on success. **Scopes:** `lead-gen:delete` — _write_ Parameters: - `id` (path, uuid, required): Initiative UUID to delete ```bash curl -X DELETE \ "https://api.trustpager.com/functions/v1/api/v1/lead-gen/initiatives/a1b2c3d4-..." \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### POST /lead-gen/initiatives/:id/steps Add a new step to an outreach initiative. Steps are executed in step_number order. Supported action types: send_gmail_email (requires gmail_connection_id and subject/body templates with {{lead.*}} tokens), send_sms (requires sms body with {{lead.*}} tokens), notify_assigned_staff (sends an internal alert email to the enrolling user). **Scopes:** `lead-gen:write` — _write_ Parameters: - `id` (path, uuid, required): Initiative UUID - `step_number` (body, integer, required): Execution order (1-based). Existing steps with >= this number are shifted down. - `action_type` (body, string, required): Action to perform: send_gmail_email, send_sms, notify_assigned_staff - `delay_days` (body, integer): Days to wait after the previous step before executing this one (default: 0) - `config` (body, object, required): Action-specific config. For send_gmail_email: {gmail_connection_id, subject, body}. For send_sms: {body}. For notify_assigned_staff: {subject, body}. ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/lead-gen/initiatives/a1b2c3d4-.../steps" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"step_number":1,"action_type":"send_gmail_email","delay_days":0,"config":{"gmail_connection_id":"conn-id","subject":"Hi {{lead.name}}","body":"Hi {{lead.name}}, I saw your business {{lead.business_name}}..."}}' ``` ### PATCH /lead-gen/initiatives/:id/steps/:stepId Update a step's delay, config (subject/body templates), or reorder its step_number. Updating step_number re-sequences other steps automatically. **Scopes:** `lead-gen:write` — _write_ Parameters: - `id` (path, uuid, required): Initiative UUID - `stepId` (path, uuid, required): Step UUID - `delay_days` (body, integer): Updated delay in days - `config` (body, object): Updated action config - `step_number` (body, integer): New step position (triggers re-sequence) ```bash curl -X PATCH \ "https://api.trustpager.com/functions/v1/api/v1/lead-gen/initiatives/a1b2c3d4-.../steps/s1b2c3d4-..." \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"delay_days":2}' ``` ### DELETE /lead-gen/initiatives/:id/steps/:stepId Remove a step from an initiative. Subsequent steps are re-numbered to fill the gap. Returns 204 No Content on success. **Scopes:** `lead-gen:delete` — _write_ Parameters: - `id` (path, uuid, required): Initiative UUID - `stepId` (path, uuid, required): Step UUID to remove ```bash curl -X DELETE \ "https://api.trustpager.com/functions/v1/api/v1/lead-gen/initiatives/a1b2c3d4-.../steps/s1b2c3d4-..." \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### POST /lead-gen/initiatives/:id/enrol Enrol one or more search results into an outreach initiative. Each enrolment tracks progress through the initiative's steps. Leads already enrolled in this initiative are skipped (idempotent). Unsubscribed leads are silently excluded. **Scopes:** `lead-gen:write` — _write_ Parameters: - `id` (path, uuid, required): Initiative UUID - `search_result_ids` (body, array, required): Array of search result UUIDs to enrol - `assigned_user_id` (body, uuid): User whose Gmail connection to use for email steps. Defaults to the API key owner. ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/lead-gen/initiatives/a1b2c3d4-.../enrol" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"search_result_ids":["r1...","r2...","r3..."]}' ``` ### GET /lead-gen/initiatives/:id/enrolments List enrolments for an initiative. Returns each enrolment with the lead's details, current step number, status, and next scheduled action date. **Scopes:** `lead-gen:read` Parameters: - `id` (path, uuid, required): Initiative UUID - `status` (query, string): Filter by enrolment status: active, completed, unsubscribed, failed - `limit` (query, integer): Max results (default: 50) - `after` (query, string): Pagination cursor ```bash curl \ "https://api.trustpager.com/functions/v1/api/v1/lead-gen/initiatives/a1b2c3d4-.../enrolments?status=active" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### POST /lead-gen/initiatives/dispatcher/run Manually trigger the initiative dispatcher for your workspace. The dispatcher processes all due enrolment tasks: sends emails, SMS messages, and staff notifications for active initiatives. Under normal operation this runs automatically via cron. Use this endpoint to trigger an immediate processing cycle, for testing, or to catch up after a pause. Returns a summary of tasks processed. **Scopes:** `lead-gen:write` — _write_ Parameters: - `initiative_id` (body, uuid): Limit dispatch to a single initiative. Omit to process all active initiatives. - `dry_run` (body, boolean): If true, simulate processing without sending anything (default: false) ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/lead-gen/initiatives/dispatcher/run" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` --- ## Learning Hub Manage Learning Hub canvases and their cards. Canvases are drag-and-drop boards of cards that can embed YouTube videos, external links, iframes, notepads, PDFs, images, and secure files. ### GET /training-canvases List all Learning Hub canvases. Returns id, name, template, and timestamps. Use GET /training-canvases/:id to retrieve a canvas with its cards. **Scopes:** `resources:read` Parameters: - `limit` (query, number): Items per page (default 25, max 100) - `after` (query, string): Cursor for next page ### GET /training-canvases/:id Get a single canvas including all cards, sorted by sort_order. Cards include resource_type, url, description, category, and display_config (grid layout). **Scopes:** `resources:read` Parameters: - `id` (path, uuid, required): Canvas UUID ### POST /training-canvases Create a new Learning Hub canvas. Pass template="getting_started" to seed 6 starter cards automatically. **Scopes:** `resources:write` — _write_ Parameters: - `name` (body, string, required): Canvas name (e.g., "New Hire Onboarding") - `template` (body, string): "getting_started" to seed starter cards ### PATCH /training-canvases/:id Rename a Learning Hub canvas. **Scopes:** `resources:write` — _write_ Parameters: - `id` (path, uuid, required): Canvas UUID - `name` (body, string): New canvas name ### DELETE /training-canvases/:id Delete a canvas and all its cards. This is permanent. **Scopes:** `resources:delete` — _write_ Parameters: - `id` (path, uuid, required): Canvas UUID ### POST /training-canvases/:id/cards Add a card to a canvas. Seven resource_type values are supported: - notepad: url = company_notepads.id - youtube: url = full YouTube URL - link: url = external URL - html_embed: url = URL to embed in an iframe - document: url = crm_documents.id (opens EnhancedPDFViewer) - image: url = company_files.id (must be category=images, opens FilePreviewModal) - file: url = company_secure_files.id (opens SecureFilePreviewModal) **Scopes:** `resources:write` — _write_ Parameters: - `id` (path, uuid, required): Canvas UUID - `resource_type` (body, string, required): One of: notepad, youtube, link, html_embed, document, image, file - `title` (body, string, required): Card title - `url` (body, string, required): Entity UUID (for notepad/document/image/file) or full URL (for youtube/link/html_embed) - `description` (body, string): Optional card description - `category` (body, string): training, sales, policy, reference, or general (default) - `position` (body, number): Sort order (0-based, default 0) - `display_config` (body, object): Grid layout: { layout: { lg: { x, y, w, h }, md, sm, xs } } ### PATCH /training-cards/:id Update a Learning Hub card. All fields are optional. **Scopes:** `resources:write` — _write_ Parameters: - `id` (path, uuid, required): Card UUID - `title` (body, string): New title - `resource_type` (body, string): New card type - `url` (body, string): New URL or entity UUID - `description` (body, string): New description - `category` (body, string): New category - `position` (body, number): New sort order - `display_config` (body, object): New grid layout config ### DELETE /training-cards/:id Remove a card from its canvas. Does not delete the underlying entity (the notepad, document, image, or file still exists). **Scopes:** `resources:delete` — _write_ Parameters: - `id` (path, uuid, required): Card UUID --- ## Scheduled Communications Queue deferred emails, SMS, and AI voice calls for future dispatch. The communications_scheduler_tick cron (runs every minute) claims pending rows, enforces business-hours windows, retries with exponential backoff, and dispatches via the automation pipeline. Row lifecycle: pending -> dispatching -> dispatched | failed | cancelled | skipped. ### GET /scheduled-communications List all scheduled communications for the company. Supports filtering by status, channel, and linked entity (contact, deal, customer). Paginated via cursor. **Scopes:** `dispatcher:read` Parameters: - `status` (query, string): Filter by lifecycle status: pending, dispatching, dispatched, failed, cancelled, skipped - `channel` (query, string): Filter by channel: email, sms, voice_call - `contact_id` (query, uuid): Filter by linked contact UUID - `deal_id` (query, uuid): Filter by linked deal/opportunity UUID - `customer_id` (query, uuid): Filter by linked customer/account UUID - `limit` (query, number): Max results (1-100, default 25) - `after` (query, string): Cursor for pagination ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/scheduled-communications?status=pending&channel=email&limit=10" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### POST /scheduled-communications Schedule a future email, SMS, or AI voice call. Requires dispatcher:write PLUS a channel-specific scope (email:send, sms:send, or calls:initiate). The row is created with status="pending". The scheduler tick picks it up at or after scheduled_for. If respect_business_hours=true, the scheduler holds the row outside of the configured business window and re-schedules to the next window start in the callee's timezone. **Scopes:** `dispatcher:write`, `email:send | sms:send | calls:initiate` — _write_ Parameters: - `channel` (body, string, required): Channel: email, sms, or voice_call - `scheduled_for` (body, string, required): ISO 8601 dispatch time. Must not be more than 1 minute in the past. - `payload` (body, object, required): Channel-specific payload. email: {to_email, subject, html_body, [from_email, sender_user_id, mode, contact_id, deal_id, customer_id, cc, to_name]}. sms: {phone_number_id, to_number, message_body}. voice_call: {voice_agent_outbound_config_id, to_number, [dynamic_variables, metadata, follow_ups]}. The follow_ups object enables multi-attempt voice cadence: {max_attempts (integer, total including first), follow_up_delays (array of hours between each retry -- decimal allowed, e.g. 0.5 for 30 min), voice_agent_outbound_config_id (uuid), to_number (E.164), [respect_business_hours, dynamic_variables, contact_id, customer_id, deal_id]}. - `timezone` (body, string): IANA timezone for business-hours evaluation (e.g. "Australia/Sydney"). Falls back to company default if omitted. - `respect_business_hours` (body, boolean): If true, hold delivery until callee is within business hours. Default false. - `contact_id` (body, uuid): Link to a contact UUID for timeline logging - `customer_id` (body, uuid): Link to a customer/account UUID - `deal_id` (body, uuid): Link to a deal/opportunity UUID for timeline logging - `related_entity_type` (body, string): Generic entity type for additional context linkage - `related_entity_id` (body, uuid): Generic entity ID for additional context linkage - `cancel_policy` (body, object): Optional cancellation policy configuration (jsonb) ```bash # Schedule an email for tomorrow morning (business hours enforced) curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/scheduled-communications" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "channel": "email", "scheduled_for": "2026-05-01T09:00:00Z", "timezone": "Australia/Sydney", "respect_business_hours": true, "payload": { "to_email": "client@example.com", "subject": "Following up on your quote", "html_body": "

Hi Sarah, just checking in...

", "contact_id": "abc123..." }, "contact_id": "abc123..." }' # Schedule an AI voice call for next week curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/scheduled-communications" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "channel": "voice_call", "scheduled_for": "2026-05-05T10:00:00Z", "timezone": "Australia/Brisbane", "respect_business_hours": true, "payload": { "voice_agent_outbound_config_id": "config-uuid...", "to_number": "+61412345678", "dynamic_variables": { "contact_name": "Sarah", "deal_name": "Website Project" } } }' # Schedule a voice call with multi-attempt cadence (3 attempts: now, +2h, +24h) curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/scheduled-communications" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "channel": "voice_call", "scheduled_for": "2026-05-05T10:00:00Z", "timezone": "Australia/Brisbane", "respect_business_hours": true, "contact_id": "contact-uuid...", "payload": { "voice_agent_outbound_config_id": "config-uuid...", "to_number": "+61412345678", "dynamic_variables": { "contact_name": "Sarah" }, "follow_ups": { "max_attempts": 3, "follow_up_delays": [2, 24], "voice_agent_outbound_config_id": "config-uuid...", "to_number": "+61412345678", "respect_business_hours": true, "contact_id": "contact-uuid..." } } }' ``` ### GET /scheduled-communications/:id Retrieve a single scheduled communication by ID with full status, payload, and dispatch result (dispatched_external_id, dispatched_at, attempt_count, last_error). **Scopes:** `dispatcher:read` Parameters: - `id` (path, uuid, required): Scheduled communication UUID ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/scheduled-communications/60b5889e-c8e1-4273-836e-67a3f0b3231a" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### PATCH /scheduled-communications/:id Update a pending scheduled communication. Only rows with status="pending" can be modified. Editable fields: scheduled_for, timezone, respect_business_hours, payload, cancel_policy. Returns a validation error if the row is not pending. **Scopes:** `dispatcher:write` — _write_ Parameters: - `id` (path, uuid, required): Scheduled communication UUID - `scheduled_for` (body, string): New ISO 8601 dispatch time - `timezone` (body, string): IANA timezone override - `respect_business_hours` (body, boolean): Toggle business-hours enforcement - `payload` (body, object): Replacement payload (must still satisfy channel required fields) - `cancel_policy` (body, object): Replacement cancel policy ```bash curl -X PATCH \ "https://api.trustpager.com/functions/v1/api/v1/scheduled-communications/60b5889e-c8e1-4273-836e-67a3f0b3231a" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"scheduled_for": "2026-05-02T09:00:00Z", "respect_business_hours": false}' ``` ### DELETE /scheduled-communications/:id Soft-cancel a pending scheduled communication. Sets status to "cancelled". The row is retained for audit. Returns 204 No Content on success. Returns a validation error if the row is not pending. **Scopes:** `dispatcher:write` — _write_ Parameters: - `id` (path, uuid, required): Scheduled communication UUID to cancel ```bash curl -X DELETE \ "https://api.trustpager.com/functions/v1/api/v1/scheduled-communications/60b5889e-c8e1-4273-836e-67a3f0b3231a" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### POST /scheduled-communications/:id/dispatch-now Flush a pending scheduled communication immediately. Sets scheduled_for to now and disables business-hours enforcement so the next scheduler tick (within 1 minute) picks it up. Only works on "pending" rows. **Scopes:** `dispatcher:write` — _write_ Parameters: - `id` (path, uuid, required): Scheduled communication UUID to dispatch immediately ```bash curl -X POST \ "https://api.trustpager.com/functions/v1/api/v1/scheduled-communications/60b5889e-c8e1-4273-836e-67a3f0b3231a/dispatch-now" \ -H "Authorization: Bearer YOUR_API_KEY" ``` --- ## Schemas Discoverable reference data for automation trigger types. Returns the shape of trigger_data each trigger publishes, the {{variable}} tokens available in action templates, and CRM-enriched variable namespaces. Free -- 0 credits, no scope required. ### GET /schemas/triggers List all 43 trigger types supported by the automation engine. Each entry includes trigger_type, label, description, sample_trigger_data, and available_variables[]. The response also includes enriched_variables -- the CRM-enriched namespaces (contact.*, deal.*, customer.*, etc.) available on every trigger after CRM matching. Use this before building automations so you know which variable tokens to reference in action templates. **Scopes:** _none_ ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/schemas/triggers" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### GET /schemas/triggers/:trigger_type Get the canonical schema for a single trigger type. Returns label, description, sample_trigger_data (a realistic example payload), available_variables[] (all {{token}} names extracted from the sample), and enriched_variables (CRM namespace reference). Returns 404 if the trigger_type is not recognised. **Scopes:** _none_ Parameters: - `trigger_type` (path, string, required): Trigger type key (e.g. facebook_lead_ad, form_submitted, deal_updated, booking_created). Use GET /schemas/triggers to discover all values. ```bash curl -X GET \ "https://api.trustpager.com/functions/v1/api/v1/schemas/triggers/facebook_lead_ad" \ -H "Authorization: Bearer YOUR_API_KEY" ``` --- ## Reputation Public B2B reputation profiles, verified reviews, and case studies. Build credibility with a public reputation page at trustpager.com/reputation/. Includes a companion anonymous image upload endpoint for reviewer logos and avatars. ### POST https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/public-image-upload Anonymous public image upload endpoint. No API key required. Accepts PNG, WebP, JPEG, or SVG up to 100 KB. Stores in Cloudflare R2 at trustpager.net/uploads//.. Rate limited to 3 uploads per IP or browser fingerprint per 24 hours. Use context=reputation-reviewer-logo for reviewer company logos and context=form-avatar for reviewer headshots. **Scopes:** _none_ — _write_ Parameters: - `file` (body, File, required): Image file (PNG/WebP/JPEG/SVG, max 100 KB). Send as multipart/form-data. - `context` (body, string, required): Upload namespace. Must be one of: reputation-reviewer-logo, form-avatar - `fingerprint` (body, string): Optional browser fingerprint string for per-device rate limiting in addition to IP limiting. ```bash curl -X POST https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/public-image-upload \ -H "Origin: https://app.trustpager.com" \ -F "file=@company-logo.png;type=image/png" \ -F "context=reputation-reviewer-logo" ``` ### GET /reputation/profile Get the company's public Reputation profile (singleton per company). Returns slug, display name, category, branding, overall rating, review/case-study counts, publish state, and settings. **Scopes:** `reputation:read` ```bash curl https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/reputation/profile \ -H "Authorization: Bearer tp_live_..." ``` ### POST /reputation/profile Create or update the company's Reputation profile (upsert). On creation, slug and display_name are required. On update, only the provided fields are changed. **Scopes:** `reputation:write` — _write_ Parameters: - `slug` (body, string): URL slug (e.g. "acme-agency"). Required on first creation. Lowercase letters, numbers, hyphens. - `display_name` (body, string): Business display name shown on the public page. Required on first creation. - `category_id` (body, string): Company profile category UUID. - `tagline` (body, string): Short positioning line. - `description` (body, string): Longer about-us paragraph. - `logo_url` (body, string): URL to logo image. - `cover_image_url` (body, string): URL to cover/hero image. - `website_url` (body, string): Business website URL. - `linkedin_url` (body, string): Company LinkedIn URL. - `settings` (body, object): Widget style, theme, and CTA config. - `is_published` (body, boolean): true = live at trustpager.com/reputation/. ```bash curl -X POST https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/reputation/profile \ -H "Authorization: Bearer tp_live_..." \ -H "Content-Type: application/json" \ -d '{ "slug": "acme-agency", "display_name": "Acme Agency", "tagline": "B2B marketing for SaaS founders", "is_published": true }' ``` ### GET /reputation/stats Get aggregate stats for the company's Reputation: profile state, total/published review count, rating distribution (1-5), and case study counts by status. **Scopes:** `reputation:read` ```bash curl https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/reputation/stats \ -H "Authorization: Bearer tp_live_..." ``` ### GET /reputation/reviews List reviews on the company's Reputation profile. Supports cursor pagination. Filter by status, featured, service_category, or rating. Use search for fuzzy match on reviewer name or testimonial. **Scopes:** `reputation:read` Parameters: - `status` (query, string): Filter: draft | approved | published | rejected | archived - `featured` (query, boolean): Filter to featured reviews only - `service_category` (query, string): Filter by service category - `rating` (query, number): Filter by exact rating (1-5) - `search` (query, string): Fuzzy match on reviewer_name or testimonial_text - `limit` (query, number): Page size (default 25, max 100) - `cursor` (query, string): Pagination cursor from previous response ```bash curl https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/reputation/reviews?status=published \ -H "Authorization: Bearer tp_live_..." ``` ### POST /reputation/reviews Create a new reputation review (starts in "draft" status). Required fields: profile_id, reviewer_name, rating (1-5), testimonial_text. Supply reviewer_company_logo and reviewer_avatar as URLs from the public-image-upload endpoint for richer display. **Scopes:** `reputation:write` — _write_ Parameters: - `profile_id` (body, string, required): Company profile UUID - `reviewer_name` (body, string, required): Reviewer full name - `rating` (body, number, required): Rating 1-5 - `testimonial_text` (body, string, required): The review text - `reviewer_email` (body, string): Reviewer email (admin-only field) - `reviewer_company` (body, string): Reviewer company name - `reviewer_company_logo` (body, string): URL of reviewer company logo. Use public-image-upload (context: reputation-reviewer-logo). - `reviewer_role` (body, string): e.g. CMO, Head of Ops - `reviewer_avatar` (body, string): URL of reviewer headshot. Use public-image-upload (context: form-avatar). - `reviewer_linkedin_url` (body, string): LinkedIn profile URL - `reviewer_linkedin_verified` (body, boolean): Whether LinkedIn identity was verified - `reviewer_company_size` (body, string): One of: 1-10 | 11-50 | 51-200 | 201-1000 | 1000+ - `reviewer_industry` (body, string): Reviewer industry (free text) - `service_category` (body, string): e.g. "Demand Gen", "Brand Strategy" - `engagement_type` (body, string): project | retainer | one-off | trial - `engagement_value_band` (body, string): <5k | 5-25k | 25-100k | 100k+ - `consent_given` (body, boolean): Reviewer consented to publication - `consent_method` (body, string): voice_call | form | email | manual | linkedin ```bash curl -X POST https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/reputation/reviews \ -H "Authorization: Bearer tp_live_..." \ -H "Content-Type: application/json" \ -d '{ "profile_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "reviewer_name": "Sarah Chen", "reviewer_company": "Momentum Labs", "reviewer_company_logo": "https://trustpager.net/uploads/reputation-reviewer-logo/abc123.png", "reviewer_role": "CMO", "reviewer_avatar": "https://trustpager.net/uploads/form-avatar/def456.png", "reviewer_linkedin_url": "https://linkedin.com/in/sarahchen", "reviewer_linkedin_verified": true, "reviewer_company_size": "51-200", "reviewer_industry": "Technology", "rating": 5, "testimonial_text": "Exceptional work. Highly recommended.", "consent_given": true, "consent_method": "email" }' ``` ### PATCH /reputation/reviews/:id Update a reputation review. Accepts all writable fields. Setting vendor_response auto-stamps vendor_response_by and vendor_response_at. Use reviewer_company_logo and reviewer_avatar to attach images uploaded via public-image-upload. **Scopes:** `reputation:write` — _write_ Parameters: - `id` (path, string, required): Review UUID - `reviewer_name` (body, string): Reviewer full name - `reviewer_company` (body, string): Reviewer company name - `reviewer_company_logo` (body, string): URL of reviewer company logo (from public-image-upload) - `reviewer_role` (body, string): Reviewer job title - `reviewer_avatar` (body, string): URL of reviewer headshot (from public-image-upload) - `reviewer_linkedin_url` (body, string): LinkedIn profile URL - `reviewer_linkedin_verified` (body, boolean): LinkedIn identity verified - `reviewer_company_size` (body, string): 1-10 | 11-50 | 51-200 | 201-1000 | 1000+ - `reviewer_industry` (body, string): Reviewer industry - `rating` (body, number): 1-5 - `testimonial_text` (body, string): Review text - `vendor_response` (body, string): Public vendor reply to this review. Auto-stamps vendor_response_at. - `featured` (body, boolean): Pin to featured slot on public profile - `service_category` (body, string): Service category label - `engagement_type` (body, string): project | retainer | one-off | trial - `engagement_value_band` (body, string): <5k | 5-25k | 25-100k | 100k+ ```bash curl -X PATCH https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/reputation/reviews/e14dad66-b422-42d4-a636-cd17af1c9970 \ -H "Authorization: Bearer tp_live_..." \ -H "Content-Type: application/json" \ -d '{ "reviewer_company_logo": "https://trustpager.net/uploads/reputation-reviewer-logo/abc123.png", "reviewer_linkedin_verified": true, "reviewer_company_size": "51-200", "reviewer_industry": "Technology", "vendor_response": "Thank you Sarah -- it was a pleasure working with your team.", "featured": true }' ``` ### POST /reputation/reviews/:id/approve Transition a review from draft to approved. Stamps approved_by and approved_at. **Scopes:** `reputation:write` — _write_ Parameters: - `id` (path, string, required): Review UUID ```bash curl -X POST https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/reputation/reviews/e14dad66-b422-42d4-a636-cd17af1c9970/approve \ -H "Authorization: Bearer tp_live_..." ``` ### POST /reputation/reviews/:id/publish Transition a review from approved to published. Stamps published_at and recalculates the profile overall_rating and total_reviews. **Scopes:** `reputation:write` — _write_ Parameters: - `id` (path, string, required): Review UUID ```bash curl -X POST https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/reputation/reviews/e14dad66-b422-42d4-a636-cd17af1c9970/publish \ -H "Authorization: Bearer tp_live_..." ``` ### DELETE /reputation/reviews/:id Permanently delete a review. If the review was published, the profile overall_rating and total_reviews are recalculated. **Scopes:** `reputation:delete` — _write_ Parameters: - `id` (path, string, required): Review UUID ```bash curl -X DELETE https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/reputation/reviews/e14dad66-b422-42d4-a636-cd17af1c9970 \ -H "Authorization: Bearer tp_live_..." ``` --- ## Referrals Track the referrer -> referred contact -> opportunity chain. Includes workspace-defined category picklist, a leaderboard endpoint, a public token-based submission form, and an automation action (attribute_referral) for pipeline-driven attribution. Every write to the referrals table fires the referral_attributed automation trigger (see GET /schemas/triggers/referral_attributed). Attribution is also cached on the opportunity: crm_deals.primary_referrer_contact_id and crm_deals.primary_referrer_category are maintained by a Postgres trigger and surface in GET /opportunities/:id (including the expand=referrer expansion). ### GET /referrals List referrals for the workspace. Filter by status, referrer_contact_id, referred_contact_id, or category. Returns the full referral chain including category and notes. **Scopes:** `referrals:read` Parameters: - `status` (query, string): pending | accepted | converted | declined - `referrer_contact_id` (query, string): UUID. Filter to referrals made BY this contact. - `referred_contact_id` (query, string): UUID. Filter to the referral that produced this contact. - `category` (query, string): Filter by workspace-defined category (e.g. "CT", "MRI", "Mortgage"). - `limit` (query, number): Max records to return (default 20, max 100). - `after` (query, string): Cursor for pagination (from previous response pagination.next_cursor). ```bash curl "https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/referrals?category=CT&status=pending" \ -H "Authorization: Bearer tp_live_..." ``` ### GET /referrals/:id Get a single referral by UUID. **Scopes:** `referrals:read` Parameters: - `id` (path, string, required): Referral UUID. ```bash curl "https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/referrals/f2788884-aaaa-bbbb-cccc-111111111111" \ -H "Authorization: Bearer tp_live_..." ``` ### POST /referrals Manually log a referral (verbal/ad-hoc). source defaults to "manual". For form-driven flows use POST /referrals/request. For automation-driven attribution configure the attribute_referral automation action. **Scopes:** `referrals:write` — _write_ Parameters: - `referrer_contact_id` (body, string, required): UUID of the contact who made the referral. - `referred_contact_id` (body, string): UUID of the referred person if already in the CRM. - `referred_deal_id` (body, string): UUID of the opportunity created from this referral. - `source` (body, string): form | manual | api. Defaults to "manual". - `status` (body, string): pending | accepted | converted | declined. Defaults to "pending". - `category` (body, string): Workspace-defined category (e.g. "CT", "MRI", "Mortgage"). Configure the picklist via PATCH /v1/company/crm-settings with referral_categories. - `notes` (body, string): Free-text notes about this referral. ```bash curl -X POST "https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/referrals" \ -H "Authorization: Bearer tp_live_..." \ -H "Content-Type: application/json" \ -d '{ "referrer_contact_id": "1068084c-f975-4bb8-be1b-4f0a2f0843be", "category": "MRI", "notes": "Met at the networking event last Tuesday" }' ``` ### PATCH /referrals/:id Update a referral. Writable fields: referred_contact_id, referred_deal_id, status, category, notes. Setting status="converted" stamps converted_at but does NOT send the thank-you email -- use POST /referrals/:id/convert for the full flow. **Scopes:** `referrals:write` — _write_ Parameters: - `id` (path, string, required): Referral UUID. - `referred_contact_id` (body, string): Link to the referred contact UUID. - `referred_deal_id` (body, string): Link to the opportunity UUID. - `status` (body, string): pending | accepted | converted | declined - `category` (body, string): Update the workspace category. - `notes` (body, string): Update free-text notes. ```bash curl -X PATCH "https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/referrals/8aa0c05e-d0b2-49b3-b6e8-2da112254c63" \ -H "Authorization: Bearer tp_live_..." \ -H "Content-Type: application/json" \ -d '{"status": "accepted", "category": "CT"}' ``` ### DELETE /referrals/:id Delete a referral row. The linked contact and opportunity are NOT deleted -- only the referral relationship row is removed. **Scopes:** `referrals:delete` — _write_ Parameters: - `id` (path, string, required): Referral UUID. ```bash curl -X DELETE "https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/referrals/8aa0c05e-d0b2-49b3-b6e8-2da112254c63" \ -H "Authorization: Bearer tp_live_..." ``` ### POST /referrals/:id/convert Mark a referral as converted (referred deal closed Won) AND send the branded thank-you email to the referrer. Stamps converted_at. Prefer this over PATCH /referrals/:id when transitioning to converted -- it triggers the email side-effect. Response includes thank_you_sent boolean. **Scopes:** `referrals:write` — _write_ Parameters: - `id` (path, string, required): Referral UUID. ```bash curl -X POST "https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/referrals/f2788884-aaaa-bbbb-cccc-111111111111/convert" \ -H "Authorization: Bearer tp_live_..." ``` ### POST /referrals/request Send a tracked referral-request email to a known contact. The recipient lands on a public page and submits a friend's details -- the platform auto-creates a contact + opportunity + referral row on submit. Returns request_id, token, and public_url. **Scopes:** `referrals:write` — _write_ Parameters: - `contact_id` (body, string, required): UUID of the referrer contact (the person being asked to refer someone). - `deal_id` (body, string): Optional opportunity context for variable resolution in the email template. - `subject_override` (body, string): Override the email subject line. Supports {{contact.first_name}} tokens. - `intro_html` (body, string): Override the intro paragraph (raw HTML). Supports template tokens. - `how_it_works_html` (body, string): Override the "how it works" card HTML. - `expires_in_days` (body, number): Days until the public link expires. Default 30. ```bash curl -X POST "https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/referrals/request" \ -H "Authorization: Bearer tp_live_..." \ -H "Content-Type: application/json" \ -d '{"contact_id": "1068084c-f975-4bb8-be1b-4f0a2f0843be", "expires_in_days": 30}' ``` ### GET /referrals/requests List referral request lifecycle rows (sent -> viewed -> submitted -> expired). Filter by status or contact_id. Useful for "who has been asked but not yet submitted". **Scopes:** `referrals:read` Parameters: - `status` (query, string): sent | viewed | submitted | expired - `contact_id` (query, string): UUID. Filter to requests sent TO this contact. - `limit` (query, number): Max records (default 20, max 100). - `after` (query, string): Pagination cursor. ```bash curl "https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/referrals/requests?status=sent" \ -H "Authorization: Bearer tp_live_..." ``` ### GET /referrals/leaderboard Top referrers ranked by converted_referrals desc, then total_referrals desc. Returns up to limit entries with contact details, total_referrals, converted_referrals, and conversion_rate. **Scopes:** `referrals:read` Parameters: - `limit` (query, number): Max entries (default 10, max 50). - `only_converted` (query, boolean): When true, only count referrals with status="converted". ```bash curl "https://ucqwijexmjctglmrxlej.supabase.co/functions/v1/api/v1/referrals/leaderboard?limit=10" \ -H "Authorization: Bearer tp_live_..." ``` --- ## CRM Export Bulk-export CRM data as XLSX or CSV. Supports all four entity types (contacts, customers, deals, work-orders) with entity-specific filters. Custom fields auto-expand into dedicated columns. Maximum 50,000 rows per request. ### GET /crm/export Export CRM data as a downloadable file. Returns the file as an attachment (XLSX or CSV). Custom fields from company_settings are appended as dynamic columns. Work-order dynamic fields come from crm_work_order_fields. Response headers include X-Row-Count (actual rows returned) and X-Truncated (1 if the 50,000-row cap was hit). **Scopes:** `contacts:read`, `companies:read`, `opportunities:read`, `work-orders:read` Parameters: - `type` (query, string, required): Entity type to export. One of: contacts, customers, deals, work-orders. The caller must hold the matching read scope for the chosen type. - `format` (query, string): File format. One of: xlsx (default), csv. - `search` (query, string): Text search. Contacts: first_name, last_name, email, phone, landline. Customers: name, email, phone, landline. Deals: name only. Not available for work-orders. - `created_after` (query, string): ISO 8601 datetime. Return records created on or after this timestamp. - `created_before` (query, string): ISO 8601 datetime. Return records created on or before this timestamp. - `source` (query, string): (contacts only) Filter by lead source string. - `customer_id` (query, uuid): (contacts, deals) Filter contacts linked to this customer, or deals belonging to this customer. - `email_unsubscribed` (query, string): (contacts only) true = opted-out contacts only, false = opted-in contacts only. - `sms_unsubscribed` (query, string): (contacts only) true = opted-out contacts only, false = opted-in contacts only. - `is_customer` (query, string): (customers only) true = is a customer, false = is not. - `is_supplier` (query, string): (customers only) true = is a supplier, false = is not. - `industry` (query, string): (customers only) Exact industry match. - `status` (query, string): (deals only) Filter by deal status. Valid values: open, won, lost. - `pipeline_id` (query, uuid): (deals only) Filter deals that are placed in this pipeline. - `stage_id` (query, uuid): (deals only) Filter deals that are placed in this specific stage. - `contact_id` (query, uuid): (deals only) Filter deals linked to this contact. - `assigned_to` (query, uuid): (deals, work-orders) Filter by assigned user UUID. - `status_id` (query, uuid): (work-orders only) Filter by work order status UUID. - `deal_product_id` (query, uuid): (work-orders only) Filter by deal product UUID. - `schedule_date` (query, string): (work-orders only) Exact date match (YYYY-MM-DD). --- ## Export Templates Reusable export configurations that combine a column layout, filters, sort order, and output format into a named template. Run a template to get XLSX or CSV data; preview returns the first 10 rows. Named views let each user persist their own filter/sort variant on a shared template. Scopes: exports:read, exports:write, exports:delete. ### GET /exports/field-catalog Discover which fields are available as columns and filters for a given root entity. Returns filter_fields (with allowed operator sets per field type) and column_fields (source + relation options). Call this before building a create_export_template payload so column IDs and filter ops are correct. **Scopes:** `exports:read` Parameters: - `root_entity` (query, string, required): Entity to explore. One of: opportunity, contact, account, work_order. ### GET /exports/templates List all export templates for this workspace. **Scopes:** `exports:read` Parameters: - `root_entity` (query, string): Filter by root entity. One of: opportunity, contact, account, work_order. - `search` (query, string): Partial name match. - `limit` (query, number): Items per page (default 25, max 100). - `after` (query, string): Cursor: last ID from previous page. ### GET /exports/templates/:id Get a single export template with full column, filter, and output config. **Scopes:** `exports:read` Parameters: - `id` (path, uuid, required): Template ID. ### POST /exports/templates Create an export template. Define columns (root fields or relation fields), filters, optional sort, and output format. **Scopes:** `exports:write` — _write_ Parameters: - `name` (body, string, required): Template name. - `root_entity` (body, string, required): One of: opportunity, contact, account, work_order. - `columns` (body, array, required): Array of ExportColumn objects. Each has: id (uuid), source ("root" or "relation"), field (for root source, dotted path e.g. "name", "created_at", "metadata.cf_custom"), relation (for relation source, e.g. "primary_contact", "pipeline", "stage"), relation_mode ("primary", "first_n", "comma_joined", "explode"), relation_field (field on the related entity, e.g. "email", "name"), header (column label), format (optional: "date", "datetime", "currency", "boolean", "array"). - `filters` (body, array): Array of ExportFilter objects. Each has: id (uuid), field (dotted path), op, value. Canonical ops: eq, neq, in, not_in, gte, lte, between, contains (text substring), is_null, not_null. Tags use "in" (OR -- "has any of these tags") and "not_in" (NOR -- "has none of these tags"). Reference fields (pipeline_id, stage_id, assigned_to, status): in, not_in. Legacy: "not_contains" on tags is a back-compat alias for "not_in" -- new code should use "not_in". - `sort` (body, array): Array of ExportSortEntry objects: { column_id, direction: "asc"|"desc" }. column_id must reference an ExportColumn.id from the columns array. Only root-source scalar columns can be sorted; relation columns are silently skipped. - `output` (body, object): ExportOutput config: { format: "xlsx"|"csv", bom: boolean (CSV UTF-8 BOM, default true), filename_template: string (tokens: {name}, {YYYY}, {MM}, {DD}, {YYYY-MM-DD}) }. ### PATCH /exports/templates/:id Update an export template. All body fields are optional; send only the fields you want to change. **Scopes:** `exports:write` — _write_ Parameters: - `id` (path, uuid, required): Template ID. - `name` (body, string): New name. - `columns` (body, array): Replacement column array (full replace, not merge). - `filters` (body, array): Replacement filter array. Canonical ops: eq, neq, in, not_in, gte, lte, between, contains (text substring), is_null, not_null. Tags use in (OR) / not_in (NOR). Legacy not_contains is a back-compat alias for not_in. - `sort` (body, array): Replacement sort array. - `output` (body, object): Replacement output config. ### DELETE /exports/templates/:id Delete an export template and all its saved views. **Scopes:** `exports:delete` — _write_ Parameters: - `id` (path, uuid, required): Template ID. ### POST /exports/templates/:id/preview Preview the first 10 rows of an export template (or a saved view). Returns JSON with headers and rows -- useful for verifying filter logic before running a full export. Filters, sort, and column layout from the template (or overrides in the request body) are applied. All filter operators are supported: tags use in (OR/"has any of") / not_in (NOR/"has none of"). Legacy not_contains is a back-compat alias for not_in. **Scopes:** `exports:read` Parameters: - `id` (path, uuid, required): Template ID. - `view_id` (body, uuid): Apply a saved view's filter/sort overrides on top of the template. ### POST /exports/templates/:id/run Run an export template and download the resulting XLSX or CSV file. Up to 250,000 rows are exported (X-Truncated: 1 header if the cap is hit). Optionally apply a saved view's filter/sort overrides. Supports all filter operators: tags use in (OR/"has any of") / not_in (NOR/"has none of"); not_contains is a back-compat alias for not_in. **Scopes:** `exports:read` Parameters: - `id` (path, uuid, required): Template ID. - `view_id` (body, uuid): Apply a saved view's filter/sort overrides before running. ### GET /exports/templates/:id/views List all saved views for an export template. **Scopes:** `exports:read` Parameters: - `id` (path, uuid, required): Template ID. ### POST /exports/templates/:id/views Create a saved view on an export template. A view stores a per-user filter/sort overlay without modifying the base template. **Scopes:** `exports:write` — _write_ Parameters: - `id` (path, uuid, required): Template ID. - `name` (body, string, required): View name. - `filters` (body, array): Filter overrides. Same ExportFilter shape as the template (see POST /exports/templates). - `sort` (body, array): Sort overrides. Same ExportSortEntry shape as the template. ### PATCH /exports/templates/:template_id/views/:id Update a saved view name, filters, or sort. **Scopes:** `exports:write` — _write_ Parameters: - `template_id` (path, uuid, required): Template ID. - `id` (path, uuid, required): View ID. - `name` (body, string): New name. - `filters` (body, array): Replacement filter array. - `sort` (body, array): Replacement sort array. ### DELETE /exports/templates/:template_id/views/:id Delete a saved view. **Scopes:** `exports:delete` — _write_ Parameters: - `template_id` (path, uuid, required): Template ID. - `id` (path, uuid, required): View ID. --- ## Evie (In-App Agent) Evie is the conversational AI assistant embedded in the TrustPager CRM sidebar. These endpoints are separate from the standard /api/v1 gateway and use OAuth tokens minted specifically for the in-app agent. ### POST /evie-grant Mint an Evie OAuth token for the authenticated user. The caller must supply a Supabase JWT (portal session token) as the Bearer credential. The endpoint verifies workspace membership and returns a tp_oauth_* token scoped to the intersection of the client max-scopes and the user role scopes. Idempotent: re-calling revokes any prior Evie token for the same (user, company) pair and issues a fresh one. Store the returned access_token in sessionStorage; include it as Bearer on all /agent-chat calls. **Scopes:** _none_ — _write_ Parameters: - `company_id` (body, uuid, required): Target workspace company ID. Must be a workspace the authenticated user belongs to. ```bash POST /functions/v1/evie-grant Authorization: Bearer Content-Type: application/json { "company_id": "ebeff86e-7b09-4e49-96db-f711d69d2d57" } ``` ### POST /agent-chat Run a single Evie conversation turn and stream the result via Server-Sent Events (SSE). The caller supplies a tp_oauth_* token (from /evie-grant) as the Bearer credential. Evie uses the token scopes to determine which CRM tools she can call, dispatches tool calls in-process (no HTTP round-trip per tool), and streams text + tool-call events back in real time. The thread is created automatically on the first message; supply thread_id on subsequent messages to continue a conversation. Model: TrustPager AI Standard (fast) by default; prefix your message with /think for TrustPager AI Advanced. **Scopes:** _none_ — _write_ Parameters: - `message` (body, string, required): User message text. Prefix with /think to use the advanced reasoning model. - `thread_id` (body, uuid): Existing thread ID for multi-turn conversations. Omit to start a new thread. - `context` (body, object): Portal context for grounding Evie in the user current location. Include current_route (e.g. /crm/opportunities/123), entity_type (opportunity|contact|company), and entity_id when on a detail page. - `attachments` (body, array): Image attachments (jpeg, png, gif, webp). Each item: { media_type, data (base64 without data: prefix), secure_file_id? }. Max 5MB per image. ```bash POST /functions/v1/agent-chat Authorization: Bearer tp_oauth_a1b2c3d4e5f6... Content-Type: application/json { "message": "How many open opportunities do we have in the Sales pipeline?", "thread_id": "a1b2c3d4-...", "context": { "current_route": "/crm/pipelines", "entity_type": null, "entity_id": null } } ``` ### POST /agent-chat-compact Compact a long Evie conversation thread into a new thread seeded with a summary of the original. Call this when a thread grows too long for efficient context handling. The original thread is marked as compacted (superseded_by_thread_id is set); the returned new_thread_id is ready for continued conversation. A mechanical compaction pass runs first (free); if the thread is still too long after that, a lightweight AI summarisation pass runs (small credit cost). The new thread starts with one assistant message summarising the conversation so far. **Scopes:** _none_ — _write_ Parameters: - `thread_id` (body, uuid, required): The thread to compact. Must belong to the authenticated (user, company) pair. ```bash POST /functions/v1/agent-chat-compact Authorization: Bearer tp_oauth_a1b2c3d4e5f6... Content-Type: application/json { "thread_id": "a1b2c3d4-..." } ``` ### GET /api/v1/_meta/tools Return the list of CRM tools available to the authenticated API key holder. Each tool entry includes: name (snake_case), description, input_schema (JSON Schema), preferred_model (haiku|sonnet), api_path, required_scopes, is_write, and portal_path. Tools are filtered by the caller scopes - a tp_live_* key with only contacts:read will see only read-scoped contact tools. Used internally by Evie to populate her tool catalog at chat-open; also useful for building your own agent on top of the TrustPager API. **Scopes:** _none_ --- ## Agent Ops Observability and management for AI agent infrastructure. Covers: registry, run log, signals, metrics, tool log, report runs, alert rules, fired alerts, and the aggregated dashboard. All routes require agent-ops:read (reads) or agent-ops:write (writes/actions). ### GET /agent-ops/dashboard Aggregated agent operations summary: all registered agents, today's runs, pending signals, and report status. Free (0 credits). **Scopes:** `agent-ops:read` ### GET /agent-ops/registry List all registered AI agents. Filter by agent_type or status. **Scopes:** `agent-ops:read` Parameters: - `agent_type` (query, string): Filter by type: cron_report, real_time_responder, scheduled_task, error_handler - `status` (query, string): Filter by status: active, paused, error, disabled - `limit` (query, number): Max results (default 25) ### POST /agent-ops/registry Register a new AI agent. **Scopes:** `agent-ops:write` — _write_ Parameters: - `name` (body, string, required): Machine name (e.g. sales-report) - `display_name` (body, string, required): Display name shown in Agent Hub - `description` (body, string): What this agent does - `agent_type` (body, string): cron_report | real_time_responder | scheduled_task | error_handler - `status` (body, string): active | paused | error | disabled (default: active) - `schedule` (body, string): Cron expression (for scheduled agents) - `timezone` (body, string): IANA timezone (e.g. Australia/Sydney) - `capabilities` (body, array): e.g. ["email","sms","screenshot"] - `configuration` (body, object): Agent-specific settings (JSONB) ```bash { "name": "weekly-report", "display_name": "Weekly Report Agent", "agent_type": "cron_report", "schedule": "0 9 * * 1", "timezone": "Australia/Sydney", "capabilities": ["email", "notepad"] } ``` ### GET /agent-ops/registry/:id Get a single registered agent by UUID. **Scopes:** `agent-ops:read` Parameters: - `id` (path, string, required): Agent registry UUID ### PATCH /agent-ops/registry/:id Update an agent registry entry. **Scopes:** `agent-ops:write` — _write_ Parameters: - `id` (path, string, required): Agent registry UUID - `display_name` (body, string): Updated display name - `status` (body, string): active | paused | error | disabled - `schedule` (body, string): New cron expression - `configuration` (body, object): Agent-specific settings (JSONB) ### DELETE /agent-ops/registry/:id Delete an agent registry entry. Requires agent-ops:delete scope. **Scopes:** `agent-ops:delete` — _write_ Parameters: - `id` (path, string, required): Agent registry UUID ### POST /agent-ops/registry/:id/test-run Trigger a manual test run for a managed agent. Verifies company ownership, confirms the agent is active and managed_agents runtime, then proxies to the dispatcher. Returns a run_id and session_id. Requires agent-ops:write scope. **Scopes:** `agent-ops:write` — _write_ Parameters: - `id` (path, string, required): Agent registry UUID - `trigger_context` (body, object): Optional context injected into the kickoff (default: { source: "api-test-run" }) ```bash { "trigger_context": { "source": "api-test-run", "note": "Manual verification run" } } ``` ### POST /agent-ops/registry/:id/definition Update the managed agent definition (bumps the version on the underlying AI platform). Verifies company ownership of both the registry row and the linked definition before proxying. Body must contain an "agent" key with the update payload. Requires agent-ops:write scope. **Scopes:** `agent-ops:write` — _write_ Parameters: - `id` (path, string, required): Agent registry UUID - `agent` (body, object, required): Definition update payload (e.g. { name, system_prompt, model }) ```bash { "agent": { "name": "My Updated Agent", "system_prompt": "You are a helpful CRM assistant..." } } ``` ### GET /agent-ops/runs List agent run log entries. Filter by agent_name, status, or since date. **Scopes:** `agent-ops:read` Parameters: - `agent_name` (query, string): Filter by agent name - `status` (query, string): started | completed | failed | skipped - `since` (query, string): ISO datetime -- only runs after this time - `limit` (query, number): Max results (default 25) ### POST /agent-ops/runs Log a new agent run. If eve_id is provided and already exists, upserts. **Scopes:** `agent-ops:write` — _write_ Parameters: - `agent_name` (body, string, required): Agent name - `task_type` (body, string): Task type (e.g. sales-report, email_response) - `status` (body, string): started | completed | failed | skipped - `model` (body, string): Model used (e.g. claude-sonnet-4-6) - `duration_ms` (body, number): Run duration in milliseconds - `output_summary` (body, string): Human-readable summary of what the agent did ### PATCH /agent-ops/runs/:id Update an agent run by UUID or eve_id. Automatically updates agent registry stats on completion or failure. **Scopes:** `agent-ops:write` — _write_ Parameters: - `id` (path, string, required): Run UUID or eve_id (numeric string) - `status` (body, string): completed | failed | skipped - `duration_ms` (body, number): Run duration in milliseconds - `output_summary` (body, string): Human-readable summary of what the agent did - `error_message` (body, string): Error message if run failed ### GET /agent-ops/signals List inter-agent signals. Filter by for_agent, created_by, signal type, or pending. **Scopes:** `agent-ops:read` Parameters: - `for_agent` (query, string): Filter by recipient agent - `created_by` (query, string): Filter by sender agent - `signal` (query, string): Filter by signal type - `pending` (query, boolean): If true, only return unactioned signals ### POST /agent-ops/signals Send an inter-agent signal. **Scopes:** `agent-ops:write` — _write_ Parameters: - `for_agent` (body, string, required): Recipient agent name - `created_by` (body, string, required): Sender agent name - `signal` (body, string, required): Signal type (e.g. screenshot_request, error_fix_request) - `detail` (body, string): Signal payload - `expires_at` (body, string): ISO datetime for expiry ### GET /agent-ops/alert-rules List configured alert rules for agents. **Scopes:** `agent-ops:read` Parameters: - `limit` (query, number): Max results (default 25) ### POST /agent-ops/alert-rules Create an alert rule. Types: consecutive_failures, duration_exceeded, no_run_since, error_rate. **Scopes:** `agent-ops:write` — _write_ Parameters: - `rule_type` (body, string, required): consecutive_failures | duration_exceeded | no_run_since | error_rate - `threshold` (body, object, required): e.g. {"max_failures": 2} or {"max_duration_ms": 300000} - `agent_name` (body, string): Target agent name (null for global rule) - `notify_channel` (body, string): slack | email | both - `enabled` (body, boolean): Default: true ### GET /agent-ops/alerts List fired agent alerts. Filter by agent_name or unacknowledged. **Scopes:** `agent-ops:read` Parameters: - `agent_name` (query, string): Filter by agent name - `unacknowledged` (query, boolean): If true, only unacknowledged alerts ### POST /agent-ops/alerts/:id/acknowledge Acknowledge a fired alert. **Scopes:** `agent-ops:write` — _write_ Parameters: - `id` (path, string, required): Alert UUID - `acknowledged_by` (body, string): Who acknowledged it (defaults to "api")