{"openapi":"3.0.3","info":{"title":"SuperSend API","version":"2.0","description":"The SuperSend API enables programmatic access to manage contacts, campaigns, teams, senders, and events.\n\n## Base URL\n\nAll API requests should be made to:\n```\nhttps://api.supersend.io/v2\n```\n\n## Authentication\n\nAuthenticate requests using your API key in the `Authorization` header:\n```\nAuthorization: Bearer YOUR_API_KEY\n```\n\n## Rate Limits\n\n- Standard: 100 requests/minute\n- Bulk operations: 10 requests/minute\n- List operations: 60 requests/minute\n\n## Errors\n\nAll errors follow a consistent format with `type`, `code`, `message`, and `request_id`.\n\n## LinkedIn availability\n\nLinkedIn endpoints and fields (`linkedin_url`, `channel=linkedin`, `linkedin_identity`, etc.)\nare only available to organizations created before SuperSend's LinkedIn sunset date, unless\nan admin enables the `linkedin_enabled` feature flag for the org. New orgs after the cutoff\nreceive `403` on LinkedIn-specific writes and LinkedIn fields are omitted from responses.\n","contact":{"name":"SuperSend Support","email":"support@supersend.io","url":"https://supersend.io"},"license":{"name":"Proprietary","url":"https://supersend.io/terms"}},"servers":[{"url":"https://api.supersend.io/v2","description":"Production"},{"url":"https://staging-api.supersend.io/v2","description":"Staging"}],"tags":[{"name":"Health","description":"API health check endpoints"},{"name":"Setup","description":"What an account still needs before it can send (billing, inboxes, warmup, sender profiles), for AI agents and other API clients"},{"name":"Contacts","description":"Manage contacts in campaigns"},{"name":"Campaigns","description":"Manage outreach campaigns"},{"name":"Playbooks","description":"Manage outbound playbooks - strategy, offer, segment, messaging, and hypothesis for campaigns. Requires a valid API key and team-scoped authorization as documented per operation."},{"name":"Campaign Categories","description":"Manage campaign categories (folders) for organizing campaigns"},{"name":"Teams","description":"Manage teams and team settings"},{"name":"Senders","description":"Manage email senders"},{"name":"Placement Tests","description":"Manage placement tests for deliverability analysis"},{"name":"Events","description":"Activity log and event tracking"},{"name":"Webhooks","description":"Manage webhooks and delivery logs"},{"name":"Conversations","description":"Manage conversations and send messages across email and LinkedIn"},{"name":"Devices","description":"Register phones running the SuperSend iPhone app for new-reply push notifications"},{"name":"Account","description":"Requests about your own SuperSend account"},{"name":"Mailboxes","description":"Purchase and manage mailboxes for domains"},{"name":"Domains","description":"Manage managed domains and purchase new domains"},{"name":"Intelligence","description":"Composite analysis endpoints for AI agents and in-app chat"},{"name":"Email validation","description":"Standalone SMTP email verification (consumes global email verification credits)"},{"name":"Billing","description":"Billing and cost allocation for multi-team organizations"}],"security":[{"BearerAuth":[]}],"paths":{"/health":{"get":{"tags":["Health"],"summary":"Health Check","description":"Returns API health status. Does not require authentication.","operationId":"getHealth","security":[],"responses":{"200":{"description":"API is healthy","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"},"example":{"success":true,"data":{"status":"healthy","version":"v2","timestamp":"2026-01-19T12:00:00.000Z","uptime":86400},"request_id":"req_abc123def456"}}}}}}},"/announcements":{"get":{"tags":["Health"],"summary":"List announcements","description":"Returns active in-app announcements (manual and Crisp status-driven).\nUsed by the app shell to display banners. Authentication is optional;\nwhen a Bearer token is present, announcements with `audience: supersend_esp`\nare included only if the user's org has an active SuperSend ESP sender.\nWithout auth, ESP-scoped announcements are omitted.\n","operationId":"getAnnouncements","security":[],"responses":{"200":{"description":"List of active announcements","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnnouncementsResponse"},"example":{"success":true,"data":{"announcements":[{"id":"550e8400-e29b-41d4-a716-446655440000","source":"manual","title":"Scheduled maintenance","message":"Maintenance tonight 10p–12a PT.","link":"https://status.supersend.io","severity":"info","audience":"all","dismissible":true}]},"request_id":"req_abc123def456"}}}}}}},"/setup":{"get":{"tags":["Setup"],"summary":"Get Setup Status","description":"One call to see what an account still needs before it can send: billing (trial or plan),\ninboxes and warmup, sender profiles, LinkedIn accounts, and campaigns.\n\n`next_steps` lists what is missing, in order. Each step says who does it: `agent` (with the\nMCP tool and API call to use), `human` (with a dashboard link for browser-only steps such as\nadding a card or Google/Microsoft sign-in), or `human_or_agent`.\n\nWarmup counts as done 14 days after it starts. `ready_to_send` is true when billing allows\nsending and at least one sender profile has an active inbox.\n","operationId":"getSetupStatus","parameters":[{"name":"team_id","in":"query","required":false,"description":"Team to check. Defaults to the caller's default team, then their oldest team.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Setup status and next steps","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetupStatusResponse"},"example":{"success":true,"data":{"object":"setup_status","ready_to_send":false,"team":{"id":"550e8400-e29b-41d4-a716-446655440000","name":"Acme Outbound"},"billing":{"status":"trialing","can_send":true,"plan":"growth","trial_available":false,"trial_ends_at":"2026-10-09T17:00:00.000Z"},"inboxes":{"total":3,"active":3,"disabled":0,"warming":3,"warmed":0,"not_warming":0,"in_sender_profile":0,"days_until_first_warmed":9},"sender_profiles":{"total":0,"with_active_inboxes":0},"linkedin":{"enabled":false,"accounts":0},"campaigns":{"total":0,"active":0,"drafts":0},"next_steps":[{"id":"group_inboxes","who":"agent","mcp_tool":"create_sender_profile","api":"POST /v2/sender-profiles, then PATCH /v2/senders/{id} {\"SenderProfileId\": \"<profile id>\"}","why":"Campaigns send from sender profiles. Put the inboxes in a sender profile, then assign it to campaigns."},{"id":"create_campaign","who":"agent","mcp_tool":"setup_outbound_campaign","api":"POST /v2/campaigns","why":"No campaigns yet. Build the sequence, assign a sender profile, and import contacts."}],"links":{"add_card":"https://app.supersend.io/auth?type=login&redirect=%2Fselect-plan","billing":"https://app.supersend.io/auth?type=login&redirect=%2Forg%2Fbilling","connect_google_or_outlook":"https://app.supersend.io/auth?type=login&redirect=%2Fadd-sender%3FteamId%3D550e8400-e29b-41d4-a716-446655440000%26initialTab%3Dproviders","connect_smtp":"https://app.supersend.io/auth?type=login&redirect=%2Fadd-sender%3FteamId%3D550e8400-e29b-41d4-a716-446655440000%26initialTab%3Dmanual","buy_inboxes":"https://app.supersend.io/auth?type=login&redirect=%2Fteam%2F550e8400-e29b-41d4-a716-446655440000%2Fsenders%3Fconnect%3Dbuy"}},"request_id":"req_abc123def456"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/setup/inbox-checkout":{"post":{"tags":["Setup"],"summary":"Create Inbox Checkout Link","description":"Plans sending domains and inboxes without spending money, then returns a prefilled checkout\nlink for a human to review and pay. **Nothing is charged by this call.**\n\nDomains can be new (checked that they can be bought) or ones the organization already has:\nbought through SuperSend, or connected with `POST /domains/connect`. Owned domains come back\nwith `owned: true` and `price_per_year: 0`; only their inboxes are charged. Inbox limits count\nthe inboxes already on a domain (5 per domain, one provider per domain).\n\nAfter payment, DNS is set up automatically and new inboxes appear within 24-48 hours and start\nwarming. The exception is a connected domain registered outside SuperSend\n(`nameserver_change_required: true`): a few minutes after payment,\n`POST /domains/{id}/verify` returns the two nameservers to set at its registrar, and its\ninboxes are created once they're live. Track progress with `GET /setup`.\n","operationId":"createInboxCheckout","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboxCheckoutRequest"},"example":{"domains":[{"domain":"tryacme.com","provider":"smtp"}],"mailboxes":[{"username":"jane","domain":"tryacme.com","first_name":"Jane","last_name":"Doe"},{"username":"jane.doe","domain":"tryacme.com","first_name":"Jane","last_name":"Doe"}]}}}},"responses":{"200":{"description":"Checkout link and cost estimate (nothing charged)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboxCheckoutResponse"},"example":{"success":true,"data":{"object":"inbox_checkout","url":"https://app.supersend.io/auth?type=login&redirect=%2Fteam%2F550e8400-e29b-41d4-a716-446655440000%2Fsenders%3Fcheckout%3D...","team":{"id":"550e8400-e29b-41d4-a716-446655440000","name":"Acme Outbound"},"domains":[{"domain":"tryacme.com","provider":"smtp","price_per_year":13}],"mailboxes":[{"email":"jane@tryacme.com","provider":"smtp"},{"email":"jane.doe@tryacme.com","provider":"smtp"}],"estimated_cost":{"currency":"usd","domains_per_year":13,"mailboxes_per_month":10},"next":"Give the user this link. They review the cart, add domain contact details, and pay. DNS is set up automatically, and new inboxes appear within 24-48 hours and start warming. Check progress with get_setup_status (GET /v2/setup)."},"request_id":"req_abc123def456"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"One or more domains can't be bought. `error.details.unavailable` lists each domain and why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"type":"invalid_request_error","code":"domain_unavailable","message":"These domains can't be bought: acme.com (taken). Pick other names and try again.","param":"domains","details":{"unavailable":[{"domain":"acme.com","reason":"taken"}]},"doc_url":"https://docs.supersend.io/docs/errors#domain_unavailable"},"request_id":"req_abc123def456"}}}}}}},"/contacts":{"post":{"tags":["Contacts"],"summary":"Create Contact","description":"Creates a new contact or updates an existing one (upsert by email/LinkedIn), or\ncreates/updates **team contact profile only** when `CampaignId` is omitted or null (no campaign\nenrollment row).\n\n**Campaign enrollment:** When `CampaignId` is set, returns the created/updated **contact**\n(enrollment). Optional `contact_profile_id` seeds the enrollment from an existing profile;\nthe profile must already have a **ContactTeam** row for the campaign's team.\n\n**Profile-only (no campaign):** Omit `CampaignId` (or send null/empty). Requires `TeamId` and\nat least one of `email` or `linkedin_url`. Returns `201` with `data.object`:\n`contact_profile` and `enrolled: false` (no `Contact` row).\n\n**Flow:** With `CampaignId`, the API upserts **ContactProfile** + **ContactTeam** for the team,\nthen creates or updates the **Contact** (campaign enrollment). Without `CampaignId`, only the\nprofile and team membership are upserted.\n\n**Repeats and retries:** An existing enrollment is matched by `CampaignId` plus `email` (or\n`linkedin_url` when there is no email), case-insensitive. Sending the same contact to the same\ncampaign again — including concurrent or retried requests — updates that enrollment (`200`)\ninstead of enrolling the person twice, and keeps its A/B variant. If another request for the\nsame contact and campaign is still being processed after about 10 seconds, the API returns\n`409` `contact_enrollment_in_progress`; retry after a few seconds. `integration_contact_id` is\nstored but not used for matching.\n\n**Labels:** Optional `labels` (array of strings) attaches **existing** org/team labels to the\ncontact profile (`applies_to`: contact or both). Unknown names and conversation-only labels are\nignored.\n\n**Email validation:** Set `validate_emails: true` to queue deliverability verification when the\ncontact profile is **unvalidated or pending** (`valid2` -1 or 0). Already-validated profiles\nare not re-checked (no extra credits). Applies on create, update, and re-enroll. Default is\nfalse. Not applied on the profile-only path.\n","operationId":"createContact","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateContactRequest"},"example":{"email":"john@example.com","first_name":"John","last_name":"Doe","company":"Acme Corp","TeamId":"550e8400-e29b-41d4-a716-446655440000","CampaignId":"660e8400-e29b-41d4-a716-446655440001"}}}},"responses":{"200":{"description":"Contact updated (existing contact)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"}}}},"201":{"description":"Contact enrolled or team contact profile created (no enrollment)","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ContactResponse"},{"$ref":"#/components/schemas/TeamContactProfileCreatedResponse"}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"Not enough email verification credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"payment_required_error","code":"insufficient_credits","message":"Not enough email verification credits available","details":{"requiredCredits":1,"availableCredits":0}},"request_id":"req_a1b2c3d4e5f6789012345678"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"Another request for the same contact and campaign is still being processed; retry after a few seconds","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"conflict_error","code":"contact_enrollment_in_progress","message":"Another request is adding this contact to this campaign. Retry in a few seconds."},"request_id":"req_a1b2c3d4e5f6789012345678"}}}}}},"get":{"tags":["Contacts"],"summary":"List Contacts","description":"Lists contacts with filtering, search, and pagination.\nSupports filtering by team, campaign, and status.\n","operationId":"listContacts","parameters":[{"$ref":"#/components/parameters/TeamIdQuery"},{"$ref":"#/components/parameters/CampaignIdQuery"},{"$ref":"#/components/parameters/LimitParam"},{"$ref":"#/components/parameters/OffsetParam"},{"name":"search","in":"query","description":"Search by email, name, company","schema":{"type":"string"}},{"name":"interest","in":"query","description":"Filter by contact status (interest). Comma-separated for multiple values.\nValues: interested, not_interested, meeting_request, meeting_booked, customer, future, follow_up.\nUse empty string or null to filter for contacts with no status.\n","schema":{"type":"string"}},{"name":"has_email_sent","in":"query","description":"Filter by whether the contact has been sent at least one email in this campaign.\n`true` returns contacts with at least one email send event.\n`false` returns contacts that have never been emailed.\nWhen CampaignId is provided, only email sends for that campaign are considered.\n","schema":{"type":"boolean"}},{"name":"sort_by","in":"query","description":"Sort order for results. `last_activity` sorts by the most recent **Events** row for the contact\n(all channels and event types — same timeline as contact Event History) within the campaign scope when `CampaignId` is set.\n`last_updated` / `last_updated_asc` sort by the contact record `updatedAt`.\n`current_step` / `current_step_desc` sort by numeric step order (legacy: `next_step`;\nflow campaigns: `data.step_number` on the node matching `next_node`), not by step label.\nFinished contacts sort after in-progress contacts (they no longer share the active-step key).\n","schema":{"type":"string","enum":["newest","oldest","last_activity","last_activity_asc","last_updated","last_updated_asc","current_step","current_step_desc","first_name","last_name","company_name"],"default":"newest"}}],"responses":{"200":{"description":"List of contacts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactListResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"delete":{"tags":["Contacts"],"summary":"Delete Contact by Identifier","description":"Deletes contacts by email or LinkedIn URL within a team and campaign.\nThis is a soft delete operation.\n","operationId":"deleteContactByIdentifier","parameters":[{"name":"TeamId","in":"query","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"CampaignId","in":"query","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"email","in":"query","description":"Email address to delete (required if no linkedin_url)","schema":{"type":"string","format":"email"}},{"name":"linkedin_url","in":"query","description":"LinkedIn URL to delete (required if no email)","schema":{"type":"string","format":"uri"}}],"responses":{"200":{"description":"Contacts deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/contacts/{id}":{"get":{"tags":["Contacts"],"summary":"Get Contact","description":"Retrieves a single **campaign enrollment** contact by ID (the `Contact` row), including linked campaign.\n\nFor **team contact profiles** (All Contacts / `ContactProfile`), list and detail in the app use\n**`GET https://api.supersend.io/v1/contact/profile/{profileId}?TeamId=`** (V1 path, same API key auth).\nThe JSON `contact` / `Contact` object includes **`associated_campaigns`**: per-campaign enrollment status\n(see schema **`AssociatedCampaignEnrollment`**). **`profileId`** is the ContactProfile UUID, not an enrollment id.\n","operationId":"getContact","parameters":[{"$ref":"#/components/parameters/IdPath"}],"responses":{"200":{"description":"Contact details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Contacts"],"summary":"Update Contact","description":"Updates contact fields. Only provided fields are updated.","operationId":"updateContact","parameters":[{"$ref":"#/components/parameters/IdPath"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateContactRequest"}}}},"responses":{"200":{"description":"Contact updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"tags":["Contacts"],"summary":"Delete Contact","description":"Soft deletes a contact by ID.","operationId":"deleteContact","parameters":[{"$ref":"#/components/parameters/IdPath"}],"responses":{"200":{"description":"Contact deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/contacts/{id}/profile-labels":{"get":{"tags":["Contacts"],"summary":"List contact profile labels","description":"Lists labels on the contact's **ContactProfile** for **this contact's team only** (`ContactProfileLabels.TeamId` matches the contact).\nThe same person (profile) can have different label rows per team; other teams' assignments are not included here.\nUse `GET /labels?team_id=...&label_purpose=contact` to list labels allowed on contact profiles (`applies_to` is `contact` or `both`).\nThe contact must have at least one of `email` or `linkedin_url` so a profile can be resolved.\n","operationId":"listContactProfileLabels","parameters":[{"$ref":"#/components/parameters/IdPath"},{"$ref":"#/components/parameters/LimitParam"},{"$ref":"#/components/parameters/OffsetParam"}],"responses":{"200":{"description":"Profile labels (paginated)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ContactProfileLabelItem"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"tags":["Contacts"],"summary":"Assign label to contact profile","description":"Assigns an existing label to the contact's **ContactProfile** in the **contact's team** context (`TeamId` on the junction row).\nThe label must belong to the same org; if the label is team-scoped, its team must match the contact's team. Legacy labels may have null `TeamId` on the label row.\nReturns `201` when a new link is created, `200` when an inactive link is reactivated for this team.\n","operationId":"assignContactProfileLabel","parameters":[{"$ref":"#/components/parameters/IdPath"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["label_id"],"properties":{"label_id":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"Existing soft-deleted assignment reactivated","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ContactProfileLabelAssignment"}}}]}}}},"201":{"description":"Label assigned","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ContactProfileLabelAssignment"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Label already assigned to this contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/contacts/{id}/profile-labels/{labelId}":{"delete":{"tags":["Contacts"],"summary":"Remove contact profile label","description":"Soft-removes the label for **this contact's team** only (junction match on profile, label, and `TeamId`).\n","operationId":"removeContactProfileLabel","parameters":[{"$ref":"#/components/parameters/IdPath"},{"name":"labelId","in":"path","required":true,"description":"Label UUID to remove from the contact's profile","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Label removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/contacts/{id}/inbox-link-clicks":{"get":{"tags":["Contacts"],"summary":"List contact inbox link clicks","description":"Returns link click events from Super Inbox outbound replies for a contact.","operationId":"getContactInboxLinkClicks","parameters":[{"$ref":"#/components/parameters/IdPath"},{"name":"team_id","in":"query","description":"Filter to a team (defaults to the contact's team)","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/LimitParam"},{"$ref":"#/components/parameters/OffsetParam"}],"responses":{"200":{"description":"Paginated link click list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactInboxLinkClicksResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/contacts/bulk":{"post":{"tags":["Contacts"],"summary":"Bulk Import Contacts","description":"Import contacts in bulk via JSON array or CSV file upload.\nProcessing happens asynchronously - returns an upload ID for tracking.\n\n**JSON Body:**\n```json\n{\n  \"contacts\": [\n    { \"email\": \"john@example.com\", \"first_name\": \"John\" },\n    { \"email\": \"jane@example.com\", \"first_name\": \"Jane\" }\n  ],\n  \"TeamId\": \"uuid\",\n  \"CampaignId\": \"uuid\"\n}\n```\n\n**CSV Upload:**\nUse `multipart/form-data` with:\n- `contacts`: CSV file\n- `TeamId`: Team UUID\n- `CampaignId`: Campaign UUID\n- `mapping_fields`: JSON mapping of CSV columns to contact fields\n- `validate_emails`: Optional. Set to `true` to run email verification (consumes credits). Default `false`.\n\n**Email Validation:**\nSet `validate_emails: true` to queue verification for contacts in the import (consumes credits).\nDefault is false to avoid surprise billing. Applies to both JSON and CSV uploads. For single-contact\nupsert (`POST /contacts`), already-validated profiles are not re-checked.\n\n**Limits:**\n- Maximum 50,000 contacts per request\n- Subject to your organization's contact limit\n","operationId":"bulkImportContacts","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkContactRequest"},"example":{"contacts":[{"email":"john@example.com","first_name":"John","last_name":"Doe","company_name":"Acme Corp"},{"email":"jane@example.com","first_name":"Jane","last_name":"Smith"}],"TeamId":"550e8400-e29b-41d4-a716-446655440000","CampaignId":"660e8400-e29b-41d4-a716-446655440001"}},"multipart/form-data":{"schema":{"type":"object","required":["contacts","TeamId","CampaignId","mapping_fields"],"properties":{"contacts":{"type":"string","format":"binary","description":"CSV file with contacts"},"TeamId":{"type":"string","format":"uuid","description":"Team ID"},"CampaignId":{"type":"string","format":"uuid","description":"Campaign ID"},"mapping_fields":{"type":"string","description":"JSON object mapping CSV columns to contact fields"},"validate_emails":{"type":"string","enum":["true","false"],"description":"Set to \"true\" to run email verification (consumes credits). Default \"false\"."}}}}}},"responses":{"202":{"description":"Upload accepted for processing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkContactResponse"},"example":{"success":true,"data":{"upload_id":"770e8400-e29b-41d4-a716-446655440002","status":"pending","contact_count":500,"message":"Upload received. Processing will begin shortly."},"request_id":"req_abc123def456"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/contacts/bulk-action":{"post":{"tags":["Contacts"],"summary":"Bulk contact actions","description":"Perform bulk actions on campaign contacts: finish, unfinish, resume, restart, verify,\narchive, transfer, and more.\n\n**Selection:** Provide `contact_ids` for explicit IDs, or use select-all mode with\n`exclude_ids` plus optional `selection_limit`, `selection_offset`, and `sort_by`.\nDo not combine `contact_ids` with selection window fields.\n\n**`campaign_id`:** Required for most campaign-scoped actions (finish, resume, restart,\ntransfer source, assign_step). Optional for team-level actions when using filters.\n\n**Note:** Setting `interest` to `meeting_booked` via `PATCH /contacts/{id}` is informational\nonly and does **not** stop the sequence. Use `action: finish` here to mark contacts finished\nand stop outreach.\n\n**V1 parity:** Request body uses the same snake_case fields as `POST /v1/contact/bulk-action`.\nCamelCase aliases (`CampaignId`, `contactIds`, etc.) are also accepted.\n","operationId":"bulkContactAction","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkContactActionRequest"},"examples":{"finish":{"summary":"Finish selected contacts","value":{"action":"finish","campaign_id":"660e8400-e29b-41d4-a716-446655440001","contact_ids":["770e8400-e29b-41d4-a716-446655440002"]}},"resume":{"summary":"Resume paused contacts in a campaign","value":{"action":"resume","campaign_id":"660e8400-e29b-41d4-a716-446655440001","contact_ids":["770e8400-e29b-41d4-a716-446655440002"]}}}}}},"responses":{"200":{"description":"Bulk action completed (or queued for async actions such as transfer)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkContactActionResponse"},"examples":{"finish":{"summary":"Finish success","value":{"success":true,"message":"Contact(s) finished status updated successfully","request_id":"req_abc123def456"}},"transferQueued":{"summary":"Transfer queued","value":{"success":true,"data":{"message":"Contact transfer queued","count":250,"queued":true,"batch_id":"880e8400-e29b-41d4-a716-446655440003"},"request_id":"req_abc123def456"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"503":{"description":"Background queue unavailable (e.g. transfer)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/campaigns":{"post":{"tags":["Campaigns"],"summary":"Create Campaign","description":"Creates a new campaign. Campaigns start in inactive/draft status.\nWhen **`nodes`** are provided, the server fills **`node.data.type`** (Message type id) from each node’s flow type and **syncs `Messages`** so downstream app flows stay consistent.\n","operationId":"createCampaign","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCampaignRequest"}}}},"responses":{"201":{"description":"Campaign created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"}}},"get":{"tags":["Campaigns"],"summary":"List Campaigns","description":"Lists campaigns with filtering and pagination.","operationId":"listCampaigns","parameters":[{"$ref":"#/components/parameters/TeamIdQuery"},{"$ref":"#/components/parameters/LimitParam"},{"$ref":"#/components/parameters/OffsetParam"},{"name":"search","in":"query","description":"Search by campaign name","schema":{"type":"string"}},{"name":"status","in":"query","description":"Filter by status","schema":{"type":"string","enum":["active","inactive"]}},{"name":"include_subsequences","in":"query","description":"When true, include subsequence campaigns (linked child campaigns) in results. Default false.","schema":{"type":"string","enum":["true","false"],"default":"false"}}],"responses":{"200":{"description":"List of campaigns","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignListResponse"}}}}}}},"/campaigns/{id}":{"get":{"tags":["Campaigns"],"summary":"Get Campaign","description":"Retrieves a single campaign with messages and contact count.","operationId":"getCampaign","parameters":[{"$ref":"#/components/parameters/IdPath"}],"responses":{"200":{"description":"Campaign details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Campaigns"],"summary":"Update Campaign","description":"Updates campaign fields. Only provided fields are updated.\nSome fields cannot be modified on active campaigns.\n","operationId":"updateCampaign","parameters":[{"$ref":"#/components/parameters/IdPath"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCampaignRequest"}}}},"responses":{"200":{"description":"Campaign updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"tags":["Campaigns"],"summary":"Delete Campaign","description":"Archives a campaign (soft delete) and its contacts. The campaign is paused and hidden from default lists.\nDraft campaigns are permanently removed.\n\nTeams must retain at least one non-archived campaign; deleting the last eligible campaign returns **409**.\n","operationId":"deleteCampaign","parameters":[{"$ref":"#/components/parameters/IdPath"}],"responses":{"200":{"description":"Campaign archived or permanently deleted (draft)","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string","format":"uuid"}}},"request_id":{"type":"string"}}},{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"request_id":{"type":"string"}}}]}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Minimum campaigns required on team","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/campaigns/{id}/subsequences":{"get":{"tags":["Campaigns"],"summary":"List Campaign Subsequences","description":"Lists linked subsequence campaigns for a parent campaign.\n","operationId":"listCampaignSubsequences","parameters":[{"$ref":"#/components/parameters/IdPath"}],"responses":{"200":{"description":"Subsequences for the parent campaign","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignSubsequencesResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/campaigns/{id}/parent":{"get":{"tags":["Campaigns"],"summary":"Get Campaign Parent","description":"Returns whether a campaign is a subsequence and, if so, its parent campaign.\n","operationId":"getCampaignParent","parameters":[{"$ref":"#/components/parameters/IdPath"}],"responses":{"200":{"description":"Parent campaign relationship","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignParentResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/campaigns/{id}/sequence":{"get":{"tags":["Campaigns"],"summary":"Get Campaign Sequence","description":"Retrieves the sequence (nodes and edges) for a campaign.\nThis endpoint provides detailed access to the visual sequence builder structure.\n\nThe sequence defines the automation flow including:\n- Start and stop nodes\n- Email, LinkedIn action nodes\n- Wait/delay nodes\n- Conditional logic (If/Switch nodes)\n- HTTP API request nodes\n- Task and note nodes\n","operationId":"getCampaignSequence","parameters":[{"$ref":"#/components/parameters/IdPath"}],"responses":{"200":{"description":"Campaign sequence","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignSequenceResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Campaigns"],"summary":"Update Campaign Sequence","description":"Updates the sequence (nodes and/or edges) for a campaign.\n\n**Important notes:**\n- Providing `nodes` replaces ALL existing nodes\n- Providing `edges` replaces ALL existing edges\n- Omitting a field leaves it unchanged\n\n**Validation rules:**\n- If/Switch nodes cannot exceed 10 paths/cases\n- Each path cannot exceed 10 conditions\n- All node and edge IDs should be valid UUIDs\n- Email nodes must use `subject_a` and `body_a` — the bare `subject` and `body` fields are not accepted\n- For A/B testing, add `subject_b`/`body_b`, `subject_c`/`body_c`, or `subject_d`/`body_d`\n- The server sets **`data.type`** (Message type id) from each node’s flow type when omitted, then syncs **`Messages`**\n","operationId":"updateCampaignSequence","parameters":[{"$ref":"#/components/parameters/IdPath"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSequenceRequest"},"example":{"nodes":[{"id":"550e8400-e29b-41d4-a716-446655440000","type":"startNode","data":{"step_number":0},"position":{"x":100,"y":100}},{"id":"550e8400-e29b-41d4-a716-446655440001","type":"emailNode","data":{"step_number":1,"subject_a":"Hello {{first_name}}","body_a":"Just reaching out..."},"position":{"x":100,"y":250}}],"edges":[{"id":"edge-1","source":"550e8400-e29b-41d4-a716-446655440000","target":"550e8400-e29b-41d4-a716-446655440001","type":"buttonedge"}]}}}},"responses":{"200":{"description":"Sequence updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignSequenceResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/campaigns/{id}/review":{"get":{"tags":["Campaigns"],"summary":"Get Campaign Review","description":"Everything to check before launching a campaign, in one call: the steps on the main path\n(subjects, waits, LinkedIn actions), who sends (sender profiles, inboxes, warmup), who receives\n(contacts by state), the schedule and daily volume, and the launch checks.\n\n`checks` combines the dashboard's start checklist with the trial/plan gate (all `blocking: true`),\nplus warnings that don't block launch (`blocking: false`), such as no contacts waiting or inboxes\nstill warming. `can_launch` is true when the campaign is not already active and no blocking check\nfails.\n\nSequences longer than 12 steps list the first 12 in `sequence.steps`; `sequence.total_steps` has\nthe full count. Waits are folded into `wait_before` on the step that follows them.\n\nReturns **404** for campaigns that don't exist or belong to another organization, and **403**\nwhen the campaign is in your organization but you are not on its team.\n","operationId":"getCampaignReview","parameters":[{"$ref":"#/components/parameters/IdPath"}],"responses":{"200":{"description":"Pre-launch review of the campaign","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignReviewResponse"},"example":{"success":true,"data":{"object":"campaign_review","campaign":{"id":"7c9e6679-7425-40de-944b-e07fc1f90ae7","name":"Q4 founders outreach","status":"not_launched","is_draft":false,"version":"simple","team_id":"550e8400-e29b-41d4-a716-446655440000"},"sequence":{"steps":[{"step":1,"type":"email","label":"Email","wait_before":null,"subject":"Quick question, {{first_name}}","variants":2,"as_reply":false},{"step":2,"type":"email","label":"Email","wait_before":{"value":3,"unit":"days"},"subject":null,"variants":1,"as_reply":true}],"total_steps":2,"emails":2,"has_branches":false},"sending":{"sender_profiles":[{"id":"3f2b8c1a-0d4e-4f6a-9b7c-2e1d5a8f9c0b","name":"Jane's inboxes","inboxes":3,"active_inboxes":3,"warming_inboxes":0,"linkedin":false,"daily_capacity":90}],"active_inboxes":3,"warming_inboxes":0,"linkedin_accounts":0,"daily_capacity":90,"campaign_daily_limit":null},"contacts":{"total":450,"not_started":450,"in_progress":0,"finished":0,"paused":0},"schedule":{"timezone":"America/New_York","days":["monday","tuesday","wednesday","thursday","friday"],"hours":[{"start":"09:00","end":"17:00"}],"next_window":{"day":"tomorrow","start":"09:00","sending_now":false}},"settings":{"tracking":false,"unsubscribe_link":true,"stop_on_reply":{"ends_sequence":true,"applies_to":["email","linkedin"],"configurable":false,"note":"A reply by email or on LinkedIn ends the sequence for that contact. This is automatic, not a setting."}},"billing":{"can_send":true,"trial_available":false},"checks":[{"key":"has_nodes","ok":true,"blocking":true,"message":"Campaign has sequence steps"},{"key":"has_messages","ok":true,"blocking":true,"message":"Campaign has messages"},{"key":"has_sender_profile","ok":true,"blocking":true,"message":"At least one sender profile attached"},{"key":"has_active_email_sender","ok":true,"blocking":true,"message":"At least one active email sender"},{"key":"has_active_linkedin_identity","ok":true,"blocking":true,"message":"At least one sender profile with LinkedIn identity"},{"key":"schedule_valid","ok":true,"blocking":true,"message":"Campaign schedule is valid"},{"key":"can_send","ok":true,"blocking":true,"message":"Trial or plan active"},{"key":"has_contacts","ok":true,"blocking":false,"message":"450 contacts to email"}],"can_launch":true,"estimate":{"sending_days_to_reach_everyone":5},"links":{"campaign":"https://app.supersend.io/auth?type=login&redirect=%2Fcampaign%2F7c9e6679-7425-40de-944b-e07fc1f90ae7%2Fsequence"}},"request_id":"req_abc123def456"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/campaigns/{id}/sender-profiles":{"get":{"tags":["Campaigns"],"summary":"List Campaign Sender Profiles","description":"Lists sender profiles (warmup tags) assigned to a campaign.","operationId":"listCampaignSenderProfiles","parameters":[{"$ref":"#/components/parameters/IdPath"}],"responses":{"200":{"description":"Sender profiles assigned to the campaign","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignSenderProfilesResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"tags":["Campaigns"],"summary":"Assign Sender Profile to Campaign","description":"Associates a sender profile with a campaign so its email senders can send for that campaign. On a parent campaign, the change is copied to its subsequences. Subsequences send from the parent campaign's sender profiles, so this returns 400 for a subsequence.","operationId":"assignCampaignSenderProfile","parameters":[{"$ref":"#/components/parameters/IdPath"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignCampaignSenderProfileRequest"}}}},"responses":{"201":{"description":"Sender profile assigned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignSenderProfileAssignmentResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Sender profile already assigned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/campaigns/{id}/sender-profiles/{senderProfileId}":{"delete":{"tags":["Campaigns"],"summary":"Remove Sender Profile from Campaign","description":"Removes a sender profile assignment from a campaign. On a parent campaign, the change is copied to its subsequences. Subsequences send from the parent campaign's sender profiles, so this returns 400 for a subsequence.","operationId":"removeCampaignSenderProfile","parameters":[{"$ref":"#/components/parameters/IdPath"},{"name":"senderProfileId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Sender profile removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignSenderProfileRemovalResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/playbooks":{"post":{"tags":["Playbooks"],"summary":"Create Playbook","description":"Create a new outbound playbook for a team. A playbook captures the strategy behind campaigns - offer, segment, messaging, hypothesis. All fields except name and TeamId are optional.","operationId":"createPlaybook","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePlaybookRequest"}}}},"responses":{"201":{"description":"Playbook created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaybookResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}},"get":{"tags":["Playbooks"],"summary":"List Playbooks","description":"List playbooks for a team with performance rollup summaries. Supports status filter and pagination.","operationId":"listPlaybooks","parameters":[{"name":"TeamId","in":"query","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["draft","active","archived"]}},{"name":"search","in":"query","required":false,"description":"Case-insensitive partial match on playbook name","schema":{"type":"string","maxLength":200}},{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"sortBy","in":"query","required":false,"schema":{"type":"string","enum":["oldest","newest","last_modified","reply_rate_high","reply_rate_low","campaign_count_high","campaign_count_low"],"default":"last_modified"}}],"responses":{"200":{"description":"List of playbooks with rollup summaries","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaybookListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/playbooks/{id}":{"get":{"tags":["Playbooks"],"summary":"Get Playbook","description":"Get a full playbook record including offer, segment, messaging, hypothesis, linked campaigns, and aggregated performance rollup.\nOmit linked-campaign query parameters to return every linked campaign row. When any `campaigns_*` parameter is present, `campaigns` is a filtered/sorted page and `campaigns_total` is the matching count (rollup `performance` still reflects all linked campaigns).\n","operationId":"getPlaybook","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"campaigns_search","in":"query","description":"Filter linked campaigns by name (case-insensitive substring). Enables paginated campaign list mode.","schema":{"type":"string"}},{"name":"campaigns_status","in":"query","description":"Filter linked campaigns by send status (`active` = sending, `inactive` = stopped).","schema":{"type":"string","enum":["active","inactive"]}},{"name":"campaigns_sort","in":"query","description":"Sort order for the linked campaigns page.","schema":{"type":"string","enum":["newest","oldest","last_modified","name_asc","name_desc","sends_high","sends_low","replies_high","replies_low","reply_rate_high","reply_rate_low","interested_high","interested_low"],"default":"newest"}},{"name":"campaigns_limit","in":"query","description":"Page size for linked campaigns (1–500, default 20).","schema":{"type":"integer","minimum":1,"maximum":500,"default":20}},{"name":"campaigns_offset","in":"query","description":"Offset for linked campaigns pagination.","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Playbook with performance rollup","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaybookDetailResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Playbooks"],"summary":"Update Playbook","description":"Update any writable playbook fields. Can set status to archived.","operationId":"updatePlaybook","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePlaybookRequest"}}}},"responses":{"200":{"description":"Updated playbook","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaybookResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"tags":["Playbooks"],"summary":"Archive or permanently delete Playbook","description":"Without `permanent`, sets the playbook status to **archived** (soft delete). Campaign links remain until changed on each campaign.\n\nWith **`permanent=true`**, permanently removes the playbook row. Only allowed when the playbook is already **archived**. Linked campaigns are not deleted; `PlaybookId` on those campaigns is cleared (SET NULL).\n","operationId":"deletePlaybook","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"permanent","in":"query","required":false,"description":"If `true`, permanently delete the playbook (only when status is `archived`).","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"Archived playbook (soft delete) or success message when permanently deleted","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"Playbook archived"},"data":{"$ref":"#/components/schemas/Playbook"}}},{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"Playbook deleted"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/playbooks/{id}/duplicate":{"post":{"tags":["Playbooks"],"summary":"Duplicate playbook","description":"Creates a new **draft** playbook with the same offer, segment, messaging, and hypothesis as the source.\nDoes **not** copy linked campaigns or outcome notes. Default name is `Copy of {original name}` unless `name` is provided.\n","operationId":"duplicatePlaybook","parameters":[{"name":"id","in":"path","required":true,"description":"Source playbook UUID","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":255,"description":"Optional name for the duplicate (default Copy of …)"}}}}}},"responses":{"201":{"description":"New playbook created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaybookResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/playbooks/{id}/link-campaigns":{"post":{"tags":["Playbooks"],"summary":"Bulk link campaigns to playbook","description":"Sets `PlaybookId` on all matching team campaigns in a single database update.\nOnly campaigns that belong to `TeamId`, are not deleted, and are not disabled are updated.\nRequest may include up to 500 campaign UUIDs.\n","operationId":"linkPlaybookCampaignsBulk","parameters":[{"name":"id","in":"path","required":true,"description":"Playbook UUID","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkPlaybookCampaignsRequest"}}}},"responses":{"200":{"description":"Number of campaigns updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkPlaybookCampaignsResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/campaign-categories":{"get":{"tags":["Campaign Categories"],"summary":"List Campaign Categories","description":"Lists campaign categories (folders) for a team. Categories are used to organize campaigns.","operationId":"listCampaignCategories","parameters":[{"name":"teamId","in":"query","required":true,"schema":{"type":"string","format":"uuid"},"description":"Team ID to list categories for"}],"responses":{"200":{"description":"List of campaign categories","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignCategoryListResponse"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"tags":["Campaign Categories"],"summary":"Create Campaign Category","description":"Creates a new campaign category (folder) for a team.","operationId":"createCampaignCategory","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["TeamId","name"],"properties":{"TeamId":{"type":"string","format":"uuid","description":"Team ID to create category for"},"name":{"type":"string","minLength":1,"description":"Category name (unique per team)"}}}}}},"responses":{"201":{"description":"Category created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignCategoryResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Campaign Categories"],"summary":"Update Campaign Category","description":"Updates (renames) a campaign category.","operationId":"updateCampaignCategory","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["TeamId","categoryId","name"],"properties":{"TeamId":{"type":"string","format":"uuid","description":"Team ID the category belongs to"},"categoryId":{"type":"string","format":"uuid","description":"Category ID to update"},"name":{"type":"string","minLength":1}}}}}},"responses":{"200":{"description":"Category updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignCategoryResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/campaign-categories/{id}":{"delete":{"tags":["Campaign Categories"],"summary":"Delete Campaign Category","description":"Deletes a campaign category. Campaigns in this category become uncategorized.","operationId":"deleteCampaignCategory","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Category ID to delete"},{"name":"teamId","in":"query","required":true,"schema":{"type":"string","format":"uuid"},"description":"Team ID the category belongs to"}],"responses":{"200":{"description":"Category deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/campaigns/{id}/inbox-link-clicks":{"get":{"tags":["Campaigns"],"summary":"List Campaign Inbox Reply Link Clicks","description":"Retrieves inbox reply link clicks attributed to a campaign.\n\nA click is attributed when a tracked inbox reply link (`link_source = inbox_reply`)\nhas `CampaignId` set on the `Link` row. Clicks are deduplicated per\n`(contact, link, UTC calendar day)` for rollup counters; raw clicks are returned here.\n\n**Filtering:**\n- `contact_id` — restrict to a specific contact\n- `date_from` / `date_to` — ISO 8601 date strings\n- `first_only=true` — return only the first click per (contact, link, day)\n\n**Pagination:** `page` (default 1) and `limit` (default 25, max 100).\n","operationId":"getCampaignInboxLinkClicks","parameters":[{"$ref":"#/components/parameters/IdPath"},{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"limit","in":"query","schema":{"type":"integer","default":25,"maximum":100}},{"name":"contact_id","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"date_from","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"date_to","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"first_only","in":"query","description":"When true, return only the first click per (contact, link, day).","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"Paginated inbox reply link clicks for the campaign","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","properties":{"campaign_id":{"type":"string","format":"uuid"},"clicks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"link_id":{"type":"string","format":"uuid"},"original_url":{"type":"string"},"final_url":{"type":"string","description":"Resolved URL including appended UTM parameters."},"clicked_at":{"type":"string","format":"date-time"},"is_first_today":{"type":"boolean"},"ip":{"type":"string","nullable":true},"contact":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string"},"name":{"type":"string","nullable":true}}},"sender_email":{"type":"string","nullable":true},"sequence_step":{"type":"integer","nullable":true},"conversation_id":{"type":"string","format":"uuid"},"conversation_message_id":{"type":"string","format":"uuid"}}}},"pagination":{"type":"object","properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"total_pages":{"type":"integer"}}}}}}}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/teams":{"post":{"tags":["Teams"],"summary":"Create Team","description":"Creates a new team. The creating user is added as a member.","operationId":"createTeam","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTeamRequest"}}}},"responses":{"201":{"description":"Team created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"}}},"get":{"tags":["Teams"],"summary":"List Teams","description":"Lists teams the user has access to.\nOrg admins can list all teams with `?all=true`.\n","operationId":"listTeams","parameters":[{"$ref":"#/components/parameters/LimitParam"},{"$ref":"#/components/parameters/OffsetParam"},{"name":"search","in":"query","description":"Search by team name","schema":{"type":"string"}},{"name":"all","in":"query","description":"List all org teams (admin only)","schema":{"type":"string","enum":["true","false"]}}],"responses":{"200":{"description":"List of teams","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamListResponse"}}}}}}},"/teams/{id}":{"get":{"tags":["Teams"],"summary":"Get Team","description":"Retrieves a team with member information.","operationId":"getTeam","parameters":[{"$ref":"#/components/parameters/IdPath"}],"responses":{"200":{"description":"Team details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamDetailResponse"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Teams"],"summary":"Update Team","description":"Updates team settings. Only provided fields are updated.","operationId":"updateTeam","parameters":[{"$ref":"#/components/parameters/IdPath"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTeamRequest"}}}},"responses":{"200":{"description":"Team updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/teams/{id}/usage":{"get":{"tags":["Teams"],"summary":"Get Team Usage","description":"Returns contact usage and capacity for a team.","operationId":"getTeamUsage","parameters":[{"$ref":"#/components/parameters/IdPath"}],"responses":{"200":{"description":"Usage data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamUsageResponse"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/teams/{id}/inbox-link-activity":{"get":{"tags":["Teams"],"summary":"List team inbox link activity","description":"Returns recent inbox reply link clicks for a team.","operationId":"listTeamInboxLinkActivity","parameters":[{"$ref":"#/components/parameters/IdPath"},{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"Team link activity feed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamInboxLinkActivityResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/billing/team-usage":{"get":{"tags":["Billing"],"summary":"Get Team Cost Allocation","description":"Returns org-level usage and costs broken down by team for accounting and reconciliation.\nUsed by customers with multiple teams (e.g. one per client) who need to see the split\nfor internal allocation, chargeback, or payment method reconciliation.\n","operationId":"getBillingTeamUsage","parameters":[{"name":"period","in":"query","description":"Billing period index (0=current, 1=previous, etc., up to 6)","schema":{"type":"integer","minimum":0,"maximum":6,"default":0}}],"responses":{"200":{"description":"Team usage and cost allocation data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingTeamUsageResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/billing/client-costs":{"get":{"tags":["Billing"],"summary":"Get Client Costs (COGS)","description":"Returns combined per-team COGS report including measured SuperSend costs,\nuser-defined BYO infrastructure costs, and tool/enrichment costs.\n`revenue`, `margin`, and `has_revenue` are reserved for a future team-revenue feature; they are currently null/false.\n","operationId":"getBillingClientCosts","parameters":[{"name":"period","in":"query","description":"Billing period index (0=current, 1=previous, etc., up to 6)","schema":{"type":"integer","minimum":0,"maximum":6,"default":0}}],"responses":{"200":{"description":"Combined client costs data","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"summary":{"type":"object","properties":{"ss_costs":{"type":"number"},"byo_infra":{"type":"number"},"tools_enrichment":{"type":"number"},"total_cogs":{"type":"number"}}},"teams":{"type":"array","items":{"type":"object","properties":{"team_id":{"type":"string"},"team_name":{"type":"string"},"ss_allocated":{"type":"number"},"byo_infra":{"type":"number"},"tools_enrichment":{"type":"number"},"total_cogs":{"type":"number"},"revenue":{"type":"number","nullable":true,"description":"Reserved; currently always null until team revenue ships."},"margin":{"type":"number","nullable":true,"description":"Reserved; currently always null until team revenue ships."}}}},"has_revenue":{"type":"boolean","description":"Reserved; currently always false until team revenue ships."}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/billing/cost-items":{"get":{"tags":["Billing"],"summary":"List Cost Items","description":"List all user-defined cost items for the organization.","operationId":"listBillingCostItems","responses":{"200":{"description":"List of cost items","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/CostItem"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"post":{"tags":["Billing"],"summary":"Create Cost Item","description":"Create a new cost item for COGS tracking.","operationId":"createBillingCostItem","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CostItemInput"}}}},"responses":{"201":{"description":"Cost item created","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"item":{"$ref":"#/components/schemas/CostItem"}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/billing/cost-items/{id}":{"put":{"tags":["Billing"],"summary":"Update Cost Item","description":"Update an existing cost item.","operationId":"updateBillingCostItem","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CostItemInput"}}}},"responses":{"200":{"description":"Cost item updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"item":{"$ref":"#/components/schemas/CostItem"}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"tags":["Billing"],"summary":"Delete Cost Item","description":"Soft-delete a cost item.","operationId":"deleteBillingCostItem","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Cost item deleted"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/email-validation/verify":{"post":{"tags":["Email validation"],"summary":"Verify email (SMTP)","description":"Runs the same deliverability verification used for contacts (SMTP probe via SuperSend's validator).\n**Consumes global email verification credits** (same pool as contact validation and CSV import).\n\nOptional `TeamId` attributes the credit debit to a team for reporting; omit for org-level-only attribution.\n\nThe response includes email intelligence fields (risk score, flags, provider hints, network metrics) in the\nsame payload as the SMTP result—one verification credit unlocks both.\n","operationId":"verifyEmailAddress","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailValidationVerifyRequest"},"example":{"email":"prospect@example.com"}}}},"responses":{"200":{"description":"Verification result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailValidationVerifyResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"Not enough email verification credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"payment_required_error","code":"insufficient_credits","message":"Not enough email verification credits available","details":{"requiredCredits":1,"availableCredits":0}},"request_id":"req_a1b2c3d4e5f6789012345678"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"503":{"description":"Validator unavailable or incomplete response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/intelligence/capacity-summary":{"get":{"tags":["Intelligence"],"summary":"Capacity Summary","description":"Returns capacity planning data for a team: campaign completion forecasts,\nallocated capacity, and whether more infrastructure is needed.\nUsed by MCP tools and in-app chat.\n","operationId":"getCapacitySummary","parameters":[{"name":"teamId","in":"query","required":true,"description":"Team UUID (from list teams)","schema":{"type":"string","format":"uuid"}},{"name":"page","in":"query","description":"Page number","schema":{"type":"integer","default":1}},{"name":"limit","in":"query","description":"Max campaigns per page","schema":{"type":"integer","default":20}},{"name":"sortBy","in":"query","description":"Sort by name, contactsRemaining, or projectedCompletionDays","schema":{"type":"string","default":"projectedCompletionDays"}},{"name":"sortDirection","in":"query","description":"asc or desc","schema":{"type":"string","enum":["asc","desc"],"default":"asc"}},{"name":"planningFilter","in":"query","description":"Filter campaigns before pagination (all, active=sending with contacts remaining (includes no-capacity rows), inactive, finished contacts, no-capacity=sending campaigns with no allocated send capacity). Overview metrics in the response are always for the full team.","schema":{"type":"string","enum":["all","active","inactive","finished","no-capacity"],"default":"all"}},{"name":"search","in":"query","description":"Case-insensitive substring match on campaign name","schema":{"type":"string","maxLength":500}},{"name":"includeCampaignId","in":"query","description":"When set, response includes focusedCampaign with this campaign's row even if it would not appear on the requested page.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Capacity planning data","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","description":"overview, campaigns, pagination, recommendations, completionForecastChart (team-wide top 10 by projected completion days, ignores table filters)"},"request_id":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/intelligence/deliverability-summary":{"get":{"tags":["Intelligence"],"summary":"Deliverability Summary","description":"Returns deliverability diagnosis for a team: reply rate, bounce breakdown,\nplacement test results, target mix (Gmail/Outlook/corporate), and recommendations.\nUse for questions like \"Why is my reply rate low?\"\n\n**Bounce data:** `bounce_categories` and `bounce_types` are both derived from **send/bounce\nevents** (`Events.bounce_category` and `Events.bounce_subtype`). Each `bounce_types[]` row\nincludes `category` from the event; `percentage` is the share of that subtype within its\n**category bucket** (so lines under a category sum to ~100% when subtypes are fully populated).\n","operationId":"getDeliverabilitySummary","parameters":[{"name":"teamId","in":"query","required":true,"description":"Team UUID (from list teams)","schema":{"type":"string","format":"uuid"}},{"name":"windowDays","in":"query","description":"Days to look back (1-90, default 30)","schema":{"type":"integer","minimum":1,"maximum":90,"default":30}}],"responses":{"200":{"description":"Deliverability diagnosis data","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","description":"summary, bounce_by_provider, placement, target_mix, recommendations"},"request_id":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/intelligence/sender-health-summary":{"get":{"tags":["Intelligence"],"summary":"Sender Health Summary","description":"Returns per-sender health metrics for a team: sends, bounce rate, and at-risk flags.\n**Bounce rate and `bounces` count invalid-recipient (list-quality) bounces only** — aligned\nwith in-app sender health score and sender list bounce rate. Field **`all_bounces`** (per sender)\nand **`total_all_bounces`** (summary) include every bounced send (soft, policy, etc.) for context.\nUse for \"Which senders are at risk?\"\n","operationId":"getSenderHealthSummary","parameters":[{"name":"teamId","in":"query","required":true,"description":"Team UUID (from list teams)","schema":{"type":"string","format":"uuid"}},{"name":"windowDays","in":"query","description":"Days to look back (1-90, default 30)","schema":{"type":"integer","minimum":1,"maximum":90,"default":30}}],"responses":{"200":{"description":"Sender health data"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/intelligence/outbound-summary":{"get":{"tags":["Intelligence"],"summary":"Outbound Summary","description":"Returns team-level outbound metrics and top campaigns. Use for\n\"How's my outreach performing?\"\n","operationId":"getOutboundSummary","parameters":[{"name":"teamId","in":"query","required":true,"description":"Team UUID (from list teams)","schema":{"type":"string","format":"uuid"}},{"name":"windowDays","in":"query","description":"Days to look back (1-90, default 30)","schema":{"type":"integer","minimum":1,"maximum":90,"default":30}}],"responses":{"200":{"description":"Outbound summary data"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/intelligence/domain-health-summary":{"get":{"tags":["Intelligence"],"summary":"Domain Health Summary","description":"Returns an actionable sending-domain inventory for a team, grouped for humans and agents:\n`good`, `empty`, `needs_dns_fix`, `should_cancel` (sending domain on RBL/DNSBL),\n`cancelled`, `failed`, `expired`, `setting_up`, `inactive`, and `can_add_mailboxes`.\nEach domain includes a short `reason`, mailbox capacity, and SPF/DKIM/DMARC/MX booleans.\n\nImportant: `sending_domain_on_rbl` / `should_cancel` is NOT the Admin recipient Blacklist\n(suppression of emails/domains you should not mail *to*). See `clarifications` in the response.\n","operationId":"getDomainHealthSummary","parameters":[{"name":"teamId","in":"query","required":true,"description":"Team UUID (from list teams)","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Domain health data"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/senders":{"get":{"tags":["Senders"],"summary":"List Senders","description":"Lists email senders with filtering and pagination.","operationId":"listSenders","parameters":[{"$ref":"#/components/parameters/TeamIdQuery"},{"$ref":"#/components/parameters/LimitParam"},{"$ref":"#/components/parameters/OffsetParam"},{"name":"search","in":"query","description":"Search by email or send_as","schema":{"type":"string"}},{"name":"provider","in":"query","description":"Filter by provider","schema":{"type":"string","enum":["smtp","outlook","google","missioninbox","supersend-relay","ss-private-smtp","sendgrid","mailreef","infraforge","zoho","namecheap","hostinger","titan","ionos","dreamhost","aol","mailgun","aws","azure"]}},{"name":"disabled","in":"query","description":"Filter by disabled status","schema":{"type":"string","enum":["true","false"]}},{"name":"warm","in":"query","description":"Filter by warming status","schema":{"type":"string","enum":["true","false"]}},{"name":"domain","in":"query","description":"Filter by the mailbox email domain (part after @). Case-insensitive.","schema":{"type":"string","maxLength":255}},{"name":"domain_match","in":"query","description":"How `domain` is applied: `exact` (default) matches the full domain; `partial` matches\nsubstrings (e.g. `company` matches `mail.company.com`).\n","schema":{"type":"string","enum":["exact","partial"],"default":"exact"}}],"responses":{"200":{"description":"List of senders","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SenderListResponse"}}}}}}},"/senders/{id}":{"get":{"tags":["Senders"],"summary":"Get Sender","description":"Retrieves sender details including health and configuration.","operationId":"getSender","parameters":[{"$ref":"#/components/parameters/IdPath"}],"responses":{"200":{"description":"Sender details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SenderResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Senders"],"summary":"Update Sender","description":"Updates sender settings. Only provided fields are updated.\nDoes not support changing provider or re-verifying credentials.\n\n**Reply forwarding:** `forward_to` is the legacy single destination when `forward_rules` is null or empty.\nWhen `forward_rules` is a non-empty array, each rule is evaluated: rules with `label_ids` set to `null` forward every reply to that rule's `emails`; rules with one or more label UUIDs forward only when the conversation has at least one matching label. Omit or clear `forward_rules` to use `forward_to` only.\n\n**Unsubscribe forwarding:** `forward_unsubscribes` controls whether unsubscribe replies are forwarded to the configured forwarding destinations. When enabled, unsubscribe replies ignore label filters and are forwarded to every configured destination.\n","operationId":"updateSender","parameters":[{"$ref":"#/components/parameters/IdPath"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSenderRequest"}}}},"responses":{"200":{"description":"Sender updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SenderResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/placement-tests":{"get":{"tags":["Placement Tests"],"summary":"List Placement Tests","description":"Lists placement tests with filtering by team, sender, status, and search.\nSupports conditional filters for score segments and date ranges.\n","operationId":"listPlacementTests","parameters":[{"name":"team_id","in":"query","description":"Filter by team ID","schema":{"type":"string","format":"uuid"}},{"name":"sender_id","in":"query","description":"Filter by sender ID","schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","description":"Filter by status","schema":{"type":"string","enum":["pending","sending","sent","completed","failed"]}},{"name":"search","in":"query","description":"Search by test name","schema":{"type":"string"}},{"name":"conditional_filters","in":"query","description":"JSON string for score/date filters","schema":{"type":"string"}},{"name":"sort_by","in":"query","description":"Sort field","schema":{"type":"string","enum":["created_at","name","status","score","sent_at","completed_at"],"default":"created_at"}},{"name":"sort_order","in":"query","description":"Sort direction","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"$ref":"#/components/parameters/LimitParam"},{"$ref":"#/components/parameters/OffsetParam"}],"responses":{"200":{"description":"List of placement tests","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlacementTestListResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"post":{"tags":["Placement Tests"],"summary":"Create Placement Test","description":"Creates a new placement test with seed addresses for deliverability analysis.","operationId":"createPlacementTest","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePlacementTestRequest"}}}},"responses":{"201":{"description":"Placement test created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePlacementTestResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/placement-tests/export":{"post":{"tags":["Placement Tests"],"summary":"Export Placement Tests","description":"Creates an async CSV export of placement tests for a team.\nUser receives an email when the export is ready.\n","operationId":"exportPlacementTests","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportPlacementTestsRequest"}}}},"responses":{"202":{"description":"Export job created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"Export already in progress"}}}},"/placement-tests/{id}":{"get":{"tags":["Placement Tests"],"summary":"Get Placement Test","description":"Retrieves a single placement test with full details including the test email\n(subject, from, body), results summary, and per-seed placement results.\n","operationId":"getPlacementTest","parameters":[{"$ref":"#/components/parameters/IdPath"}],"responses":{"200":{"description":"Placement test details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlacementTestResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/events":{"get":{"tags":["Events"],"summary":"List Events","description":"Lists activity events (sends, opens, clicks, replies, etc.)\nwith filtering by team, campaign, contact, and event type.\n","operationId":"listEvents","parameters":[{"$ref":"#/components/parameters/TeamIdQuery"},{"$ref":"#/components/parameters/CampaignIdQuery"},{"name":"ContactId","in":"query","description":"Filter by contact ID","schema":{"type":"string","format":"uuid"}},{"name":"SenderId","in":"query","description":"Filter by sender ID","schema":{"type":"string","format":"uuid"}},{"name":"type","in":"query","description":"Filter by event type","schema":{"type":"string","enum":["scheduled","sent","open","reply","click","bounce","unsubscribe"]}},{"name":"channel","in":"query","description":"Filter by channel","schema":{"type":"string","enum":["email","linkedin","text","call"]}},{"$ref":"#/components/parameters/LimitParam"},{"$ref":"#/components/parameters/OffsetParam"},{"name":"start_date","in":"query","description":"Filter events after this date (ISO 8601). When omitted with no end_date, defaults to 30 days ago.","schema":{"type":"string","format":"date-time"}},{"name":"end_date","in":"query","description":"Filter events before this date (ISO 8601). When both start_date and end_date are provided, the gap cannot exceed 90 days.","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"List of events","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventListResponse"}}}}}}},"/events/{id}":{"get":{"tags":["Events"],"summary":"Get Event","description":"Retrieves detailed information about a specific event.","operationId":"getEvent","parameters":[{"$ref":"#/components/parameters/IdPath"}],"responses":{"200":{"description":"Event details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/webhooks":{"get":{"tags":["Webhooks"],"summary":"List Webhooks","description":"List all webhook integrations for a team.","operationId":"listWebhooks","parameters":[{"name":"team_id","in":"query","required":true,"schema":{"type":"string","format":"uuid"},"description":"Team ID"},{"name":"enabled","in":"query","schema":{"type":"boolean"},"description":"Filter by enabled status"},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/offset"}],"responses":{"200":{"description":"List of webhooks","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookListResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/webhooks/{id}/deliveries":{"get":{"tags":["Webhooks"],"summary":"List Webhook Deliveries","description":"List webhook delivery history for a specific webhook integration.\nReturns delivery attempts with status, response details, and retry information.\n","operationId":"listWebhookDeliveries","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Webhook integration ID"},{"name":"team_id","in":"query","required":true,"schema":{"type":"string","format":"uuid"},"description":"Team ID"},{"name":"status","in":"query","schema":{"type":"string","enum":["pending","delivered","failed","retrying"]},"description":"Filter by delivery status"},{"name":"event_type","in":"query","schema":{"type":"string"},"description":"Filter by event type (reply, open, click, etc.)"},{"name":"campaign_id","in":"query","schema":{"type":"string","format":"uuid"},"description":"Filter by campaign ID"},{"name":"search","in":"query","schema":{"type":"string","maxLength":320},"description":"Filter by contact email (case-insensitive partial match)"},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/offset"}],"responses":{"200":{"description":"List of webhook deliveries","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryListResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/webhooks/{id}/deliveries/{deliveryId}":{"get":{"tags":["Webhooks"],"summary":"Get Webhook Delivery","description":"Get detailed information about a specific webhook delivery,\nincluding the full payload and response body.\n","operationId":"getWebhookDelivery","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Webhook integration ID"},{"name":"deliveryId","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Delivery ID"},{"name":"team_id","in":"query","required":true,"schema":{"type":"string","format":"uuid"},"description":"Team ID"}],"responses":{"200":{"description":"Webhook delivery details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/webhooks/{id}/deliveries/{deliveryId}/retry":{"post":{"tags":["Webhooks"],"summary":"Retry Webhook Delivery","description":"Manually retry a failed webhook delivery.\nResets the attempt count and queues the delivery for immediate retry.\n","operationId":"retryWebhookDelivery","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Webhook integration ID"},{"name":"deliveryId","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Delivery ID to retry"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["team_id"],"properties":{"team_id":{"type":"string","format":"uuid","description":"Team ID"}}}}}},"responses":{"200":{"description":"Retry queued successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryRetryResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/mailboxes/purchase":{"post":{"tags":["Mailboxes"],"summary":"Purchase Mailboxes","description":"Purchase mailboxes for existing domains. Domains must exist in the org.","operationId":"purchaseMailboxes","responses":{"200":{"description":"Purchase started; mailboxes are created asynchronously"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/domains":{"get":{"tags":["Domains"],"summary":"List Domains","description":"List managed domains with filtering and pagination.","operationId":"listDomains","parameters":[{"name":"team_id","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","schema":{"type":"string","enum":["pending","purchasing","purchased","setting_up","active","purchase_failed","setup_failed","inactive","expired","cancelled","awaiting_nameservers"]},"description":"`awaiting_nameservers` = connected domains waiting on their owner's nameserver change"},{"name":"managed","in":"query","schema":{"type":"string","enum":["internal","external"]}},{"name":"search","in":"query","schema":{"type":"string"}},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/offset"}],"responses":{"200":{"description":"List of domains"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/domains/purchase":{"post":{"tags":["Domains"],"summary":"Purchase Domains","description":"Purchase one or more domains. Domains are processed asynchronously.","operationId":"purchaseDomains","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["domains","paymentMethodId","forwardingAddress","contactDetails"],"properties":{"domains":{"type":"array","items":{"type":"string"},"minItems":1},"paymentMethodId":{"type":"string"},"forwardingAddress":{"type":"string"},"contactDetails":{"type":"object"},"dmarcEmail":{"type":"string"}}}}}},"responses":{"201":{"description":"Purchase initiated"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/domains/purchase-with-mailboxes":{"post":{"tags":["Domains"],"summary":"Purchase Domains and Mailboxes","description":"Purchase domains and/or mailboxes in one transaction.","operationId":"purchaseDomainsAndMailboxes","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["paymentMethodId"],"properties":{"domains":{"type":"array","items":{"type":"string"}},"domainsWithProviders":{"type":"array","items":{"type":"object","required":["domain","provider"],"properties":{"domain":{"type":"string"},"provider":{"type":"string","enum":["google","outlook","azure","smtp"]}}}},"mailboxes":{"type":"array","items":{"type":"object","required":["username","firstName","lastName","domain"],"properties":{"username":{"type":"string"},"firstName":{"type":"string","minLength":1},"lastName":{"type":"string","minLength":1},"domain":{"type":"string"},"signature":{"type":"string"},"provider":{"type":"string","enum":["google","outlook","azure","smtp"]}}},"minItems":1},"paymentMethodId":{"type":"string"},"TeamId":{"type":"string","format":"uuid"},"forwardingAddress":{"type":"string"},"dmarcEmail":{"type":"string"},"contactDetails":{"type":"object"},"saveContactAsDefault":{"type":"boolean"}}}}}},"responses":{"201":{"description":"Purchase initiated"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/domains/connect":{"post":{"tags":["Domains"],"summary":"Connect Domain","description":"Connects a domain the organization already owns (registered anywhere outside SuperSend) to a\nteam, so inboxes can be bought on it. Free. **Nothing changes at the registrar yet**: the\ndomain gets nameservers to set only once inboxes on it are paid for (list it in\n`POST /setup/inbox-checkout`), and `POST /domains/{id}/verify` then shows them.\n\nUse a domain only for outreach: changing its nameservers later moves all of its DNS (website,\nother email) to SuperSend.\n\nConnecting a domain that's already in the organization returns it with\n`already_connected: true` (200) instead of failing.\n","operationId":"connectDomain","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["domain"],"properties":{"domain":{"type":"string","description":"Bare domain name; a URL or leading www. is stripped.","example":"tryacme.com"},"team_id":{"type":"string","format":"uuid","description":"Team to connect it to. Defaults to the caller's default team."}}}}}},"responses":{"200":{"description":"Domain was already in the organization (`already_connected: true`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainSetupResponse"}}}},"201":{"description":"Domain connected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainSetupResponse"},"example":{"success":true,"data":{"object":"domain_setup","id":"7c9e6679-7425-40de-944b-e07fc1f90ae7","name":"tryacme.com","managed":"external","status":"pending","team_id":"550e8400-e29b-41d4-a716-446655440000","team":{"id":"550e8400-e29b-41d4-a716-446655440000","name":"Acme Outbound"},"stage":"connected","nameservers":{"change_required":false,"set_to":null,"current":null,"verified":false,"verified_at":null},"mailboxes":{"total":0,"active":0,"in_progress":0,"failed":0,"items":[]},"next":"Connected. No DNS change is needed until you buy mailboxes on it. ...","already_connected":false},"request_id":"req_abc123def456"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"The domain is in another SuperSend account (`domain_taken`) or another team in this organization (`domain_in_other_team`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/domains/{id}/verify":{"post":{"tags":["Domains"],"summary":"Verify Domain","description":"Where a domain stands on the way to sending inboxes. For a connected domain waiting on its\nowner's nameserver change, looks the nameservers up live first. Returns the nameservers to set\n(`nameservers.set_to`), whether they're live (`nameservers.verified`), the domain's inboxes,\nand `next`.\n\n`stage` is one of: `connected` (nothing to do until inboxes are bought), `preparing_nameservers`\n(inboxes paid; nameservers ready in a few minutes), `awaiting_nameservers` (set the nameservers\nshown at the registrar), `ready` (inboxes being created or live), `setting_up` (bought through\nSuperSend, still registering), `unavailable` (cancelled, expired or failed).\n","operationId":"verifyDomain","parameters":[{"name":"id","in":"path","required":true,"description":"Domain UUID or name (e.g. tryacme.com)","schema":{"type":"string"}}],"responses":{"200":{"description":"Domain setup","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainSetupResponse"},"example":{"success":true,"data":{"object":"domain_setup","id":"7c9e6679-7425-40de-944b-e07fc1f90ae7","name":"tryacme.com","managed":"external","status":"awaiting_nameservers","stage":"awaiting_nameservers","nameservers":{"change_required":true,"set_to":["ada.ns.example.net","bob.ns.example.net"],"current":["ns1.registrar-example.com","ns2.registrar-example.com"],"verified":false,"verified_at":null},"mailboxes":{"total":2,"active":0,"in_progress":2,"failed":0,"items":[{"email":"ada@tryacme.com","provider":"google","status":"pending"},{"email":"alan@tryacme.com","provider":"google","status":"pending"}]},"next":"This domain is registered outside SuperSend. At the registrar where tryacme.com is registered, replace all of its nameservers with: ada.ns.example.net, bob.ns.example.net. ...","check":{"checked_at":"2026-10-06T12:00:00.000Z","looked_up":true}},"request_id":"req_abc123def456"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/domains/{id}":{"get":{"tags":["Domains"],"summary":"Get Domain","description":"Get a single domain by ID or name, including `setup` (stage, nameservers to set for a connected domain, its inboxes, and `next`; same fields as `POST /domains/{id}/verify`, without the live lookup).","operationId":"getDomain","parameters":[{"name":"id","in":"path","required":true,"description":"Domain UUID or name (e.g. tryacme.com)","schema":{"type":"string"}}],"responses":{"200":{"description":"Domain details"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/conversations":{"get":{"tags":["Conversations"],"summary":"List Conversations","description":"List conversations from email inbox or LinkedIn inbox with filtering and pagination.\nUse `channel` to filter by platform. Use `last_message_direction` to filter by who sent the last message:\n- `inbound` = replies from contacts (needs your response)\n- `outbound` = your sent messages (waiting on their reply)\n","operationId":"listConversations","parameters":[{"name":"team_id","in":"query","description":"Filter by team ID (UUID from list teams)","schema":{"type":"string","format":"uuid"}},{"name":"channel","in":"query","description":"Which inbox to list (email or linkedin)","schema":{"type":"string","enum":["email","linkedin"],"default":"linkedin"}},{"name":"last_message_direction","in":"query","description":"Filter by who sent the last message; inbound = replies from contacts, outbound = your sent messages","schema":{"type":"string","enum":["inbound","outbound"]}},{"name":"identity_id","in":"query","description":"Filter by identity ID","schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","description":"Filter by archive status","schema":{"type":"string","enum":["archived","unarchived"]}},{"name":"read_status","in":"query","description":"Filter by read status","schema":{"type":"string","enum":["read","unread"]}},{"name":"search","in":"query","description":"Search in conversation title, messages, or contact details","schema":{"type":"string","maxLength":100}},{"name":"sort_by","in":"query","description":"Sort field (default date)","schema":{"type":"string","enum":["date"],"default":"date"}},{"name":"sort_direction","in":"query","description":"Sort order","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"$ref":"#/components/parameters/LimitParam"},{"$ref":"#/components/parameters/OffsetParam"}],"responses":{"200":{"description":"List of conversations","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationListResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/conversations/{id}":{"get":{"tags":["Conversations"],"summary":"Get Conversation","description":"Retrieves a conversation with recent messages (up to 50).","operationId":"getConversation","parameters":[{"name":"id","in":"path","required":true,"description":"Conversation ID","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Conversation details with messages","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Conversations"],"summary":"Update Conversation","description":"Update conversation status (archive, mark read, etc.).","operationId":"updateConversation","parameters":[{"name":"id","in":"path","required":true,"description":"Conversation ID","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","minProperties":1,"properties":{"is_archived":{"type":"boolean","description":"Archive or unarchive conversation"},"is_unread":{"type":"boolean","description":"Mark as read or unread"},"mark_all_read":{"type":"boolean","description":"Mark all messages as read"}}}}}},"responses":{"200":{"description":"Conversation updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationUpdateResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/conversations/{id}/messages":{"get":{"tags":["Conversations"],"summary":"Get Messages","description":"Get paginated messages in a conversation. Messages are returned oldest first.","operationId":"getConversationMessages","parameters":[{"name":"id","in":"path","required":true,"description":"Conversation ID","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/LimitParam"},{"$ref":"#/components/parameters/OffsetParam"}],"responses":{"200":{"description":"List of messages","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageListResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"tags":["Conversations"],"summary":"Send Message","description":"Send a message in a conversation. The endpoint automatically detects the\nconversation platform type and routes the message appropriately.\n\n**Platform-specific behavior:**\n- **Email**: Requires `sender_id`. Supports `subject`, `cc`, `bcc`, `attachments`, and `is_html`.\n- **LinkedIn**: Only requires `message`. Message is queued for delivery via LinkedIn.\nThe message is queued for async delivery. A temporary message is created\nimmediately for optimistic UI updates, and the actual delivery happens\nin the background.\n","operationId":"sendConversationMessage","parameters":[{"name":"id","in":"path","required":true,"description":"Conversation ID","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["message"],"properties":{"message":{"type":"string","minLength":1,"maxLength":8000,"description":"Message content (required for all platforms)"},"sender_id":{"type":"string","format":"uuid","description":"Sender/mailbox ID to send from (required for email conversations).\nMust be an active sender in your organization.\n"},"subject":{"type":"string","description":"Email subject line (email only, defaults to \"Re: {conversation title}\")"},"is_html":{"type":"boolean","default":false,"description":"Whether the message is HTML formatted (email only)"},"to":{"type":"string","format":"email","description":"Override recipient email address (email only, auto-selected if not provided)"},"cc":{"type":"array","items":{"type":"string","format":"email"},"description":"CC recipients (email only)"},"bcc":{"type":"array","items":{"type":"string","format":"email"},"description":"BCC recipients (email only)"},"attachments":{"type":"array","items":{"type":"object"},"description":"Email attachments (email only)"}}},"examples":{"email_message":{"summary":"Send email reply","value":{"message":"Thank you for your interest! I'd love to schedule a call.","sender_id":"123e4567-e89b-12d3-a456-426614174000","subject":"Re: Partnership Opportunity","is_html":false}},"linkedin_message":{"summary":"Send LinkedIn message","value":{"message":"Thanks for connecting! Would love to chat about your work."}}}}}},"responses":{"201":{"description":"Message queued for delivery","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendMessageResponse"},"examples":{"email_response":{"summary":"Email message response","value":{"success":true,"data":{"object":"message","id":"msg_abc123","text":"Thank you for your interest!","subject":"Re: Partnership Opportunity","timestamp":"2026-01-28T12:00:00.000Z","is_from_self":true,"is_read":true,"status":"pending","job_id":"job_xyz789","conversation_id":"conv_123","platform_type":"email","sender":{"id":"123e4567-e89b-12d3-a456-426614174000","email":"sales@company.com"}},"request_id":"req_abc123"}},"linkedin_response":{"summary":"LinkedIn message response","value":{"success":true,"data":{"object":"message","id":"msg_abc123","text":"Thanks for connecting!","timestamp":"2026-01-28T12:00:00.000Z","is_from_self":true,"is_read":true,"status":"pending","job_id":"job_xyz789","conversation_id":"conv_123","platform_type":"linkedin"},"request_id":"req_abc123"}}}}}},"400":{"description":"Bad request (missing sender_id for email, etc.)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing_sender":{"summary":"Missing sender_id for email","value":{"error":{"type":"invalid_request_error","code":"missing_required_parameter","message":"sender_id is required for email conversations","param":"sender_id","doc_url":"https://docs.supersend.io/docs/errors#missing_required_parameter"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/conversations/{id}/link-clicks":{"get":{"tags":["Conversations"],"summary":"Get Conversation Link Clicks","description":"Returns inbox reply link click events grouped by outbound message ID.","operationId":"getConversationLinkClicks","parameters":[{"$ref":"#/components/parameters/IdPath"}],"responses":{"200":{"description":"Link clicks grouped by message","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationLinkClicksResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/conversations/{id}/messages/{messageId}/link-clicks":{"get":{"tags":["Conversations"],"summary":"Get Message Link Clicks","description":"Returns inbox reply link click events for a single outbound conversation message.","operationId":"getConversationMessageLinkClicks","parameters":[{"$ref":"#/components/parameters/IdPath"},{"name":"messageId","in":"path","required":true,"description":"Conversation message ID","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Link clicks for the message","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageLinkClicksResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/devices":{"post":{"tags":["Devices"],"summary":"Register Device","description":"Register this phone for push notifications about new replies (SuperSend iPhone app).\nRegistering a token again updates it; a token signed in as another user moves to that\nuser. With team_id the phone hears about that team only; without it, about every team\nyou can see in this API key's organization.\n","operationId":"registerDevice","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token","platform","environment"],"properties":{"token":{"type":"string","description":"APNs device token (hex)","pattern":"^[0-9a-fA-F]{64,200}$"},"platform":{"type":"string","enum":["ios"]},"environment":{"type":"string","enum":["sandbox","production"],"description":"sandbox for development builds, production for TestFlight and App Store builds"},"app_version":{"type":"string","maxLength":64},"team_id":{"type":"string","format":"uuid","nullable":true,"description":"A team you belong to; omit or null for all your teams"}}}}}},"responses":{"200":{"description":"Device registered","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Device"},"request_id":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/devices/{token}":{"delete":{"tags":["Devices"],"summary":"Unregister Device","description":"Stop push notifications to this phone (for example on sign-out). Only your own registration is removed; repeating the call is harmless.","operationId":"deleteDevice","parameters":[{"name":"token","in":"path","required":true,"description":"APNs device token (hex)","schema":{"type":"string"}}],"responses":{"200":{"description":"Device unregistered","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"object":{"type":"string","enum":["device"]},"deleted":{"type":"boolean"}}},"request_id":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/account/deletion-request":{"post":{"tags":["Account"],"summary":"Request Account Deletion","description":"Ask SuperSend to delete your account and its data. Nothing is deleted right away: support\ndeletes the account within 7 days and emails you when it's done. Asking again within 24\nhours answers the same and doesn't send a second request. Support API keys can't use this.\n","operationId":"requestAccountDeletion","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","maxLength":1000,"description":"Optional reason for leaving"}}}}}},"responses":{"200":{"description":"Request received","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"object":{"type":"string","enum":["account_deletion_request"]},"status":{"type":"string","enum":["received"]},"message":{"type":"string","example":"We'll delete your account within 7 days and email you when it's done."}}},"request_id":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}}},"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Key","description":"API key from your SuperSend dashboard"}},"parameters":{"IdPath":{"name":"id","in":"path","required":true,"description":"Resource ID (UUID)","schema":{"type":"string","format":"uuid"}},"TeamIdQuery":{"name":"TeamId","in":"query","description":"Filter by team ID","schema":{"type":"string","format":"uuid"}},"CampaignIdQuery":{"name":"CampaignId","in":"query","description":"Filter by campaign ID","schema":{"type":"string","format":"uuid"}},"LimitParam":{"name":"limit","in":"query","description":"Number of items to return","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},"OffsetParam":{"name":"offset","in":"query","description":"Number of items to skip","schema":{"type":"integer","minimum":0,"default":0}}},"responses":{"BadRequest":{"description":"Bad Request - Invalid parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"type":"invalid_request_error","code":"missing_required_parameter","message":"CampaignId is required","param":"CampaignId","doc_url":"https://docs.supersend.io/errors#missing_required_parameter"},"request_id":"req_abc123def456"}}}},"Unauthorized":{"description":"Unauthorized - Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"type":"authentication_error","code":"invalid_api_key","message":"Invalid API key provided","doc_url":"https://docs.supersend.io/errors#invalid_api_key"},"request_id":"req_abc123def456"}}}},"Forbidden":{"description":"Forbidden - Access denied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"type":"authorization_error","code":"team_access_denied","message":"You do not have access to this team","doc_url":"https://docs.supersend.io/errors#team_access_denied"},"request_id":"req_abc123def456"}}}},"NotFound":{"description":"Not Found - Resource does not exist","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"type":"not_found_error","code":"contact_not_found","message":"Contact not found","doc_url":"https://docs.supersend.io/errors#contact_not_found"},"request_id":"req_abc123def456"}}}},"RateLimited":{"description":"Rate limit exceeded","headers":{"Retry-After":{"description":"Seconds to wait before retrying","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Request limit per minute","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Remaining requests this minute","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"schemas":{"SuccessResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true},"request_id":{"type":"string","example":"req_abc123def456"}}},"MessageResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"message":{"type":"string"}}}]},"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"object","properties":{"type":{"type":"string","enum":["invalid_request_error","authentication_error","authorization_error","not_found_error","payment_required_error","rate_limit_error","api_error"]},"code":{"type":"string"},"message":{"type":"string"},"param":{"type":"string","nullable":true},"doc_url":{"type":"string","format":"uri"}}},"request_id":{"type":"string"}}},"Pagination":{"type":"object","properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"},"has_more":{"type":"boolean"}}},"HealthResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"status":{"type":"string","enum":["healthy"]},"version":{"type":"string"},"timestamp":{"type":"string","format":"date-time"},"uptime":{"type":"number"}}}}}]},"AnnouncementsResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"announcements":{"type":"array","items":{"$ref":"#/components/schemas/Announcement"}}}}}}]},"Announcement":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"source":{"type":"string","enum":["manual","crisp"]},"title":{"type":"string"},"message":{"type":"string"},"link":{"type":"string"},"severity":{"type":"string","enum":["info","warning","error"]},"audience":{"type":"string","enum":["all","supersend_esp"],"description":"Who should see the banner. `supersend_esp` is returned only when the request is authenticated and the org has an active SuperSend ESP sender.\n"},"dismissible":{"type":"boolean","description":"When false, the in-app banner must not offer a dismiss control.\n","default":true}}},"EmailValidationVerifyRequest":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email","description":"Address to verify"},"TeamId":{"type":"string","format":"uuid","description":"Optional team UUID for credit transaction attribution"}}},"EmailValidationVerifyResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","required":["email","valid","validators","credits_used"],"properties":{"email":{"type":"string","format":"email"},"valid":{"type":"boolean","description":"Whether the SMTP probe considers the mailbox deliverable"},"validators":{"type":"object","description":"Raw validator output (e.g. smtp)","additionalProperties":true},"credits_used":{"type":"integer","description":"Global credits debited for this call (0 if org cost is configured as 0)"},"email_hash":{"type":"string","description":"SHA-256 hash of the normalised email address"},"domain":{"type":"string","description":"Domain portion of the email address"},"is_role_based":{"type":"boolean","description":"True if the address is a role-based alias (info@, support@, etc.)"},"is_abuse_email":{"type":"boolean","description":"True if the address is associated with abuse or spam"},"is_free_provider":{"type":"boolean","description":"True if the domain is a free email provider (gmail, yahoo, etc.)"},"is_disallowed":{"type":"boolean","description":"True if the address is on a global disallow list (disposable, etc.)"},"validity_score":{"type":"integer","minimum":0,"maximum":100,"description":"Aggregate deliverability score (0 = worst, 100 = best)"},"risk_level":{"type":"string","enum":["LOW","MEDIUM","HIGH","CRITICAL"],"description":"Risk categorisation derived from validity_score"},"confidence":{"type":"string","enum":["NONE","LOW","MEDIUM","HIGH"],"description":"Confidence level based on historical send volume"},"email_provider":{"type":"string","nullable":true,"description":"Detected ESP or mailbox provider (e.g. Google Workspace, Microsoft 365)"},"email_security_service":{"type":"string","nullable":true,"description":"Detected email security gateway (e.g. Proofpoint, Mimecast)"},"bounce_type_breakdown":{"type":"object","nullable":true,"description":"Historical breakdown of bounce types seen across the SuperSend network","additionalProperties":true},"total_sends":{"type":"integer","description":"Total sends observed across the SuperSend network for this address"},"hard_bounce_count":{"type":"integer","description":"Hard bounces observed across the network"},"reply_count":{"type":"integer","description":"Replies observed across the network"},"has_historical_data":{"type":"boolean","description":"Whether historical delivery data exists for this address"}}},"request_id":{"type":"string"}}},"AssociatedCampaignEnrollment":{"type":"object","description":"One **campaign enrollment** (`Contact` row) for a **ContactProfile**, as returned on team profile detail\n(`GET https://api.supersend.io/v1/contact/profile/{profileId}?TeamId=`). Ordered by **`contactCreatedAt`**\ndescending (newest enrollment first). Same Bearer token as V2.\n","properties":{"id":{"type":"string","format":"uuid","description":"Campaign ID"},"name":{"type":"string","description":"Campaign name"},"ContactId":{"type":"string","format":"uuid","description":"Enrollment contact row ID"},"contactCreatedAt":{"type":"string","format":"date-time","nullable":true,"description":"When the enrollment `Contact` row was created (sort key for the list)"},"replied":{"type":"boolean"},"bounced":{"type":"boolean"},"unsubscribed":{"type":"boolean"},"opened":{"type":"boolean"},"clicked":{"type":"boolean"},"finished":{"type":"boolean"},"appointment":{"type":"boolean"},"interest":{"type":"string","nullable":true},"status":{"type":"string","nullable":true,"description":"Queue status e.g. idle, queued, processing"},"next_step":{"type":"integer"},"paused_at":{"type":"string","format":"date-time","nullable":true},"resume_at":{"type":"string","format":"date-time","nullable":true},"failed":{"type":"boolean"},"reason":{"type":"string"},"last_node_processed_at":{"type":"string","format":"date-time","nullable":true}}},"Contact":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email"},"first_name":{"type":"string"},"last_name":{"type":"string"},"company":{"type":"string"},"title":{"type":"string"},"linkedin_url":{"type":"string","format":"uri"},"phone":{"type":"string"},"website":{"type":"string"},"interest":{"type":"string","description":"Contact status - interested, not_interested, meeting_request, meeting_booked, customer, future, follow_up"},"finished":{"type":"boolean"},"deleted":{"type":"boolean"},"custom":{"type":"object","additionalProperties":true},"team_id":{"type":"string","format":"uuid"},"campaign_id":{"type":"string","format":"uuid"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"CreateContactRequest":{"type":"object","required":["TeamId"],"description":"At least one of `email` or `linkedin_url` is required.\nWhen `contact_profile_id` is set, `CampaignId` is required (enroll from existing profile).\nWhen `CampaignId` is omitted, null, or empty, only profile + team membership are upserted (no enrollment).\nWhen `CampaignId` is set, profile + team membership are upserted before the enrollment row.\nOptional `labels` attaches existing contact-profile labels by name (unknown names ignored).\n","properties":{"email":{"type":"string","format":"email"},"linkedin_url":{"type":"string","format":"uri"},"first_name":{"type":"string"},"last_name":{"type":"string"},"company":{"type":"string"},"title":{"type":"string"},"phone":{"type":"string"},"website":{"type":"string"},"custom":{"type":"object","additionalProperties":true},"TeamId":{"type":"string","format":"uuid"},"CampaignId":{"type":"string","format":"uuid","nullable":true,"description":"Omit or null for profile + ContactTeam only (no Contact enrollment)."},"contact_profile_id":{"type":"string","format":"uuid","description":"When enrolling into a campaign, optionally reference an existing contact profile. Identity fields\nare taken from the profile; the profile must have team membership for the campaign's team.\n"},"validate_emails":{"type":"boolean","description":"When true, runs email verification when the contact profile is unvalidated or pending\n(`valid2` -1 or 0). Already-validated profiles are skipped (no extra credits). Default is\nfalse to avoid surprise billing. Applies on create, update, and re-enroll. Not applied on\nthe profile-only path (omit CampaignId).\n"},"labels":{"type":"array","items":{"type":"string"},"description":"Optional display names of labels to attach to the **contact profile**. Only labels that\nalready exist for the org/team and apply to contacts (`applies_to` contact or both) are\nlinked; other entries are ignored. Matching is case-insensitive (same rules as CSV import).\n"}}},"TeamContactProfile":{"type":"object","description":"Team-scoped contact profile without a campaign enrollment (`POST /contacts` with no CampaignId)","properties":{"object":{"type":"string","enum":["contact_profile"]},"id":{"type":"string","format":"uuid"},"org_id":{"type":"string","format":"uuid"},"team_id":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email","nullable":true},"linkedin_url":{"type":"string","format":"uri","nullable":true},"first_name":{"type":"string","nullable":true},"last_name":{"type":"string","nullable":true},"image":{"type":"string","nullable":true},"title":{"type":"string","nullable":true},"phone":{"type":"string","nullable":true},"company_name":{"type":"string","nullable":true},"industry":{"type":"string","nullable":true},"note":{"type":"string","nullable":true},"company_url":{"type":"string","nullable":true},"one_liner":{"type":"string","nullable":true},"one_liner_2":{"type":"string","nullable":true},"one_liner_3":{"type":"string","nullable":true},"selected_one_liner":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"state":{"type":"string","nullable":true},"country":{"type":"string","nullable":true},"integration_contact_id":{"type":"string","nullable":true},"custom":{"type":"object","additionalProperties":true,"nullable":true},"validated":{"type":"boolean","nullable":true},"valid":{"type":"boolean","nullable":true},"valid2":{"type":"integer","nullable":true},"validation_subtype":{"type":"string","nullable":true,"description":"SMTP validation classifier when the address is risky or invalid (e.g. `catch_all`, `unknown_smtp`,\n`timeout`, `mailbox_not_found`, `greylisted`, `probe_blocked`, `mailbox_full`, `disposable`, legacy `invalid_mailbox`).\nOmitted or null when valid (`valid2` 1) or not classified.\n"},"verified":{"type":"boolean","nullable":true},"enrolled":{"type":"boolean","description":"Always false for this shape (no Contact row)"},"associated_campaigns":{"type":"array","description":"Present on **GET** team contact profile only (`GET /v1/contact/profile/{profileId}?TeamId=`).\nCampaign enrollments linked to this profile; not returned on profile-only **POST /v2/contacts** create.\n","items":{"$ref":"#/components/schemas/AssociatedCampaignEnrollment"}}}},"TeamContactProfileCreatedResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/TeamContactProfile"}}}]},"UpdateContactRequest":{"type":"object","properties":{"first_name":{"type":"string"},"last_name":{"type":"string"},"company":{"type":"string"},"title":{"type":"string"},"phone":{"type":"string"},"website":{"type":"string"},"interest":{"type":"string","description":"Contact status - interested, not_interested, meeting_request, meeting_booked, customer, future, follow_up"},"custom":{"type":"object","additionalProperties":true}}},"ContactProfileLabelItem":{"type":"object","description":"A label linked to the contact's org-scoped profile for one team","properties":{"object":{"type":"string","example":"contact_profile_label"},"id":{"type":"string","format":"uuid","description":"ContactProfileLabels row id"},"contact_profile_id":{"type":"string","format":"uuid"},"label_id":{"type":"string","format":"uuid"},"team_id":{"type":"string","format":"uuid","description":"Team that owns this assignment (junction `TeamId`; matches the contact's team for this list)"},"org_id":{"type":"string","format":"uuid","description":"Org for this assignment (junction `OrgId`)"},"applies_to":{"type":"string","enum":["conversation","contact","both"],"description":"Copied from the Label definition"},"label_team_id":{"type":"string","format":"uuid","nullable":true,"description":"Team that owns the label definition; null for legacy org-wide labels"},"name":{"type":"string"},"color":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"ContactProfileLabelAssignment":{"type":"object","properties":{"object":{"type":"string","example":"contact_profile_label"},"id":{"type":"string","format":"uuid"},"contact_profile_id":{"type":"string","format":"uuid"},"label_id":{"type":"string","format":"uuid"},"team_id":{"type":"string","format":"uuid","description":"Team context for this assignment (contact's team)"},"org_id":{"type":"string","format":"uuid"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"ContactResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Contact"}}}]},"ContactListResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}]},"Device":{"type":"object","properties":{"object":{"type":"string","enum":["device"]},"id":{"type":"string","format":"uuid"},"token":{"type":"string"},"environment":{"type":"string","enum":["sandbox","production"]},"team_id":{"type":"string","format":"uuid","nullable":true}}},"ConversationMessage":{"type":"object","properties":{"object":{"type":"string","enum":["message"]},"id":{"type":"string","format":"uuid"},"text":{"type":"string","nullable":true},"html":{"type":"string","nullable":true,"description":"The email's HTML body as stored. null for LinkedIn messages, emails without an HTML part, and bodies over 500 KB (see html_truncated)."},"html_truncated":{"type":"boolean","description":"Present and true when the HTML body was over 500 KB and left out (html is null)."},"subject":{"type":"string","nullable":true},"timestamp":{"type":"string","format":"date-time"},"is_from_self":{"type":"boolean"},"is_read":{"type":"boolean"},"status":{"type":"string","enum":["delivered","pending","failed"]},"job_id":{"type":"string","nullable":true},"conversation_id":{"type":"string","format":"uuid"},"attachments":{"type":"array","description":"Email file attachments linked to this message (inbound sync, outbound inbox reply, or campaign send snapshots including custom attachment variables), when present","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"filename":{"type":"string"},"file_path":{"type":"string","description":"Storage object key for download via org-scoped APIs"},"cloud_url":{"type":"string","format":"uri","nullable":true,"description":"Signed download link for the file, valid for one hour. Fetch the messages again for a fresh link."}}}},"platform_type":{"type":"string","enum":["email","linkedin"]},"sender":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email"}}}}},"SendMessageResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ConversationMessage"}}}]},"ConversationParticipant":{"type":"object","description":"A participant in a conversation (from ConversationParticipants + PlatformPersonProfile)","properties":{"id":{"type":"string","format":"uuid","nullable":true,"description":"Platform person profile ID (null if profile unavailable)"},"display_name":{"type":"string","nullable":true,"description":"Display name for the participant"},"avatar_url":{"type":"string","format":"uri","nullable":true,"description":"Avatar/profile image URL"},"username":{"type":"string","nullable":true,"description":"Email address (email) or platform handle (LinkedIn)"},"is_self":{"type":"boolean","description":"Whether this participant is the authenticated user/team member"}}},"Conversation":{"type":"object","description":"Conversation object in list/detail responses","properties":{"object":{"type":"string","enum":["conversation"]},"id":{"type":"string","format":"uuid"},"platform_type":{"type":"string","enum":["email","linkedin"]},"title":{"type":"string"},"last_activity_at":{"type":"string","format":"date-time"},"is_unread":{"type":"boolean"},"is_archived":{"type":"boolean"},"unread_count":{"type":"integer"},"identity":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"handle":{"type":"string"},"first_name":{"type":"string"},"last_name":{"type":"string"},"photo":{"type":"string","format":"uri"},"type":{"type":"string","enum":["linkedin"]}}},"participants":{"type":"array","description":"Participants in the conversation (populated in GET /conversations/{id}; empty in list)","items":{"$ref":"#/components/schemas/ConversationParticipant"}},"last_message":{"type":"object","nullable":true,"properties":{"text":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}}},"team_id":{"type":"string","format":"uuid"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"ConversationListResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Conversation"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}]},"ConversationResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"allOf":[{"$ref":"#/components/schemas/Conversation"},{"type":"object","properties":{"messages":{"type":"array","items":{"$ref":"#/components/schemas/ConversationMessage"}}}}]}}}]},"ConversationUpdateResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","enum":["conversation"]},"id":{"type":"string","format":"uuid"},"is_unread":{"type":"boolean"},"is_archived":{"type":"boolean"},"updated_at":{"type":"string","format":"date-time"}}}}}]},"MessageListResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ConversationMessage"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}]},"BulkContactRequest":{"type":"object","required":["contacts","TeamId","CampaignId"],"properties":{"contacts":{"type":"array","description":"Array of contacts to import (max 50,000)","minItems":1,"maxItems":50000,"items":{"type":"object","properties":{"email":{"type":"string","format":"email"},"linkedin_url":{"type":"string","format":"uri"},"first_name":{"type":"string"},"last_name":{"type":"string"},"company_name":{"type":"string"},"title":{"type":"string"},"phone":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"country":{"type":"string"},"industry":{"type":"string"},"company_url":{"type":"string","format":"uri"},"note":{"type":"string"},"one_liner":{"type":"string"},"custom":{"type":"object","additionalProperties":true}}}},"TeamId":{"type":"string","format":"uuid","description":"Team ID"},"CampaignId":{"type":"string","format":"uuid","description":"Campaign ID"},"validate_emails":{"type":"boolean","default":false,"description":"When true, queues email verification for contacts in the import (consumes credits). Default false\nto avoid surprise billing. Single-contact upsert via POST /contacts skips already-validated profiles.\n"}}},"BulkContactResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"upload_id":{"type":"string","format":"uuid","description":"Unique ID to track this upload"},"status":{"type":"string","enum":["pending","processing","completed","failed"],"description":"Current processing status"},"contact_count":{"type":"integer","description":"Number of contacts in the upload"},"message":{"type":"string","description":"Status message"}}}}}]},"BulkContactActionRequest":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["get_contacts","verify_contacts","unverify_contacts","verify_passed_email_validation","unverify_failed_email_validation","restart","finish","unfinish","pause","pause_failed_validation","resume","archive_unverified","archive_all","unarchive","transfer","reassign_active_sender","validate_emails","assign_step"],"description":"Bulk action to perform on selected contacts"},"campaign_id":{"type":"string","format":"uuid","description":"Source campaign UUID (required for assign_step; recommended for finish/resume/restart)"},"to_campaign_id":{"type":"string","format":"uuid","description":"Target campaign UUID (required when action is transfer)"},"node_id":{"type":"string","description":"Flow node UUID (required when action is assign_step)"},"step_number":{"type":"integer","minimum":0,"description":"Sequence step number (required when action is assign_step)"},"contact_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Explicit contact enrollment IDs to act on"},"exclude_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Select-all mode — exclude these contact IDs"},"filters":{"type":"object","additionalProperties":true,"nullable":true,"description":"V1-style contact list filters (team/campaign scope, status, labels, etc.)"},"revalidate":{"type":"boolean","description":"When action is validate_emails, re-validate already validated contacts"},"selection_limit":{"type":"integer","minimum":1,"description":"Select-all mode — max contacts to include (requires exclude_ids or empty selection)"},"selection_offset":{"type":"integer","minimum":0,"description":"Select-all mode — skip first N matching contacts"},"sort_by":{"type":"string","description":"Sort order for select-all mode (same values as GET /contacts sort_by)"}}},"BulkContactActionResponse":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/MessageResponse"}]},{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"message":{"type":"string"},"count":{"type":"integer","description":"Number of contacts affected"},"queued":{"type":"boolean","description":"True when the action was queued for background processing"},"batch_id":{"type":"string","format":"uuid","description":"Batch ID for queued actions (e.g. transfer)"},"contacts":{"type":"array","description":"Present when action is get_contacts","items":{"$ref":"#/components/schemas/Contact"}},"truncated":{"type":"boolean","description":"True when selection was capped to the per-request limit"},"cap_message":{"type":"string","description":"Additional cap notice appended to message"},"restored":{"type":"integer","description":"unarchive — contacts restored"},"skipped":{"type":"integer","description":"unarchive — contacts not restored because the same person is already active in that campaign"}}}}}]}]},"ContactMetrics":{"type":"object","description":"Contact lifecycle metrics for a campaign","properties":{"total":{"type":"integer","description":"Total non-archived contacts in campaign"},"not_started":{"type":"integer","description":"Contacts that haven't begun the sequence yet (in queue for auto-refill)"},"in_progress":{"type":"integer","description":"Contacts currently progressing through the sequence"},"finished":{"type":"integer","description":"Contacts that completed the sequence"},"paused":{"type":"integer","description":"Contacts currently paused"}}},"Campaign":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"status":{"type":"string","enum":["active","inactive"]},"disabled":{"type":"boolean"},"track_domain":{"type":"string","description":"Custom tracking domain for links"},"track_domain_setup_complete":{"type":"boolean","description":"Whether tracking domain DNS is configured"},"track_domain_status":{"type":"string","description":"Tracking domain setup status message"},"track":{"type":"boolean","description":"Enable open and click tracking. When false, no pixels or link rewrites; replies/bounces still tracked."},"contact_count":{"type":"integer","description":"Total non-archived contacts (same as contact_metrics.total)"},"contact_metrics":{"$ref":"#/components/schemas/ContactMetrics"},"team_id":{"type":"string","format":"uuid"},"send_to_risk_levels":{"type":"array","nullable":true,"description":"List Health levels to send to (SAFE, LOW, MEDIUM, HIGH, INVALID). On create, omitted defaults to SAFE/LOW/MEDIUM/HIGH (Invalid excluded). null = send to all. Contacts pending validation are always included.","items":{"type":"string","enum":["SAFE","LOW","MEDIUM","HIGH","INVALID"]}},"include_catch_all":{"type":"boolean","description":"When false, catch-all contacts are skipped for email sends regardless of List Health risk level. Omitted or true = include catch-all addresses (default)."},"max_per_day":{"type":"integer","nullable":true,"minimum":1,"maximum":100000,"description":"Max emails per day from all senders in campaign. null = no campaign limit. Senders respect their own daily limits; this adds an additional ceiling."},"bcc":{"type":"array","items":{"type":"string","format":"email"},"description":"BCC email addresses for campaign emails"},"bcc_replies_only":{"type":"boolean","description":"When true, BCC only applies to reply emails"},"timezone":{"type":"string","example":"America/Los_Angeles","description":"Campaign schedule timezone"},"days":{"type":"object","description":"Days of week to send (monday, tuesday, etc.)","additionalProperties":{"type":"boolean"}},"hours":{"type":"array","items":{"type":"object","properties":{"start":{"type":"string","pattern":"^\\d{2}:\\d{2}$","example":"09:00"},"end":{"type":"string","pattern":"^\\d{2}:\\d{2}$","example":"17:00"}}},"description":"Sending hours windows"},"start":{"type":"string","format":"date-time","nullable":true,"description":"Campaign start date"},"end":{"type":"string","format":"date-time","nullable":true,"description":"Campaign end date"},"unsubscribe":{"type":"boolean","description":"Enable opt-out link in emails"},"unsubscribe_message":{"type":"string","description":"Keyword to detect opt-out in replies"},"blacklistIfUnsubscribe":{"type":"boolean","description":"Add contact to blacklist when they opt out"},"blacklistIfBounced":{"type":"boolean","description":"Add contact to blacklist when email bounces"},"blacklistDomainOnReply":{"type":"boolean","description":"Blacklist contact domain when reply received"},"list_unsubscribe_header":{"type":"boolean","description":"Add List-Unsubscribe header to emails"},"failureConfiguration":{"type":"object","properties":{"strategy":{"type":"string","enum":["skip","retry","pause_contact","pause_campaign"],"description":"How to handle sequence step failures"},"retryAfterDays":{"type":"integer","nullable":true,"description":"Days before retry (required when strategy is retry)"},"notification":{"type":"boolean","description":"Send notification on failure"}}},"bounce_checkpoint_at":{"type":"string","format":"date-time","nullable":true,"description":"Start of the monitoring window for invalid-recipient bounce rate (set when the campaign\nis started after pause, or when contacts are restarted). Email events before this time\nare excluded from the automated bounce-rate check.\n"},"bounce_guard_max_rate":{"type":"number","format":"float","minimum":0.01,"maximum":1,"default":0.03,"description":"Invalid-recipient bounce rate auto-pause threshold for this campaign.\nValue is a decimal between 0.01 (1%) and 1.0 (100%). Default is 0.03 (3%).\nThe campaign is automatically paused when the measured invalid-recipient\nbounce rate exceeds this value.\n"},"latest_lifecycle_event":{"$ref":"#/components/schemas/CampaignLatestLifecycleEvent"},"parent_campaign_id":{"type":"string","format":"uuid","nullable":true,"description":"Parent campaign UUID when this campaign is a subsequence; null for top-level campaigns."},"subsequence_settings":{"allOf":[{"$ref":"#/components/schemas/SubsequenceSettings"}],"nullable":true,"description":"Subsequences only (null for top-level campaigns)."},"reply_link_clicks":{"type":"integer","description":"Count of unique `(contact, UTC calendar day)` inbox reply link clicks attributed\nto this campaign. Distinct from sequence-step `clicks` (campaign email link clicks).\n","example":12},"is_draft":{"type":"boolean","description":"When true, campaign setup is incomplete (UI shows Finish Campaign)"},"version":{"type":"number","enum":[1,2,3],"description":"Sequence builder version (3 = simple, 2 = complex)"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"CampaignLatestLifecycleEvent":{"type":"object","nullable":true,"description":"Most recent start/pause (or other) lifecycle row for banner copy; full history can be added later.","properties":{"event_type":{"type":"string","enum":["started","paused"]},"reason_code":{"type":"string","nullable":true,"description":"Examples: user (manual pause/start), invalid_recipient_bounce_rate, schedule_no_email_senders,\nbilling_email_limit, failure_config_pause_campaign, org_deactivated, team_deleted,\nsmtp_subject_syntax_error, linkedin_no_sender_account, and others\nemitted by `recordCampaignLifecycleEvent` / `safeRecordCampaignPaused`.\n"},"occurred_at":{"type":"string","format":"date-time"},"actor":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"email":{"type":"string","format":"email"}}},"metadata":{"type":"object","nullable":true}}},"CreateCampaignRequest":{"type":"object","required":["name","TeamId"],"properties":{"name":{"type":"string","maxLength":255},"TeamId":{"type":"string","format":"uuid"},"PlaybookId":{"type":"string","format":"uuid","nullable":true,"description":"Optional playbook to link this campaign to"},"track_domain":{"type":"string","description":"Tracking domain for links (team default used if omitted)"},"nodes":{"type":"array","description":"Initial sequence nodes (startNode, emailNode, waitNode, etc.)"},"edges":{"type":"array","description":"Initial sequence edges (connections between nodes)"},"is_draft":{"type":"boolean","default":true},"version":{"type":"number","enum":[1,2,3],"default":3,"description":"Sequence builder version (3 = simple linear, 2 = complex branching)"},"custom_variables":{"type":"array","description":"Custom variable definitions (name, type) for contact fields","items":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"type":{"type":"string","enum":["text","boolean"],"default":"text"}}}},"templateId":{"type":"string","format":"uuid","description":"Template used to create this campaign"},"parentCampaignId":{"type":"string","format":"uuid","description":"Optional parent campaign UUID to create a subsequence.\nMax 3 subsequences per parent; cannot nest subsequences.\n"},"track":{"type":"boolean","description":"Enable open and click tracking. When false, no pixels or link rewrites."},"timezone":{"type":"string","example":"America/New_York"},"days":{"type":"object","additionalProperties":{"type":"boolean"},"description":"Days of week to send"},"hours":{"type":"array","items":{"type":"object","properties":{"start":{"type":"string","pattern":"^\\d{2}:\\d{2}$"},"end":{"type":"string","pattern":"^\\d{2}:\\d{2}$"}}},"description":"Sending hours windows"},"start":{"type":"string","format":"date-time","nullable":true},"end":{"type":"string","format":"date-time","nullable":true},"bcc":{"type":"array","items":{"type":"string","format":"email"},"nullable":true},"bcc_replies_only":{"type":"boolean"},"unsubscribe":{"type":"boolean"},"unsubscribe_message":{"type":"string","nullable":true},"blacklistIfUnsubscribe":{"type":"boolean","default":true},"blacklistIfBounced":{"type":"boolean","default":true},"blacklistDomainOnReply":{"type":"boolean","default":true},"list_unsubscribe_header":{"type":"boolean"},"failureConfiguration":{"type":"object","properties":{"strategy":{"type":"string","enum":["skip","retry","pause_contact","pause_campaign"]},"retryAfterDays":{"type":"integer","nullable":true},"notification":{"type":"boolean"}}},"send_to_risk_levels":{"type":"array","nullable":true,"description":"List Health levels to send to (SAFE, LOW, MEDIUM, HIGH, INVALID). On create, omitted defaults to SAFE/LOW/MEDIUM/HIGH (Invalid excluded). null = send to all. Contacts pending validation are always included.","items":{"type":"string","enum":["SAFE","LOW","MEDIUM","HIGH","INVALID"]}},"include_catch_all":{"type":"boolean","description":"When false, catch-all contacts are skipped for email sends. Default true when omitted."},"bounce_guard_max_rate":{"type":"number","format":"float","minimum":0.01,"maximum":1,"default":0.03,"description":"Invalid-recipient bounce rate auto-pause threshold (0.01–1.0). Default 0.03 (3%).\n"}}},"UpdateCampaignRequest":{"type":"object","properties":{"name":{"type":"string","maxLength":255},"PlaybookId":{"type":"string","format":"uuid","nullable":true,"description":"Optional playbook to link this campaign to"},"track_domain":{"type":"string"},"track":{"type":"boolean","description":"Enable open and click tracking"},"nodes":{"type":"array","description":"Campaign sequence nodes (cannot modify while active)"},"edges":{"type":"array","description":"Campaign sequence edges (cannot modify while active)"},"status":{"type":"number","enum":[1,2],"description":"1=ACTIVE, 2=INACTIVE"},"is_draft":{"type":"boolean"},"version":{"type":"number","enum":[1,2,3],"description":"Sequence builder version (3 = simple, 2 = complex)"},"timezone":{"type":"string"},"days":{"type":"object","additionalProperties":{"type":"boolean"}},"hours":{"type":"array","items":{"type":"object","properties":{"start":{"type":"string","pattern":"^\\d{2}:\\d{2}$"},"end":{"type":"string","pattern":"^\\d{2}:\\d{2}$"}}}},"start":{"type":"string","format":"date-time","nullable":true},"end":{"type":"string","format":"date-time","nullable":true},"bcc":{"type":"array","items":{"type":"string","format":"email"},"nullable":true},"bcc_replies_only":{"type":"boolean"},"unsubscribe":{"type":"boolean"},"unsubscribe_message":{"type":"string","nullable":true},"blacklistIfUnsubscribe":{"type":"boolean"},"blacklistIfBounced":{"type":"boolean"},"blacklistDomainOnReply":{"type":"boolean"},"list_unsubscribe_header":{"type":"boolean"},"failureConfiguration":{"type":"object","properties":{"strategy":{"type":"string","enum":["skip","retry","pause_contact","pause_campaign"]},"retryAfterDays":{"type":"integer","nullable":true},"notification":{"type":"boolean"}}},"send_to_risk_levels":{"type":"array","nullable":true,"description":"List Health levels to send to (SAFE, LOW, MEDIUM, HIGH, INVALID). On create, omitted defaults to SAFE/LOW/MEDIUM/HIGH (Invalid excluded). null = send to all. Contacts pending validation are always included.","items":{"type":"string","enum":["SAFE","LOW","MEDIUM","HIGH","INVALID"]}},"include_catch_all":{"type":"boolean","description":"When false, catch-all contacts are skipped for email sends. Default true when omitted."},"max_per_day":{"type":"integer","nullable":true,"minimum":1,"maximum":100000,"description":"Max emails per day from all senders in campaign. null = no campaign limit. Senders respect their own daily limits; this adds an additional ceiling."},"bounce_guard_max_rate":{"type":"number","format":"float","minimum":0.01,"maximum":1,"default":0.03,"description":"Invalid-recipient bounce rate auto-pause threshold (0.01–1.0). Default 0.03 (3%).\n"},"subsequence_settings":{"$ref":"#/components/schemas/SubsequenceSettings"}}},"CampaignResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"message":{"type":"string","description":"Present when some fields were not changed, e.g. settings a subsequence takes from its parent campaign."},"data":{"$ref":"#/components/schemas/Campaign"}}}]},"CampaignListResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Campaign"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}]},"CampaignSubsequencesResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"parentCampaign":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"subsequences":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"status":{"type":"integer"},"contact_count":{"type":"integer"},"contacted_count":{"type":"integer"},"replies":{"type":"integer"},"active_count":{"type":"integer","description":"Contacts still going through the subsequence (not finished)."},"entry_delay":{"type":"object","nullable":true,"description":"The wait before the first step after a contact enters; null when the first step isn't a wait.","properties":{"wait":{"type":"integer","example":3},"unit":{"type":"string","enum":["minutes","hours","days"],"example":"days"}}},"subsequence_entry_conditions":{"type":"object","description":"Who enters (OR rules)."},"subsequence_settings":{"$ref":"#/components/schemas/SubsequenceSettings"}}}},"count":{"type":"integer"}}}}}]},"SubsequenceSettings":{"type":"object","description":"Subsequences only. A subsequence follows its parent campaign's settings (tracking, sending\nrules, opt-outs and sender profiles); setting those on a subsequence is ignored, and the\nresponse's `message` lists the fields that were not changed. The schedule\n(`days`, `hours`, `timezone`) follows the parent too unless `schedule` is `custom`: editing\na subsequence's schedule sets `custom`, and setting `parent` copies the parent's schedule back.\n","properties":{"schedule":{"type":"string","enum":["parent","custom"],"default":"parent"}}},"CampaignParentResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"campaign":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"parentCampaignId":{"type":"string","format":"uuid","nullable":true}}},"parentCampaign":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"status":{"type":"integer"}}},"isSubsequence":{"type":"boolean"}}}}}]},"Team":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"domain":{"type":"string"},"logo":{"type":"string","format":"uri"},"about":{"type":"string"},"is_default":{"type":"boolean"},"meeting_link":{"type":"string","format":"uri"},"meeting_link_text":{"type":"string"},"auto_placement_testing":{"type":"boolean"},"notification_email":{"type":"string","format":"email","nullable":true,"description":"Optional email address for team-scoped notifications. When set, email notifications for this team are also sent to this address."},"notification_email_preferences":{"type":"object","nullable":true,"description":"Per-category preferences controlling which notification types are sent to notification_email. Defaults to all true.","properties":{"errorNotificationsEmail":{"type":"boolean"},"successNotificationsEmail":{"type":"boolean"},"warmingNotificationsEmail":{"type":"boolean"},"newInboxActivityNotificationsEmail":{"type":"boolean"},"linkedinInboxActivityNotificationsEmail":{"type":"boolean"},"outOfContactsNotificationsEmail":{"type":"boolean"}}},"inbox_auto_tag_settings":{"type":"object","description":"Auto-tag settings for inbox conversations"},"inbox_super_views":{"type":"array","description":"Customizable Super Views (Hot Leads, Needs Reply, etc.)","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"icon":{"type":"string"},"filters":{"type":"object","properties":{"mood":{"type":"string","enum":["positive","negative","neutral","needs_review"]},"statuses":{"type":"array","items":{"type":"string"}},"lastMessageDirection":{"type":"string","enum":["inbound","outbound"]},"labels":{"type":"array","items":{"type":"string"}}}},"visible":{"type":"boolean"},"order":{"type":"integer"}}}},"inbox_link_tracking_override":{"type":"boolean","nullable":true,"description":"Team override for inbox reply link tracking. null uses the organization default."},"member_count":{"type":"integer"},"organization":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"TeamMember":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"first_name":{"type":"string"},"last_name":{"type":"string"},"email":{"type":"string","format":"email"},"role":{"type":"string"},"joined_at":{"type":"string","format":"date-time"}}},"CreateTeamRequest":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":255},"domain":{"type":"string"},"logo":{"type":"string","format":"uri"},"about":{"type":"string","maxLength":2000},"meeting_link":{"type":"string","format":"uri"},"meeting_link_text":{"type":"string","maxLength":100},"auto_placement_testing":{"type":"boolean"}}},"UpdateTeamRequest":{"type":"object","properties":{"name":{"type":"string","maxLength":255},"domain":{"type":"string"},"logo":{"type":"string","format":"uri"},"about":{"type":"string","maxLength":2000},"meeting_link":{"type":"string","format":"uri"},"meeting_link_text":{"type":"string","maxLength":100},"auto_placement_testing":{"type":"boolean"},"auto_placement_testing_frequency":{"type":"integer","enum":[7,14,30]},"notification_email":{"type":"string","format":"email","nullable":true,"description":"Email address for team-scoped notifications. Set to null or empty string to remove."},"notification_email_preferences":{"type":"object","description":"Per-category preferences for which notification types are sent to notification_email. Only provided keys are updated (merge).","properties":{"errorNotificationsEmail":{"type":"boolean"},"successNotificationsEmail":{"type":"boolean"},"warmingNotificationsEmail":{"type":"boolean"},"newInboxActivityNotificationsEmail":{"type":"boolean"},"linkedinInboxActivityNotificationsEmail":{"type":"boolean"},"outOfContactsNotificationsEmail":{"type":"boolean"}}},"inbox_auto_tag_settings":{"type":"object","properties":{"auto_tag_bounced":{"type":"boolean"},"auto_tag_opt_out":{"type":"boolean"},"auto_tag_out_of_office":{"type":"boolean"}}},"inbox_super_views":{"type":"array","description":"Customizable Super Views configuration","items":{"type":"object","required":["id","name","icon","filters"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"icon":{"type":"string"},"filters":{"type":"object","properties":{"mood":{"type":"string","enum":["positive","negative","neutral","needs_review"]},"statuses":{"type":"array","items":{"type":"string"}},"lastMessageDirection":{"type":"string","enum":["inbound","outbound"]},"labels":{"type":"array","items":{"type":"string"}}}},"visible":{"type":"boolean"},"order":{"type":"integer","minimum":0}}}},"inbox_link_tracking_override":{"type":"boolean","nullable":true,"description":"Team override for inbox reply link tracking. null uses the organization default."}}},"InboxLinkClick":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"link_id":{"type":"string","format":"uuid"},"original_url":{"type":"string"},"clicked_at":{"type":"string","format":"date-time"},"conversation_id":{"type":"string","format":"uuid"},"conversation_message_id":{"type":"string","format":"uuid"},"contact_id":{"type":"string","format":"uuid","nullable":true},"sender_id":{"type":"string","format":"uuid","nullable":true}}},"ConversationLinkClicksResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"clicks":{"type":"object","additionalProperties":{"type":"array","items":{"$ref":"#/components/schemas/InboxLinkClick"}}}}}}}]},"MessageLinkClicksResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"clicks":{"type":"array","items":{"$ref":"#/components/schemas/InboxLinkClick"}}}}}}]},"ContactInboxLinkClicksResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/InboxLinkClick"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}]},"TeamInboxLinkActivityItem":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"original_url":{"type":"string"},"clicked_at":{"type":"string","format":"date-time"},"conversation_id":{"type":"string","format":"uuid"},"conversation_title":{"type":"string","nullable":true},"contact":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string"},"first_name":{"type":"string"},"last_name":{"type":"string"}}},"sender":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string"},"name":{"type":"string"}}}}},"TeamInboxLinkActivityResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/TeamInboxLinkActivityItem"}},"total":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer"}}}}}]},"TeamResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Team"}}}]},"TeamDetailResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"allOf":[{"$ref":"#/components/schemas/Team"},{"type":"object","properties":{"members":{"type":"array","items":{"$ref":"#/components/schemas/TeamMember"}}}}]}}}]},"TeamListResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Team"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}]},"TeamUsageResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"team_id":{"type":"string","format":"uuid"},"team_name":{"type":"string"},"contacts_used":{"type":"integer"},"contacts_limit":{"type":"integer"},"usage_percentage":{"type":"number"},"is_at_limit":{"type":"boolean"}}}}}]},"BillingTeamUsageResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","description":"Org-level usage and cost allocation by team","properties":{"periods":{"type":"array","description":"Available billing periods for selector","items":{"type":"object","properties":{"index":{"type":"integer"},"label":{"type":"string"},"start":{"type":"string"},"end":{"type":"string"}}}},"selected_period":{"type":"object","properties":{"index":{"type":"integer"},"start":{"type":"string"},"end":{"type":"string"},"label":{"type":"string"}}},"org_totals":{"type":"object","properties":{"emails_sent":{"type":"integer"},"email_senders":{"type":"integer"},"contacts":{"type":"integer"},"domains":{"type":"integer"},"mailboxes":{"type":"integer"},"credits_used":{"type":"integer"},"monthly_cost":{"type":"number"}}},"social_senders":{"type":"object","description":"LinkedIn senders at org level; by_team shows which teams they're assigned to","properties":{"linkedin_senders":{"type":"integer"},"by_team":{"type":"array","items":{"type":"object","properties":{"team_id":{"type":"string","nullable":true},"team_name":{"type":"string"},"linkedin_senders":{"type":"integer"}}}}}},"teams":{"type":"array","items":{"type":"object","properties":{"team_id":{"type":"string","nullable":true},"team_name":{"type":"string"},"emails_sent":{"type":"integer"},"emails_pct":{"type":"number"},"email_senders":{"type":"integer"},"contacts":{"type":"integer"},"credits_used":{"type":"integer"},"domains":{"type":"integer"},"mailboxes":{"type":"integer"},"domain_cost":{"type":"number"},"mailbox_cost":{"type":"number"},"allocated_cost":{"type":"number"}}}},"allocation_method":{"type":"string"},"volumeMetric":{"type":"string","enum":["sent","delivered"],"description":"Whether emails_sent counts use delivery-aligned (ESP) or send-handoff volume"},"volumeLabel":{"type":"string","description":"Short label for email volume (Sent or Delivered)"},"volumeLabelLong":{"type":"string","description":"Long label for email volume (Emails sent or Emails delivered)"}}}}}]},"CostItem":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"category":{"type":"string","enum":["infrastructure","enrichment","tool","other"]},"cost_type":{"type":"string","enum":["fixed","per_unit"]},"amount":{"type":"number","description":"Non-negative; up to 6 decimal places (e.g. per-unit rates)."},"unit_label":{"type":"string","nullable":true},"unit_count":{"type":"integer","nullable":true},"effective_date":{"type":"string","format":"date","nullable":true},"notes":{"type":"string","nullable":true},"TeamId":{"type":"string","format":"uuid","nullable":true},"Team":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"name":{"type":"string"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"CostItemInput":{"type":"object","required":["name","category","cost_type","amount"],"properties":{"name":{"type":"string","maxLength":255},"category":{"type":"string","enum":["infrastructure","enrichment","tool","other"]},"cost_type":{"type":"string","enum":["fixed","per_unit"]},"amount":{"type":"number","minimum":0,"description":"Up to 6 decimal places. JSON numbers use a dot as the decimal separator."},"unit_label":{"type":"string","maxLength":100,"nullable":true},"unit_count":{"type":"integer","minimum":0,"nullable":true},"effective_date":{"type":"string","format":"date","nullable":true},"notes":{"type":"string","maxLength":1000,"nullable":true},"TeamId":{"type":"string","format":"uuid","nullable":true,"description":"Assign to a specific team; null = split across all teams"}}},"CampaignCategory":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"team_id":{"type":"string","format":"uuid"},"org_id":{"type":"string","format":"uuid"},"campaign_count":{"type":"integer","description":"Number of campaigns in this category"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"CampaignCategoryListResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CampaignCategory"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}]},"CampaignCategoryResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CampaignCategory"}}}]},"Sender":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email"},"send_as":{"type":"string"},"reply_to":{"type":"string","format":"email"},"provider":{"type":"string","enum":["smtp","outlook","google","missioninbox","supersend-relay","ss-private-smtp","sendgrid","mailreef","infraforge","zoho","namecheap","hostinger","titan","ionos","dreamhost","aol","mailgun","aws","azure"]},"domain":{"type":"string"},"disabled":{"type":"boolean"},"warm":{"type":"boolean"},"warming_stage":{"type":"string","enum":["warming","building","accelerating","mature"]},"max_per_day":{"type":"integer"},"global_max_per_day":{"type":"integer"},"campaign_ramp_enabled":{"type":"boolean","description":"When true, daily limit ramps from start volume to target over duration"},"campaign_ramp_first_send_date":{"type":"string","format":"date","nullable":true,"description":"Date of first campaign send; set when first send occurs"},"campaign_ramp_duration_days":{"type":"integer","nullable":true,"minimum":1,"maximum":365,"description":"Days to reach target (required when ramp enabled)"},"campaign_ramp_start_volume":{"type":"integer","nullable":true,"minimum":1,"description":"Limit on day 0 (required when ramp enabled)"},"max_warm_per_day":{"type":"integer"},"health_score":{"type":"integer"},"status":{"type":"string"},"signature":{"type":"string"},"forward_to":{"type":"string","format":"email"},"forward_rules":{"type":"array","nullable":true,"items":{"type":"object","required":["emails"],"properties":{"emails":{"type":"string","description":"Comma-separated email addresses for this rule"},"label_ids":{"type":"array","nullable":true,"items":{"type":"string","format":"uuid"},"description":"Label IDs that trigger forwarding to this destination. Null omits label filtering and forwards all replies for this destination."}}},"description":"Per-destination forwarding rules. Each rule maps email destinations to optional label filters."},"forward_unsubscribes":{"type":"boolean","description":"Whether unsubscribe replies should be forwarded to the configured forwarding destinations. When true, unsubscribe replies ignore label filters and forward to every configured destination."},"sender_profile":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"timezone":{"type":"string"}}},"team_id":{"type":"string","format":"uuid"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"UpdateSenderRequest":{"type":"object","properties":{"send_as":{"type":"string"},"reply_to":{"type":"string","format":"email"},"signature":{"type":"string"},"forward_to":{"type":"string","format":"email"},"forward_rules":{"type":"array","nullable":true,"items":{"type":"object","required":["emails"],"properties":{"emails":{"type":"string","description":"Comma-separated email addresses for this rule"},"label_ids":{"type":"array","nullable":true,"items":{"type":"string","format":"uuid"},"description":"Label IDs that trigger forwarding to this destination. Null omits label filtering and forwards all replies for this destination."}}},"description":"Per-destination forwarding rules. Each rule maps email destinations to optional label filters."},"forward_unsubscribes":{"type":"boolean","description":"Whether unsubscribe replies should be forwarded to the configured forwarding destinations. When true, unsubscribe replies ignore label filters and forward to every configured destination."},"disabled":{"type":"boolean"},"warm":{"type":"boolean"},"max_per_day":{"type":"integer","minimum":1,"maximum":15000},"global_max_per_day":{"type":"integer","minimum":1,"maximum":1000},"max_warm_per_day":{"type":"integer","minimum":1,"maximum":1000},"warm_email_ramp":{"type":"integer","minimum":1,"maximum":365},"campaign_ramp_enabled":{"type":"boolean","description":"Enable gradual ramp from start volume to target"},"campaign_ramp_duration_days":{"type":"integer","minimum":1,"maximum":365,"description":"Days to reach target (required when ramp enabled)"},"campaign_ramp_start_volume":{"type":"integer","minimum":1,"description":"Limit on first send day (required when ramp enabled)"},"SenderProfileId":{"type":"string","format":"uuid","nullable":true}}},"SenderResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Sender"}}}]},"SenderListResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Sender"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}]},"PlacementTest":{"type":"object","description":"Full placement test details (GET /placement-tests/{id})","properties":{"object":{"type":"string","example":"placement_test"},"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"status":{"type":"string","enum":["pending","sending","sent","completed","failed"]},"tracking_code":{"type":"string"},"score":{"type":"number","nullable":true,"description":"Deliverability score 0-10"},"credit_cost":{"type":"integer"},"total_seeds":{"type":"integer"},"auto_send":{"type":"boolean"},"last_send_error":{"type":"string","nullable":true,"description":"Auto-send only. The most recent error from sending a seed email (why the test failed or is stuck)."},"test_email_subject":{"type":"string","nullable":true,"description":"Subject line of the test email sent"},"test_email_from":{"type":"string","format":"email","nullable":true,"description":"From address of the test email"},"test_email_body":{"type":"string","nullable":true,"description":"HTML body of the test email sent (for analysis)"},"seed_addresses":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string","format":"email"}}}},"results_summary":{"type":"object","properties":{"inbox":{"type":"integer"},"spam":{"type":"integer"},"not_received":{"type":"integer"},"bounced":{"type":"integer"}}},"placement_results":{"type":"array","items":{"$ref":"#/components/schemas/PlacementTestResult"}},"sender":{"$ref":"#/components/schemas/PlacementTestSender"},"created_by":{"$ref":"#/components/schemas/PlacementTestUser"},"sent_at":{"type":"string","format":"date-time","nullable":true},"completed_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"PlacementTestResult":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"seed_email":{"type":"string","nullable":true},"placement":{"type":"string","enum":["inbox","spam","bounced","pending","not_received"]},"received_at":{"type":"string","format":"date-time","nullable":true},"checked_at":{"type":"string","format":"date-time","nullable":true},"check_attempts":{"type":"integer"},"metadata":{"type":"object"},"tags":{"type":"array","items":{"type":"string"}},"send_status":{"type":"string","nullable":true,"enum":["pending","sent","failed",null],"description":"Auto-send only. Whether the email to this seed went out."},"send_error":{"type":"string","nullable":true,"description":"Auto-send only. Why the email to this seed could not be sent."}}},"PlacementTestSender":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email"},"provider":{"type":"string"}}},"PlacementTestUser":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"email":{"type":"string","format":"email"}}},"PlacementTestListItem":{"type":"object","description":"Placement test summary for list responses","properties":{"object":{"type":"string","example":"placement_test"},"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"status":{"type":"string","enum":["pending","sending","sent","completed","failed"]},"tracking_code":{"type":"string"},"score":{"type":"number","nullable":true},"credit_cost":{"type":"integer"},"total_seeds":{"type":"integer"},"auto_send":{"type":"boolean"},"is_auto_test":{"type":"boolean"},"results_summary":{"type":"object","properties":{"inbox":{"type":"integer"},"spam":{"type":"integer"},"not_received":{"type":"integer"},"bounced":{"type":"integer"}}},"sender":{"$ref":"#/components/schemas/PlacementTestSender"},"created_by":{"$ref":"#/components/schemas/PlacementTestUser"},"sent_at":{"type":"string","format":"date-time","nullable":true},"completed_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"PlacementTestResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PlacementTest"}}}]},"PlacementTestListResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PlacementTestListItem"}},"pagination":{"type":"object","properties":{"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"},"has_more":{"type":"boolean"}}}}}]},"CreatePlacementTestRequest":{"type":"object","properties":{"name":{"type":"string","maxLength":255,"default":"Untitled Test"},"test_email_subject":{"type":"string","maxLength":255},"test_email_body":{"type":"string","description":"HTML body of the test email to send"},"test_email_from":{"type":"string","format":"email"},"seed_count":{"type":"integer","minimum":3,"maximum":50,"default":10,"description":"Minimum 3 ensures at least one Google and one Outlook seed per test."},"sender_id":{"type":"string","format":"uuid","nullable":true},"auto_send":{"type":"boolean","default":false},"team_id":{"type":"string","format":"uuid","nullable":true}}},"CreatePlacementTestResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","example":"placement_test"},"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"status":{"type":"string"},"tracking_code":{"type":"string"},"total_seeds":{"type":"integer"},"credit_cost":{"type":"integer"},"auto_send":{"type":"boolean"},"sender_id":{"type":"string","format":"uuid","nullable":true},"team_id":{"type":"string","format":"uuid","nullable":true},"seed_addresses":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string"}}}},"created_at":{"type":"string","format":"date-time"}}}}}]},"ExportPlacementTestsRequest":{"type":"object","required":["team_id"],"properties":{"team_id":{"type":"string","format":"uuid"},"filters":{"type":"object","properties":{"search":{"type":"string"},"status":{"type":"string"},"conditional_filters":{"oneOf":[{"type":"string"},{"type":"array"}]},"sortBy":{"type":"string"},"sortOrder":{"type":"string","enum":["asc","desc"]}}}}},"Event":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["scheduled","sent","open","reply","unsubscribe","visit","message","follow","task","like","connection_request","move_contact"]},"channel":{"type":"string","enum":["email","linkedin","text","call","instagram"]},"date":{"type":"string","format":"date-time"},"sequence_step":{"type":"integer"},"opened":{"type":"boolean"},"clicked":{"type":"boolean"},"replied":{"type":"boolean"},"bounced":{"type":"boolean"},"autoresponse":{"type":"boolean"},"opens":{"type":"integer"},"clicks":{"type":"integer"},"replies":{"type":"integer"},"unsubscribes":{"type":"integer"},"subject":{"type":"string"},"contact":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string"},"first_name":{"type":"string"},"last_name":{"type":"string"}}},"campaign":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"sender":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string"},"send_as":{"type":"string"}}},"campaign_id":{"type":"string","format":"uuid"},"contact_id":{"type":"string","format":"uuid"},"sender_id":{"type":"string","format":"uuid"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"EventResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Event"}}}]},"EventListResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Event"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}]},"Webhook":{"type":"object","properties":{"object":{"type":"string","example":"webhook"},"id":{"type":"string","format":"uuid"},"url":{"type":"string","format":"uri"},"name":{"type":"string"},"enabled":{"type":"boolean"},"secret":{"type":"string","description":"Masked webhook secret (or null if not set)"},"campaign_count":{"type":"integer","description":"Number of campaigns using this webhook"},"config":{"type":"object","properties":{"reply_event":{"type":"boolean"},"open_event":{"type":"boolean"},"click_event":{"type":"boolean"},"sent_event":{"type":"boolean"},"bounce_event":{"type":"boolean"},"unsubscribe_event":{"type":"boolean"}}},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"WebhookListResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}]},"WebhookDelivery":{"type":"object","properties":{"object":{"type":"string","example":"webhook_delivery"},"id":{"type":"string","format":"uuid"},"event_id":{"type":"string","format":"uuid","description":"Deterministic event ID for deduplication"},"event_type":{"type":"string","description":"Event type (reply, open, click, bounce, etc.)"},"status":{"type":"string","enum":["pending","delivered","failed","retrying"],"description":"Current delivery status"},"attempt_count":{"type":"integer","description":"Number of delivery attempts made"},"max_attempts":{"type":"integer","description":"Maximum retry attempts (default 7)"},"response_status":{"type":"integer","nullable":true,"description":"HTTP response status code"},"response_time_ms":{"type":"integer","nullable":true,"description":"Response latency in milliseconds"},"error_message":{"type":"string","nullable":true,"description":"Error message if delivery failed"},"created_at":{"type":"string","format":"date-time"},"last_attempt_at":{"type":"string","format":"date-time","nullable":true},"delivered_at":{"type":"string","format":"date-time","nullable":true},"next_retry_at":{"type":"string","format":"date-time","nullable":true},"payload":{"type":"object","nullable":true,"description":"JSON body POSTed to the webhook URL (same shape as live events; null if not stored)"},"contact":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email"},"name":{"type":"string","nullable":true}}},"campaign":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}}}},"WebhookDeliveryDetail":{"allOf":[{"$ref":"#/components/schemas/WebhookDelivery"},{"type":"object","properties":{"response_body":{"type":"string","nullable":true,"description":"Response body from webhook endpoint"},"response_headers":{"type":"object","nullable":true,"description":"Response headers from webhook endpoint"}}}]},"WebhookDeliveryListResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}]},"WebhookDeliveryResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookDeliveryDetail"}}}]},"WebhookDeliveryRetryResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookDelivery"},"message":{"type":"string","example":"Retry queued successfully"}}}]},"SequenceNode":{"type":"object","description":"A node in the sequence builder canvas","required":["id","type","data","position"],"properties":{"id":{"type":"string","format":"uuid","description":"Unique identifier for the node"},"type":{"type":"string","enum":["startNode","stopNode","emailNode","waitNode","ifNode","switchNode","httpApiRequestNode","linkedinProfileVisitNode","linkedinConnectionNode","linkedinConnectionWithMessageNode","linkedinMessageNode","linkedinInmailNode","linkedinLikeRecentPostNode","linkedinFollowNode","linkedinCommentRecentPostNode","linkedinWithdrawConnectionNode","linkedinWaitUntilConnectedNode","moveToAnotherCampaignNode","taskNode","noteNode"],"description":"The type of node"},"data":{"type":"object","description":"Node-specific data (varies by node type).\n\n**For emailNode:** use `subject_a` and `body_a` (required). Do NOT use bare `subject` or `body` — these fields are rejected.\nFor A/B testing also include `subject_b`/`body_b`, `subject_c`/`body_c`, or `subject_d`/`body_d`.\n\n**For linkedinCommentRecentPostNode:** put the comment in `body`. Variables and spintax work as in a LinkedIn message; LinkedIn allows 1250 characters after rendering. Contacts with no original posts skip the step.\n**linkedinFollowNode** and **linkedinWithdrawConnectionNode** need no data beyond `type`. Withdraw skips contacts with no pending invitation.\n\n**For waitNode:** `wait` (a number) and `wait_unit`, one of `minutes`, `hours` or `days`. Any other unit counts as minutes.\n\n**For linkedinWaitUntilConnectedNode:** the contact waits until the connection request is accepted. By default it waits with no limit (`wait_infinitely: true`). To set a limit, send `wait_infinitely: false` with `max_wait` (a number) and `max_wait_unit` (`minutes`, `hours` or `days`; hours when omitted). After the limit the contact finishes with the reason \"LinkedIn connection timeout\".\n```json\n{ \"wait_infinitely\": false, \"max_wait\": 7, \"max_wait_unit\": \"days\" }\n```\n\n**For linkedinConnectionNode / linkedinConnectionWithMessageNode / linkedinMessageNode / linkedinInmailNode:** text in `body_a` (InMail also `subject_a`). The connection node sends no note; the with-message node sends `body_a` as the note.\n\n**For moveToAnotherCampaignNode:** `campaign: { \"id\": \"<campaign uuid>\" }`. **For httpApiRequestNode:** `http_config` (the request); its response can be read by an If condition as `api_step`.\n\n**For ifNode (a yes/no branch):** `event`, `comparison` and `comparison_value`. The node's outgoing edges carry `sourceHandle` `\"true\"` (yes) or `\"false\"` (no). The edges are the routing: the node stores no yes/no step pointers. `event` is one of:\n- `accepted_linkedin`: the contact accepted the connection, checked on LinkedIn when the contact reaches the branch. Use this for \"connected on LinkedIn\"; the app shows it as \"Accepted LinkedIn Invite\". `connected_on_linkedin` (shown in the app as \"Is Connected On LinkedIn\") is the same check and is saved as written.\n- `opened_email`, `replied_email`, `clicked_email`, `has_email`, `has_linkedin`, `linkedin_reply`.\n- Any contact field or custom field, compared with `comparison` (`=`, `!=`, `>`, `>=`, `<`, `<=`, `contains`, `starts_with`, `ends_with`, `exists`, `is_empty`, `regex`) and `comparison_value`. `linkedin_connection_accepted` = `true` works, but reads the stored flag, which is only set once the accept has been detected.\n- `api_step` or `api_step_<n>`: an HTTP request step's response (add `property_path` for a nested value).\n\nMulti-path branches (`multi_path_enabled` with `paths` and `condition_groups`) are built in the app. A **switchNode** has `switch_field`, `cases` (`[{ \"id\", \"label\" }]`) and `default_path_id`; its edges use `sourceHandle` `\"case_<id>\"`.\n\n**Replies:** a reply by email or on LinkedIn ends the sequence for that contact automatically; there is no setting. The exception is a campaign with a moveToAnotherCampaignNode: contacts mid-sequence keep moving so the reply can be routed. `GET /v2/campaigns/{id}/review` shows this as `settings.stop_on_reply` and lists every branch and path under `sequence.tree`.\n\nExample emailNode data:\n```json\n{\n  \"type\": 1,\n  \"label\": \"Email Step 1\",\n  \"subject_a\": \"Hello {{first_name}}\",\n  \"body_a\": \"<p>Your email body</p>\",\n  \"wait\": 1,\n  \"wait_unit\": \"days\"\n}\n```\n","additionalProperties":true},"position":{"$ref":"#/components/schemas/NodePosition"},"style":{"type":"object","properties":{"width":{"type":"integer"},"height":{"type":"integer"},"minWidth":{"type":"integer"},"minHeight":{"type":"integer"}}}}},"NodePosition":{"type":"object","required":["x","y"],"properties":{"x":{"type":"number"},"y":{"type":"number"}}},"SequenceEdge":{"type":"object","description":"A connection between two nodes in the sequence","required":["id","source","target"],"properties":{"id":{"type":"string","description":"Unique identifier for the edge"},"source":{"type":"string","format":"uuid","description":"ID of the source node"},"sourceHandle":{"type":"string","description":"Handle ID on the source node. For an ifNode, 'true' (yes) or 'false' (no); for a switchNode, 'case_<id>'. The edges are the routing (nodes store no yes/no step pointers)."},"target":{"type":"string","format":"uuid","description":"ID of the target node"},"type":{"type":"string","enum":["buttonedge"],"default":"buttonedge"}}},"IfNodeData":{"type":"object","description":"Data structure for If (Condition) nodes","required":["step_number"],"properties":{"step_number":{"type":"integer","description":"Step number in the sequence"},"label":{"type":"string","description":"Display label for the node"},"type":{"type":"integer","enum":[10],"description":"Message type (10 = IF)"},"event":{"type":"string","description":"Field or event to evaluate (e.g., 'opened_email', 'api_step.data.score')"},"property_path":{"type":"string","description":"Nested property path for API responses (e.g., 'research.score')"},"comparison":{"type":"string","enum":["=","!=",">",">=","<","<=","contains","starts_with","ends_with","exists","is_empty","regex"],"description":"Comparison operator"},"comparison_value":{"type":"string","description":"Value to compare against"},"multi_path_enabled":{"type":"boolean","default":false,"description":"Enable multi-path routing (more than 2 outcomes)"},"paths":{"type":"array","maxItems":10,"description":"Array of paths with conditions (when multi_path_enabled is true)","items":{"$ref":"#/components/schemas/IfPath"}}}},"IfPath":{"type":"object","description":"A path in a multi-path If node","required":["id","label","condition_groups"],"properties":{"id":{"type":"string","description":"Unique identifier for this path (used in edges)"},"label":{"type":"string","description":"Display label for this path"},"is_default":{"type":"boolean","default":false,"description":"Whether this is the default/fallback path"},"condition_groups":{"type":"array","description":"Condition groups (ANDed together)","items":{"$ref":"#/components/schemas/IfConditionGroup"}}}},"IfConditionGroup":{"type":"object","description":"A group of conditions (conditions within are ORed)","required":["conditions"],"properties":{"conditions":{"type":"array","maxItems":10,"items":{"$ref":"#/components/schemas/IfCondition"}}}},"IfCondition":{"type":"object","description":"A single condition in an If node","required":["event","comparison"],"properties":{"event":{"type":"string","description":"Field or event to evaluate"},"property_path":{"type":"string","description":"Nested property path for API responses"},"comparison":{"type":"string","enum":["=","!=",">",">=","<","<=","contains","starts_with","ends_with","exists","is_empty","regex"]},"comparison_value":{"type":"string","description":"Value to compare against"}}},"SwitchNodeData":{"type":"object","description":"Data structure for Switch nodes (multi-path routing by value)","required":["step_number","switch_field","cases","default_path_id"],"properties":{"step_number":{"type":"integer","description":"Step number in the sequence"},"label":{"type":"string","description":"Display label for the node"},"type":{"type":"integer","enum":[25],"description":"Message type (25 = SWITCH)"},"switch_field":{"type":"string","description":"Field to switch on (e.g., 'api_step', 'company_size')"},"property_path":{"type":"string","description":"Nested property path for API responses"},"cases":{"type":"array","maxItems":10,"description":"Array of cases to match against","items":{"$ref":"#/components/schemas/SwitchCase"}},"default_path_id":{"type":"string","description":"ID of the default case (fallback)"}}},"SwitchCase":{"type":"object","description":"A case in a Switch node","required":["id","label","match_type"],"properties":{"id":{"type":"string","description":"Unique identifier for this case (used in edges)"},"label":{"type":"string","description":"Display label for this case"},"is_default":{"type":"boolean","default":false,"description":"Whether this is the default/fallback case"},"match_type":{"type":"string","enum":["equals","not_equals","greater_than","greater_than_or_equal","less_than","less_than_or_equal","range","contains","starts_with","ends_with","regex","exists","is_empty"],"description":"Type of matching to perform"},"value":{"type":"string","description":"Value to match (for equals, contains, etc.)"},"min":{"type":"number","description":"Minimum value for range matching"},"max":{"type":"number","description":"Maximum value for range matching"},"pattern":{"type":"string","description":"Regex pattern for regex matching"}}},"HttpApiRequestNodeData":{"type":"object","description":"Data structure for HTTP API Request nodes","required":["step_number","request_method","request_url"],"properties":{"step_number":{"type":"integer"},"label":{"type":"string"},"type":{"type":"integer","enum":[23],"description":"Message type (23 = HTTP_API_REQUEST)"},"request_method":{"type":"string","enum":["GET","POST","PUT","PATCH","DELETE"]},"request_url":{"type":"string","description":"URL to make the request to (supports variables)"},"request_headers":{"type":"object","additionalProperties":{"type":"string"},"description":"HTTP headers to include in the request"},"request_body":{"type":"string","description":"Request body (for POST, PUT, PATCH)"},"timeout_ms":{"type":"integer","default":10000,"description":"Request timeout in milliseconds"},"retry_on_failure":{"type":"boolean","default":false},"max_retries":{"type":"integer","default":3},"store_response":{"type":"boolean","default":true,"description":"Whether to store response for use in If nodes"}}},"UpdateSequenceRequest":{"type":"object","description":"Request body for updating a campaign sequence","properties":{"nodes":{"type":"array","description":"Array of sequence nodes (replaces all existing nodes)","items":{"$ref":"#/components/schemas/SequenceNode"}},"edges":{"type":"array","description":"Array of edges connecting nodes (replaces all existing edges)","items":{"$ref":"#/components/schemas/SequenceEdge"}}}},"CampaignSequenceResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"campaign_id":{"type":"string","format":"uuid"},"nodes":{"type":"array","items":{"$ref":"#/components/schemas/SequenceNode"}},"edges":{"type":"array","items":{"$ref":"#/components/schemas/SequenceEdge"}}}}}}]},"CampaignReviewResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CampaignReview"}}}]},"CampaignReview":{"type":"object","properties":{"object":{"type":"string","example":"campaign_review"},"campaign":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"status":{"type":"string","enum":["active","draft","paused","not_launched"],"description":"`paused` means not active but contacts have already started or finished.\n`not_launched` means not active, not a draft, and no contact has started yet.\n"},"is_draft":{"type":"boolean"},"version":{"type":"string","enum":["simple","complex","legacy"],"description":"Sequence builder version (`simple` = 3, `complex` = 2, `legacy` = 1)."},"team_id":{"type":"string","format":"uuid"}}},"sequence":{"type":"object","properties":{"steps":{"type":"array","description":"Steps on the main path, up to 12. Waits are folded into the following step's `wait_before`.","items":{"$ref":"#/components/schemas/CampaignReviewStep"}},"total_steps":{"type":"integer","description":"Number of steps on the main path, including any beyond the 12 listed."},"emails":{"type":"integer","description":"Number of email steps on the main path."},"has_branches":{"type":"boolean","description":"True when the sequence has If (other than simple route-by-status) or Switch nodes. `steps` follows one route; `tree` lists every path."},"tree":{"type":"array","description":"Only when `has_branches` is true: the whole sequence in order, every path included. A step is `{ type, label, wait_before, ... }`\n(a linkedinWaitUntilConnected step also has `max_wait`: `{ value, unit }` or \"waits until connected, no limit\"). A branch is\n`{ type: \"branch\", label, condition, yes: [...steps], no: [...steps] }`; a switch has `cases: [{ label, steps }]` and a multi-path\nIf has `paths: [{ label, is_default, steps }]`. At most 80 steps are listed; `tree_truncated` is true when more were left out.\n","items":{"type":"object","additionalProperties":true}},"tree_truncated":{"type":"boolean"}}},"sending":{"type":"object","properties":{"sender_profiles":{"type":"array","items":{"$ref":"#/components/schemas/CampaignReviewSenderProfile"}},"active_inboxes":{"type":"integer"},"warming_inboxes":{"type":"integer","description":"Active inboxes with warmup on that started less than 14 days ago."},"linkedin_accounts":{"type":"integer","description":"Assigned sender profiles that have a LinkedIn account."},"daily_capacity":{"type":"integer","description":"Sum of active inboxes' daily limits, capped by `campaign_daily_limit` when set."},"campaign_daily_limit":{"type":"integer","nullable":true}}},"contacts":{"type":"object","properties":{"total":{"type":"integer"},"not_started":{"type":"integer"},"in_progress":{"type":"integer"},"finished":{"type":"integer"},"paused":{"type":"integer"}}},"schedule":{"type":"object","properties":{"timezone":{"type":"string","example":"America/New_York"},"days":{"type":"array","items":{"type":"string","enum":["sunday","monday","tuesday","wednesday","thursday","friday","saturday"]}},"hours":{"type":"array","items":{"type":"object","properties":{"start":{"type":"string","example":"09:00"},"end":{"type":"string","example":"17:00"}}}},"next_window":{"type":"object","nullable":true,"description":"Next sending window in the campaign's timezone, or null when the schedule has no days or hours.","properties":{"day":{"type":"string","description":"`today`, `tomorrow`, or a weekday name","example":"tomorrow"},"start":{"type":"string","description":"Start time (HH:MM). When sending now, the current time.","example":"09:00"},"sending_now":{"type":"boolean"}}}}},"settings":{"type":"object","properties":{"tracking":{"type":"boolean"},"unsubscribe_link":{"type":"boolean"},"stop_on_reply":{"type":"object","description":"Whether a reply ends the sequence. Automatic, not a setting.","properties":{"ends_sequence":{"type":"boolean","description":"False when the campaign has a \"Move to another campaign\" step, so contacts mid-sequence keep moving and the reply can be routed."},"applies_to":{"type":"array","items":{"type":"string","enum":["email","linkedin"]}},"configurable":{"type":"boolean"},"note":{"type":"string"}}}}},"billing":{"type":"object","properties":{"can_send":{"type":"boolean","description":"Whether the organization's trial or plan allows sending."},"trial_available":{"type":"boolean","description":"When `can_send` is false, whether adding a card starts the free trial (otherwise a plan is needed)."}}},"checks":{"type":"array","items":{"$ref":"#/components/schemas/CampaignReviewCheck"}},"can_launch":{"type":"boolean","description":"True when the campaign is not active and no blocking check fails."},"estimate":{"type":"object","properties":{"sending_days_to_reach_everyone":{"type":"integer","nullable":true,"description":"Sending days to send the first step to every not-started contact at `daily_capacity`. Null when there are no contacts to start or no capacity."}}},"links":{"type":"object","properties":{"campaign":{"type":"string","format":"uri","description":"Dashboard link to the campaign's sequence."},"add_card":{"type":"string","format":"uri","description":"Only present when `billing.can_send` is false. Where a human adds a card or picks a plan."}}}}},"CampaignReviewStep":{"type":"object","properties":{"step":{"type":"integer","example":1},"type":{"type":"string","description":"`email`, or the node type without the `Node` suffix (for example `linkedinConnection`, `linkedinMessage`).","example":"email"},"label":{"type":"string","example":"Email"},"wait_before":{"type":"object","nullable":true,"description":"Wait before this step, or null for none.","properties":{"value":{"type":"number","example":3},"unit":{"type":"string","example":"days"}}},"subject":{"type":"string","nullable":true,"description":"Email steps only. Subject of variant A; null for a reply with no subject."},"variants":{"type":"integer","minimum":1,"maximum":4,"description":"Email steps only. Number of A/B subject variants."},"as_reply":{"type":"boolean","description":"Email steps only. Sent as a reply in the previous email's thread."}}},"CampaignReviewSenderProfile":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"inboxes":{"type":"integer"},"active_inboxes":{"type":"integer"},"warming_inboxes":{"type":"integer"},"linkedin":{"type":"boolean","description":"Whether the profile has a LinkedIn account."},"daily_capacity":{"type":"integer","description":"Sum of the profile's active inboxes' daily limits."}}},"CampaignReviewCheck":{"type":"object","properties":{"key":{"type":"string","description":"Blocking: `has_nodes`, `has_messages`, `has_sender_profile`, `has_active_email_sender`,\n`has_active_linkedin_identity`, `schedule_valid`, `can_send`.\nWarnings: `has_contacts`, `inboxes_warming` (only when inboxes are warming).\n","example":"has_sender_profile"},"ok":{"type":"boolean"},"blocking":{"type":"boolean","description":"Whether a failing check stops launch."},"message":{"type":"string"}}},"AssignCampaignSenderProfileRequest":{"type":"object","required":["sender_profile_id"],"properties":{"sender_profile_id":{"type":"string","format":"uuid","description":"Sender profile UUID from GET /v2/sender-profiles"}}},"CampaignSenderProfilesResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","description":"Sender profile object (same shape as GET /v2/sender-profiles items)"}}}}]},"CampaignSenderProfileAssignmentResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","example":"campaign_sender_profile"},"campaign_id":{"type":"string","format":"uuid"},"sender_profile_id":{"type":"string","format":"uuid"},"sender_profile_name":{"type":"string"},"association_id":{"type":"string","format":"uuid"},"senders":{"type":"array","items":{"type":"object"}}}}}}]},"CampaignSenderProfileRemovalResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"object":{"type":"string","example":"campaign_sender_profile"},"campaign_id":{"type":"string","format":"uuid"},"sender_profile_id":{"type":"string","format":"uuid"},"removed":{"type":"boolean"}}}}}]},"CreatePlaybookRequest":{"type":"object","required":["TeamId","name"],"properties":{"TeamId":{"type":"string","format":"uuid"},"name":{"type":"string"},"offer_value_proposition":{"type":"string","nullable":true},"offer_type":{"type":"string","enum":["trial","demo","case_study","insight","discount","other"],"nullable":true},"offer_cta":{"type":"string","nullable":true},"segment_description":{"type":"string","nullable":true},"segment_industries":{"type":"array","items":{"type":"string"},"nullable":true},"segment_roles":{"type":"array","items":{"type":"string"},"nullable":true},"segment_company_size_min":{"type":"integer","nullable":true},"segment_company_size_max":{"type":"integer","nullable":true},"segment_geographies":{"type":"array","items":{"type":"string"},"nullable":true},"messaging_angle":{"type":"string","nullable":true},"messaging_key_messages":{"type":"array","items":{"type":"string"},"nullable":true},"hypothesis":{"type":"string","nullable":true}}},"UpdatePlaybookRequest":{"type":"object","properties":{"name":{"type":"string"},"offer_value_proposition":{"type":"string","nullable":true},"offer_type":{"type":"string","enum":["trial","demo","case_study","insight","discount","other"],"nullable":true},"offer_cta":{"type":"string","nullable":true},"segment_description":{"type":"string","nullable":true},"segment_industries":{"type":"array","items":{"type":"string"},"nullable":true},"segment_roles":{"type":"array","items":{"type":"string"},"nullable":true},"segment_company_size_min":{"type":"integer","nullable":true},"segment_company_size_max":{"type":"integer","nullable":true},"segment_geographies":{"type":"array","items":{"type":"string"},"nullable":true},"messaging_angle":{"type":"string","nullable":true},"messaging_key_messages":{"type":"array","items":{"type":"string"},"nullable":true},"hypothesis":{"type":"string","nullable":true},"outcome_notes":{"type":"string","nullable":true},"status":{"type":"string","enum":["draft","active","archived"]}}},"PlaybookResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Playbook"},"request_id":{"type":"string"}}},"PlaybookListResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/PlaybookSummary"}},"pagination":{"$ref":"#/components/schemas/Pagination"},"request_id":{"type":"string"}}},"PlaybookDetailResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/PlaybookDetail"},"request_id":{"type":"string"}}},"LinkPlaybookCampaignsRequest":{"type":"object","required":["TeamId","campaignIds"],"properties":{"TeamId":{"type":"string","format":"uuid"},"campaignIds":{"type":"array","items":{"type":"string","format":"uuid"},"minItems":1,"maxItems":500}}},"LinkPlaybookCampaignsResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"playbook_id":{"type":"string","format":"uuid"},"updated_count":{"type":"integer","minimum":0}}},"request_id":{"type":"string"}}},"Playbook":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"status":{"type":"string","enum":["draft","active","archived"]},"offer_type":{"type":"string","nullable":true},"TeamId":{"type":"string","format":"uuid"},"OrgId":{"type":"string","format":"uuid"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"PlaybookSummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"status":{"type":"string"},"offer_type":{"type":"string","nullable":true},"campaign_count":{"type":"integer"},"total_sends":{"type":"integer"},"reply_rate":{"type":"number"},"best_campaign":{"type":"object","nullable":true,"description":"Linked campaign with highest reply rate among campaigns with sends (matches playbook detail rollup)","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"reply_rate":{"type":"number"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"PlaybookDetail":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"status":{"type":"string"},"offer":{"type":"object","properties":{"value_proposition":{"type":"string","nullable":true},"type":{"type":"string","nullable":true},"cta":{"type":"string","nullable":true}}},"segment":{"type":"object","properties":{"description":{"type":"string","nullable":true},"industries":{"type":"array","items":{"type":"string"},"nullable":true},"roles":{"type":"array","items":{"type":"string"},"nullable":true},"company_size_min":{"type":"integer","nullable":true},"company_size_max":{"type":"integer","nullable":true},"geographies":{"type":"array","items":{"type":"string"},"nullable":true}}},"messaging":{"type":"object","properties":{"angle":{"type":"string","nullable":true},"key_messages":{"type":"array","items":{"type":"string"},"nullable":true}}},"hypothesis":{"type":"string","nullable":true},"outcome_notes":{"type":"string","nullable":true},"performance":{"type":"object","properties":{"campaign_count":{"type":"integer"},"total_sends":{"type":"integer"},"total_replies":{"type":"integer"},"reply_rate":{"type":"number"},"total_interested":{"type":"integer"},"total_meetings_booked":{"type":"integer"},"best_campaign":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"name":{"type":"string"},"reply_rate":{"type":"number"}}},"reply_rate_trend":{"type":"number","nullable":true}}},"linked_campaign_ids":{"type":"array","description":"IDs of all campaigns linked to this playbook (for bulk/modal use regardless of pagination).","items":{"type":"string","format":"uuid"}},"campaigns_total":{"type":"integer","description":"Count of linked campaigns matching the current `campaigns_*` filters (equals full linked count when not paginating)."},"campaigns":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"sends":{"type":"integer"},"replies":{"type":"integer"},"reply_rate":{"type":"number"},"interested":{"type":"integer"}}}}}},"SetupStatusResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SetupStatus"}}}]},"SetupStatus":{"type":"object","properties":{"object":{"type":"string","example":"setup_status"},"ready_to_send":{"type":"boolean","description":"True when billing allows sending and at least one sender profile has an active inbox."},"team":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"billing":{"type":"object","properties":{"status":{"type":"string","enum":["trialing","active","needs_card","inactive"],"description":"`trialing`: in the free trial. `active`: on a plan. `needs_card`: no card yet (add one to\nstart the trial or pick a plan). `inactive`: billing lapsed and must be reactivated.\n"},"can_send":{"type":"boolean"},"plan":{"type":"string","nullable":true},"trial_available":{"type":"boolean","description":"True when adding a card starts the free trial (status is `needs_card` and the trial wasn't used)."},"trial_ends_at":{"type":"string","format":"date-time","nullable":true,"description":"Set only while `trialing`."}}},"inboxes":{"type":"object","properties":{"total":{"type":"integer"},"active":{"type":"integer"},"disabled":{"type":"integer"},"warming":{"type":"integer","description":"Active inboxes in warmup for less than 14 days."},"warmed":{"type":"integer","description":"Active inboxes in warmup for 14 days or more."},"not_warming":{"type":"integer","description":"Active inboxes with warmup off."},"in_sender_profile":{"type":"integer","description":"Active inboxes assigned to a sender profile."},"days_until_first_warmed":{"type":"integer","nullable":true,"description":"0 when an inbox is already warmed; days until the first one is; null when none are warming."}}},"sender_profiles":{"type":"object","properties":{"total":{"type":"integer"},"with_active_inboxes":{"type":"integer"}}},"linkedin":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Whether LinkedIn is available to this organization."},"accounts":{"type":"integer"}}},"campaigns":{"type":"object","description":"Top-level campaigns (subsequences not counted).","properties":{"total":{"type":"integer"},"active":{"type":"integer"},"drafts":{"type":"integer"}}},"next_steps":{"type":"array","description":"What's still missing, in order. Empty when nothing is.","items":{"$ref":"#/components/schemas/SetupNextStep"}},"links":{"type":"object","description":"Dashboard links for steps that need a browser. Each opens the sign-in page first if needed.","properties":{"add_card":{"type":"string","format":"uri"},"billing":{"type":"string","format":"uri"},"connect_google_or_outlook":{"type":"string","format":"uri"},"connect_smtp":{"type":"string","format":"uri"},"buy_inboxes":{"type":"string","format":"uri"},"connect_linkedin":{"type":"string","format":"uri","description":"Omitted when LinkedIn isn't enabled for the organization."}}}}},"SetupNextStep":{"type":"object","required":["id","who","why"],"properties":{"id":{"type":"string","enum":["add_card","reactivate_billing","add_inboxes","fix_disabled_inboxes","start_warmup","group_inboxes","create_campaign"]},"who":{"type":"string","enum":["human","agent","human_or_agent"]},"mcp_tool":{"type":"string","description":"MCP tool for the agent to call (agent steps).","example":"update_sender"},"api":{"type":"string","description":"Equivalent API call (agent steps).","example":"PATCH /v2/senders/{id} {\"warm\": true}"},"url":{"type":"string","format":"uri","description":"Dashboard link for the human (human steps)."},"why":{"type":"string"},"inboxes":{"type":"array","description":"`fix_disabled_inboxes` only. Up to 25 disabled inboxes.","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string"},"status":{"type":"string","nullable":true}}}},"sender_ids":{"type":"array","description":"`start_warmup` only. Up to 25 inbox IDs with warmup off.","items":{"type":"string","format":"uuid"}}}},"InboxCheckoutRequest":{"type":"object","required":["domains"],"properties":{"team_id":{"type":"string","format":"uuid","description":"Team the inboxes are for. Defaults to the caller's default team."},"domains":{"type":"array","description":"Domains to buy, or that the organization already has (bought or connected). Each domain must be unique.","minItems":1,"maxItems":10,"items":{"type":"object","required":["domain","provider"],"properties":{"domain":{"type":"string","example":"tryacme.com"},"provider":{"type":"string","enum":["smtp","outlook","google","azure"],"description":"`azure` = Microsoft 365 on its own Azure tenant: exactly 50 inboxes per domain for $49/month."}}}},"mailboxes":{"type":"array","description":"Inboxes to create. Each mailbox's domain must be in `domains`, and every non-azure domain needs at least one. Azure domains are filled to 50 automatically; mailboxes listed for them only set the sender names.","maxItems":50,"items":{"type":"object","required":["username","domain","first_name","last_name"],"properties":{"username":{"type":"string","pattern":"^[a-z0-9._-]+$","maxLength":64,"description":"Part before the @ (lowercased)."},"domain":{"type":"string"},"first_name":{"type":"string","minLength":1,"maxLength":100},"last_name":{"type":"string","minLength":1,"maxLength":100},"signature":{"type":"string","maxLength":5000}}}}}},"DomainSetupResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/DomainSetup"}}}]},"DomainSetup":{"type":"object","properties":{"object":{"type":"string","example":"domain_setup"},"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"managed":{"type":"string","enum":["internal","external"],"description":"internal = bought through SuperSend; external = connected (registered elsewhere)"},"status":{"type":"string"},"team_id":{"type":"string","format":"uuid","nullable":true},"team":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"stage":{"type":"string","enum":["connected","preparing_nameservers","awaiting_nameservers","ready","setting_up","unavailable"]},"nameservers":{"type":"object","properties":{"change_required":{"type":"boolean","description":"The owner must change the domain's nameservers to `set_to` at the registrar."},"set_to":{"type":"array","nullable":true,"items":{"type":"string"}},"current":{"type":"array","nullable":true,"items":{"type":"string"},"description":"Nameservers last seen in DNS."},"verified":{"type":"boolean"},"verified_at":{"type":"string","format":"date-time","nullable":true}}},"mailboxes":{"type":"object","properties":{"total":{"type":"integer"},"active":{"type":"integer"},"in_progress":{"type":"integer"},"failed":{"type":"integer"},"items":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"provider":{"type":"string","enum":["google","outlook","azure","smtp"]},"status":{"type":"string","enum":["pending","processing","active","error"]}}}}}},"next":{"type":"string","description":"The one thing to do next, in plain words."},"already_connected":{"type":"boolean","description":"Connect only. The domain was already in the organization."},"check":{"type":"object","description":"Verify only.","properties":{"checked_at":{"type":"string","format":"date-time"},"looked_up":{"type":"boolean","description":"Nameservers were looked up live in this call."},"error":{"type":"string"}}}}},"InboxCheckoutResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/InboxCheckout"}}}]},"InboxCheckout":{"type":"object","properties":{"object":{"type":"string","example":"inbox_checkout"},"url":{"type":"string","format":"uri","description":"Prefilled dashboard checkout for the human to review and pay."},"team":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"domains":{"type":"array","items":{"type":"object","properties":{"domain":{"type":"string"},"provider":{"type":"string","enum":["smtp","outlook","google","azure"]},"owned":{"type":"boolean","description":"Already in the organization; no domain charge."},"id":{"type":"string","format":"uuid","description":"Domain ID (owned domains only)."},"price_per_year":{"type":"number","description":"USD (0 for owned domains)"},"nameserver_change_required":{"type":"boolean","description":"Owned domains only. True for a connected domain registered outside SuperSend whose nameservers must change after payment (get them from `POST /domains/{id}/verify`)."}}}},"mailboxes":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"provider":{"type":"string","enum":["smtp","outlook","google"]}}}},"azure_domains":{"type":"array","description":"Azure domains in the cart (50 inboxes each).","items":{"type":"object","properties":{"domain":{"type":"string"},"mailboxes":{"type":"integer","example":50},"price_per_month":{"type":"number","example":49}}}},"estimated_cost":{"type":"object","properties":{"currency":{"type":"string","example":"usd"},"domains_per_year":{"type":"number"},"mailboxes_per_month":{"type":"number"}}},"next":{"type":"string","description":"What to tell the user next."}}}}}}