{"schemes":["https"],"swagger":"2.0","info":{"description":"The Emailchaser API. The base URL is https://api.emailchaser.com/r. Every request sends your API key in the Authorization header, with the word Bearer and a space in front of it: `Authorization: Bearer <API key>`. A key sent without Bearer is refused with 401.","title":"Emailchaser API","contact":{},"version":"1.0"},"host":"api.emailchaser.com","basePath":"/r","paths":{"/audience/size":{"post":{"security":[{"ApiKeyAuth":[]}],"description":"Returns the number of prospects matching the given titles, seniorities, industries, company sizes and locations. Searching is free and never spends credits; only revealing contact details is metered.","consumes":["application/json"],"produces":["application/json"],"tags":["Audience"],"summary":"Count how many people match targeting criteria (free)","parameters":[{"description":"Targeting criteria","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.AudienceSizeRequest"}}],"responses":{"200":{"description":"Matching audience size","schema":{"$ref":"#/definitions/models.AudienceCountResponse"}},"400":{"description":"Empty or invalid criteria","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"502":{"description":"Data provider unavailable","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/autopilot/plan":{"post":{"security":[{"ApiKeyAuth":[]}],"description":"Turns a monthly budget into a concrete outbound setup: domains, mailboxes, sending volume, prospects and costs. expectedMeetingsPerMonth is a planning ESTIMATE on pessimistic funnel assumptions, not a promise. Changes nothing, but needs a read and write API key.","consumes":["application/json"],"produces":["application/json"],"tags":["Autopilot"],"summary":"Size an autopilot budget plan","parameters":[{"description":"Monthly budget in dollars","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.AutopilotPlanRequest"}}],"responses":{"200":{"description":"The sized plan","schema":{"$ref":"#/definitions/models.AutopilotPlanResponse"}},"400":{"description":"Invalid request body","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}}}}},"/autopilot/runs":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Lists the workspace's autopilot runs, newest first.","produces":["application/json"],"tags":["Autopilot"],"summary":"List autopilot runs","parameters":[{"type":"integer","description":"Page size (default 50, max 200)","name":"limit","in":"query"},{"type":"integer","description":"Page number, 1-based (default 1)","name":"page","in":"query"}],"responses":{"200":{"description":"Runs and total count","schema":{"$ref":"#/definitions/models.ListAutopilotRunsResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"500":{"description":"Failed to retrieve autopilot runs","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}},"post":{"security":[{"ApiKeyAuth":[]}],"description":"Creates an autopilot run for the given website. The run works autonomously up to the approval gate: it builds the customer profile, drafts the sequence and checks, without spending credits, that prospects match the profile. Nothing is spent, bought or sent before a human approves the run: no credits are reserved and no prospect is revealed until then, and approving is what reveals the first batch of prospects. When budgetUsd is given the sized budget plan is returned and snapshotted on the run, and targetProspects defaults to the plan's monthly prospect capacity. The plan's domain and mailbox counts are a ceiling, not a quote: one infrastructure order buys at most 10 domains, can place fewer mailboxes than the plan, and buys nothing when the workspace already has a connected sending account. Its dollar figures are an estimate, and an order is charged at the prices in force when it is placed. replyMode controls how inbound replies are handled (off, draft, approve, auto; default draft); auto sends AI reply drafts for interested replies without review. Available on every plan, and requires an active subscription. A workspace may have only one run awaiting approval at a time, and a limited number of starts per day.","consumes":["application/json"],"produces":["application/json"],"tags":["Autopilot"],"summary":"Start an autopilot run","parameters":[{"description":"Website and optional budget","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.StartAutopilotRunRequest"}}],"responses":{"201":{"description":"The created run, with the plan when a budget was given","schema":{"$ref":"#/definitions/models.AutopilotRunResult"}},"400":{"description":"Invalid request body","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"402":{"description":"The workspace does not hold enough prospect credits for the first batch the run reveals once approved: the run's prospect target, capped at what a single increment can reserve, priced at credits.ActionProspectReveal credits per prospect. A pre-flight check only; starting reserves nothing (code insufficient_credits, with have/need counts)","schema":{"$ref":"#/definitions/models.InsufficientCreditsErrorResponse"}},"403":{"description":"Autopilot requires an active subscription","schema":{"$ref":"#/definitions/models.ErrorForbidden"}},"429":{"description":"A run is already awaiting approval, or too many runs have been started today","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"500":{"description":"Failed to start the autopilot run","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/autopilot/runs/{id}":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Returns one run, including its audit trail. Runs belonging to other workspaces are reported as not found.","produces":["application/json"],"tags":["Autopilot"],"summary":"Get an autopilot run","parameters":[{"type":"integer","description":"Run ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"The run","schema":{"$ref":"#/definitions/models.AutopilotRunResult"}},"400":{"description":"Invalid run id","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Run not found","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"500":{"description":"Failed to retrieve the run","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/autopilot/runs/{id}/approve":{"post":{"security":[{"ApiKeyAuth":[]}],"description":"Approves a run that is awaiting approval, recording who approved it. Approval authorises the run's spend: the run moves to sourcing_prospects and reveals its first batch of prospects into the campaign, which spends prospect credits, then moves to provisioning_infrastructure to buy the budgeted sending infrastructure (if any), and then launches unattended. Nothing is spent, bought or sent before this call. A batch that delivers no prospects fails the run before any infrastructure is bought.","produces":["application/json"],"tags":["Autopilot"],"summary":"Approve an autopilot run","parameters":[{"type":"integer","description":"Run ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"The approved run","schema":{"$ref":"#/definitions/models.AutopilotRunResult"}},"400":{"description":"Invalid run id","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Run not found","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"409":{"description":"The run is not waiting for approval, or has been halted by the kill switch","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"500":{"description":"Failed to approve the run","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/autopilot/runs/{id}/kill":{"post":{"security":[{"ApiKeyAuth":[]}],"description":"Engages the kill switch: the run halts wherever it is and cannot be resumed, and a campaign the run launched is paused so nothing more sends. The optional reason is recorded in the audit trail.","consumes":["application/json"],"produces":["application/json"],"tags":["Autopilot"],"summary":"Kill an autopilot run","parameters":[{"type":"integer","description":"Run ID","name":"id","in":"path","required":true},{"description":"Optional reason","name":"request","in":"body","schema":{"$ref":"#/definitions/models.KillAutopilotRunRequest"}}],"responses":{"200":{"description":"The halted run","schema":{"$ref":"#/definitions/models.AutopilotRunResult"}},"400":{"description":"Invalid run id","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Run not found","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"500":{"description":"Failed to stop the run","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/autopilot/runs/{id}/pause":{"post":{"security":[{"ApiKeyAuth":[]}],"description":"Suspends a run that is still in flight. A paused run can be resumed; pausing never skips the approval gate. A completed or failed run cannot be paused.","produces":["application/json"],"tags":["Autopilot"],"summary":"Pause an autopilot run","parameters":[{"type":"integer","description":"Run ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"The paused run","schema":{"$ref":"#/definitions/models.AutopilotRunResult"}},"400":{"description":"Invalid run id","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Run not found","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"409":{"description":"The run has finished and cannot be paused","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"500":{"description":"Failed to pause the run","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/autopilot/runs/{id}/resume":{"post":{"security":[{"ApiKeyAuth":[]}],"description":"Resumes a paused run. A run paused before approval goes back to awaiting approval — resuming can never skip the gate. A failed run resumes to the stage it failed from once the cause (e.g. insufficient credits) is fixed.","produces":["application/json"],"tags":["Autopilot"],"summary":"Resume an autopilot run","parameters":[{"type":"integer","description":"Run ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"The resumed run","schema":{"$ref":"#/definitions/models.AutopilotRunResult"}},"400":{"description":"Invalid run id","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Run not found","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"409":{"description":"The run has been halted by the kill switch, or cannot be resumed","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"500":{"description":"Failed to resume the run","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/blocklist":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Returns the suppression entries (blocked domains and email addresses) that apply to the calling key's workspace, newest first. By default that is both lists: the workspace's own entries and the account-wide ones inherited from the main workspace. Narrow with `scope=workspace` or `scope=global`. Each item reports which list it came from.","produces":["application/json"],"tags":["Blocklist"],"summary":"List blocklist entries","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"enum":["workspace","global","all"],"type":"string","description":"Which list to return: workspace, global, or all (default all)","name":"scope","in":"query"},{"type":"string","description":"Case-insensitive substring match on the domain or email address","name":"search","in":"query"},{"type":"integer","description":"Page size (default 100, max 1000)","name":"limit","in":"query"},{"type":"integer","description":"Offset (default 0)","name":"offset","in":"query"}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/models.BlocklistListResponse"}},"400":{"description":"Invalid scope","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"500":{"description":"Failed to list entries","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}},"post":{"security":[{"ApiKeyAuth":[]}],"description":"Adds up to 1000 suppression entries in one call. Each value is either a full email address (contains \"@\") or a bare domain. Existing entries and invalid values are skipped, so the call is safe to retry and suited to carrying over a suppression list from another sending platform. `scope` defaults to \"workspace\", which suppresses for the calling key's workspace only; \"global\" suppresses across every workspace on the account and requires a main workspace's key.","consumes":["application/json"],"produces":["application/json"],"tags":["Blocklist"],"summary":"Add blocklist entries","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"description":"Values to block","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.BlocklistAddRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/models.BlocklistAddResponse"}},"400":{"description":"Invalid request body","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"403":{"description":"Only a main workspace's key may write the account-wide blocklist","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"500":{"description":"Failed to add entries","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}},"delete":{"security":[{"ApiKeyAuth":[]}],"description":"Removes up to 1000 entries in one call, by id or by value. Values are matched case-insensitively against both the domain and the email-address column, so unblocking \"acme.com\" removes the domain entry, not the individual addresses on it. `scope` limits the delete to one list and defaults to \"workspace\"; \"all\" covers both, and anything touching the account-wide list requires a main workspace's key. Entries that match nothing the caller may delete are counted as skipped rather than failing the call.","consumes":["application/json"],"produces":["application/json"],"tags":["Blocklist"],"summary":"Delete blocklist entries","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"description":"Entries to remove","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.BlocklistDeleteRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/models.BlocklistDeleteResponse"}},"400":{"description":"Invalid request body","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"403":{"description":"Only a main workspace's key may write the account-wide blocklist","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"500":{"description":"Failed to delete entries","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/blocklist/{id}":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Returns one suppression entry by id. Entries inherited from the account-wide list are visible here too, and report scope \"global\".","produces":["application/json"],"tags":["Blocklist"],"summary":"Get a blocklist entry","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Blocklist entry ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/models.BlocklistEntry"}},"400":{"description":"Invalid id","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Entry not found","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"500":{"description":"Failed to load entry","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}},"put":{"security":[{"ApiKeyAuth":[]}],"description":"Changes the blocked value, the scope, or both. A value containing \"@\" is stored as an email address, otherwise as a domain, so an entry can be converted between the two. Moving an entry to or from the account-wide list requires a main workspace's key, and moves the entry onto the main workspace.","consumes":["application/json"],"produces":["application/json"],"tags":["Blocklist"],"summary":"Update a blocklist entry","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Blocklist entry ID","name":"id","in":"path","required":true},{"description":"Fields to change","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.BlocklistUpdateRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/models.BlocklistEntry"}},"400":{"description":"Invalid request body or id","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"403":{"description":"Only a main workspace's key may write the account-wide blocklist","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"404":{"description":"Entry not found","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"409":{"description":"That value is already blocked in the target list","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"500":{"description":"Failed to update entry","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}},"delete":{"security":[{"ApiKeyAuth":[]}],"description":"Removes one suppression entry, unblocking the domain or address. A sub-workspace key cannot delete an account-wide entry it merely inherits.","produces":["application/json"],"tags":["Blocklist"],"summary":"Delete a blocklist entry","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Blocklist entry ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/models.BlocklistDeleteEntryResponse"}},"400":{"description":"Invalid id","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"403":{"description":"Only a main workspace's key may write the account-wide blocklist","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"404":{"description":"Entry not found","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"500":{"description":"Failed to delete entry","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/campaigns":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Lists all campaigns for the authenticated user's space with optional filtering by status and folder. Supports pagination.","consumes":["application/json"],"produces":["application/json"],"tags":["Campaigns"],"summary":"List campaigns","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Page number (default: 1)","name":"page","in":"query"},{"type":"integer","description":"Page size (default: 20, maximum: 200)","name":"limit","in":"query"},{"enum":["not_started","running","completed","paused","draft"],"type":"string","description":"Filter by status","name":"status","in":"query"},{"type":"integer","description":"Filter by folder ID","name":"folderId","in":"query"}],"responses":{"200":{"description":"List of campaigns","schema":{"$ref":"#/definitions/models.ListCampaignsResponse"}},"400":{"description":"Invalid query parameters","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"500":{"description":"Failed to retrieve campaigns","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}},"post":{"security":[{"ApiKeyAuth":[]}],"description":"Creates a campaign in the API key's workspace. The campaign starts in draft status and is placed in the workspace's default folder. Before it can be launched with POST /campaigns/{id}/resume, it needs four things: a sequence (PUT /campaigns/{id}/sequence), sending days (PUT /campaigns/{id}/schedule; there is no default), at least one email account (POST /campaigns/{id}/sender-emails), and at least one lead (POST /leads with campaignId). Campaigns with flow api can launch without leads.","consumes":["application/json"],"produces":["application/json"],"tags":["Campaigns"],"summary":"Create a campaign","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"description":"Campaign to create","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.CreateCampaignRequest"}}],"responses":{"201":{"description":"Campaign created successfully","schema":{"$ref":"#/definitions/models.CreateCampaignResponse"}},"400":{"description":"Invalid request body","schema":{"$ref":"#/definitions/models.ErrorInvalidRequest"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"500":{"description":"Failed to create campaign","schema":{"$ref":"#/definitions/models.ErrorFailedToCreateCampaign"}}}}},"/campaigns/{id}":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Retrieves detailed information about a specific campaign including settings, schedule, statistics, and email counts. schedule.daysSchedule is one comma-separated string of weekday numbers, such as \"1,2,3,4,5\", not an array. minimumHealth lists each connected email account on the campaign with its health score and whether the campaign's minimum health score (settings.minimumHealthScore) holds it back; when every account is held back, allHeldBack is true and message says the campaign is sending nothing.","consumes":["application/json"],"produces":["application/json"],"tags":["Campaigns"],"summary":"Get a campaign by ID","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Campaign ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"Campaign details","schema":{"$ref":"#/definitions/models.GetCampaignResponse"}},"400":{"description":"Invalid campaign ID","schema":{"$ref":"#/definitions/models.ErrorInvalidCampaignID"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Campaign not found","schema":{"$ref":"#/definitions/models.ErrorCampaignNotFound"}},"500":{"description":"Failed to retrieve campaign","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}},"put":{"security":[{"ApiKeyAuth":[]}],"description":"Updates campaign properties such as name, emoji, timezone, and various settings. Only provided fields will be updated. minimumHealthScore (1-100) stops any email account whose health score is below it, or that has no score yet, from sending in this campaign until its score is back up; its conversations wait for it and never move to another account. Send 0 to remove the minimum.","consumes":["application/json"],"produces":["application/json"],"tags":["Campaigns"],"summary":"Update a campaign by ID","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Campaign ID","name":"id","in":"path","required":true},{"description":"Campaign fields to update","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.UpdateCampaignRequest"}}],"responses":{"200":{"description":"Campaign updated successfully","schema":{"$ref":"#/definitions/models.UpdateCampaignResponse"}},"400":{"description":"Invalid campaign ID or request body","schema":{"$ref":"#/definitions/models.ErrorInvalidCampaignID"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Campaign not found","schema":{"$ref":"#/definitions/models.ErrorCampaignNotFound"}},"500":{"description":"Failed to update campaign","schema":{"$ref":"#/definitions/models.ErrorFailedToUpdateCampaign"}}}},"delete":{"security":[{"ApiKeyAuth":[]}],"description":"Deletes a campaign and all associated data including emails, sequences, and scheduled tasks.","consumes":["application/json"],"produces":["application/json"],"tags":["Campaigns"],"summary":"Delete a campaign","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Campaign ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"Campaign deleted successfully","schema":{"$ref":"#/definitions/models.DeleteCampaignResponse"}},"400":{"description":"Invalid campaign ID","schema":{"$ref":"#/definitions/models.ErrorInvalidCampaignID"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Campaign not found","schema":{"$ref":"#/definitions/models.ErrorCampaignNotFound"}},"500":{"description":"Failed to delete campaign","schema":{"$ref":"#/definitions/models.ErrorFailedToDeleteCampaign"}}}}},"/campaigns/{id}/leads/{leadId}/move":{"post":{"security":[{"ApiKeyAuth":[]}],"description":"Removes a lead from the source campaign (deleting its unsent emails) and adds it to the target campaign. If the target campaign is running, emails for the lead are enqueued.","consumes":["application/json"],"produces":["application/json"],"tags":["Campaigns"],"summary":"Move a lead to another campaign","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Source campaign ID","name":"id","in":"path","required":true},{"type":"integer","description":"Lead ID","name":"leadId","in":"path","required":true},{"description":"Target campaign","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.MoveLeadRequest"}}],"responses":{"200":{"description":"Lead moved successfully","schema":{"$ref":"#/definitions/models.MoveLeadResponse"}},"400":{"description":"Invalid campaign ID, lead ID or request body","schema":{"$ref":"#/definitions/models.ErrorInvalidCampaignID"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"403":{"description":"Forbidden - lead does not belong to your space","schema":{"$ref":"#/definitions/models.ErrorForbidden"}},"404":{"description":"Campaign, target campaign or lead not found","schema":{"$ref":"#/definitions/models.ErrorCampaignNotFound"}},"500":{"description":"Failed to move lead","schema":{"$ref":"#/definitions/models.ErrorFailedToMoveLead"}}}}},"/campaigns/{id}/pause":{"post":{"security":[{"ApiKeyAuth":[]}],"description":"Pauses a running campaign and cancels all scheduled emails.","consumes":["application/json"],"produces":["application/json"],"tags":["Campaigns"],"summary":"Pause a campaign","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Campaign ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"Campaign paused successfully","schema":{"$ref":"#/definitions/models.PauseCampaignResponse"}},"400":{"description":"Invalid campaign ID","schema":{"$ref":"#/definitions/models.ErrorInvalidCampaignID"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Campaign not found","schema":{"$ref":"#/definitions/models.ErrorCampaignNotFound"}},"500":{"description":"Failed to pause campaign","schema":{"$ref":"#/definitions/models.ErrorFailedToPauseCampaign"}}}}},"/campaigns/{id}/resume":{"post":{"security":[{"ApiKeyAuth":[]}],"description":"Launches a new campaign or resumes a paused campaign. Validates campaign configuration before launching: the campaign needs sending days, at least one email account, a sequence, and, unless its flow is api, at least one lead. A missing one is refused with 400 and an error naming it.","consumes":["application/json"],"produces":["application/json"],"tags":["Campaigns"],"summary":"Launch or resume a campaign","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Campaign ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"Campaign launched/resumed successfully","schema":{"$ref":"#/definitions/models.LaunchCampaignResponse"}},"400":{"description":"Campaign validation failed (missing configuration)","schema":{"$ref":"#/definitions/models.ErrorCampaignValidation"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Campaign not found","schema":{"$ref":"#/definitions/models.ErrorCampaignNotFound"}},"409":{"description":"Campaign is currently processing leads","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"500":{"description":"Failed to launch/resume campaign","schema":{"$ref":"#/definitions/models.ErrorFailedToLaunchCampaign"}}}}},"/campaigns/{id}/schedule":{"put":{"security":[{"ApiKeyAuth":[]}],"description":"Updates the sending schedule for a campaign including timezone, days, time windows, and frequency. daysSchedule is sent as an array of days, but the response returns it as one comma-separated string of weekday numbers, such as \"1,2,3,4,5\". GET /campaigns/{id} returns it the same way.","consumes":["application/json"],"produces":["application/json"],"tags":["Campaigns"],"summary":"Update campaign schedule","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Campaign ID","name":"id","in":"path","required":true},{"description":"Schedule configuration","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.UpdateCampaignScheduleRequest"}}],"responses":{"200":{"description":"Campaign schedule updated successfully","schema":{"$ref":"#/definitions/models.UpdateCampaignScheduleResponse"}},"400":{"description":"Invalid request body or schedule configuration","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Campaign not found","schema":{"$ref":"#/definitions/models.ErrorCampaignNotFound"}},"500":{"description":"Failed to update campaign schedule","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/campaigns/{id}/sender-emails":{"post":{"security":[{"ApiKeyAuth":[]}],"description":"Attaches connected sender emails from your workspace to a campaign. A running campaign only sends through the sender emails attached to it, so this call decides which mailboxes the campaign uses. Idempotent: already-attached senders are left untouched and only missing ones are added. The call is refused whole when any requested sender email is not yours (404) or not connected (409). Attaching to a running campaign triggers a reschedule so the new senders are picked up.","consumes":["application/json"],"produces":["application/json"],"tags":["Campaigns"],"summary":"Attach sender emails to a campaign","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Campaign ID","name":"id","in":"path","required":true},{"description":"Sender emails to attach","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.AttachCampaignSendersRequest"}}],"responses":{"200":{"description":"Sender emails attached","schema":{"$ref":"#/definitions/models.AttachCampaignSendersResponse"}},"400":{"description":"Invalid campaign ID or request body","schema":{"$ref":"#/definitions/models.ErrorInvalidCampaignID"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Campaign or sender email not found","schema":{"$ref":"#/definitions/models.ErrorCampaignNotFound"}},"409":{"description":"Sender email is not connected","schema":{"$ref":"#/definitions/models.ErrorSenderEmailNotConnected"}},"500":{"description":"Failed to attach sender emails","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/campaigns/{id}/sender-emails/{senderEmailId}":{"delete":{"security":[{"ApiKeyAuth":[]}],"description":"Detaches a sender email from a campaign. A running campaign only sends through the sender emails attached to it, so after this call the campaign stops sending through this mailbox: unsent follow-ups to emails this sender already sent are canceled, the sender is removed from the campaign's unsent emails, and a running campaign is rescheduled onto its remaining senders. Emails already sent are untouched. Idempotent: detaching a sender that is not attached is a no-op.","consumes":["application/json"],"produces":["application/json"],"tags":["Campaigns"],"summary":"Detach a sender email from a campaign","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Campaign ID","name":"id","in":"path","required":true},{"type":"integer","description":"Sender Email ID","name":"senderEmailId","in":"path","required":true}],"responses":{"200":{"description":"Sender email detached","schema":{"$ref":"#/definitions/models.DetachCampaignSenderResponse"}},"400":{"description":"Invalid campaign or sender email ID","schema":{"$ref":"#/definitions/models.ErrorInvalidCampaignID"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Campaign or sender email not found","schema":{"$ref":"#/definitions/models.ErrorCampaignNotFound"}},"500":{"description":"Failed to detach sender email","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/campaigns/{id}/sequence":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Returns the ordered emails that make up a campaign's sequence, including any A/B variants. delayDays on each step is counted from the previous step; the first step is always 0.","consumes":["application/json"],"produces":["application/json"],"tags":["Campaigns"],"summary":"Get a campaign's sequence","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Campaign ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"Campaign sequence","schema":{"$ref":"#/definitions/models.GetSequenceResponse"}},"400":{"description":"Invalid campaign ID","schema":{"$ref":"#/definitions/models.ErrorInvalidCampaignID"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Campaign not found","schema":{"$ref":"#/definitions/models.ErrorCampaignNotFound"}},"500":{"description":"Failed to retrieve campaign sequence","schema":{"$ref":"#/definitions/models.ErrorFailedToRetrieveSequence"}}}},"put":{"security":[{"ApiKeyAuth":[]}],"description":"Replaces the campaign's whole sequence with the steps provided. The operation is idempotent, so sending the same payload twice leaves the same state and a retried request cannot duplicate steps. delayDays is counted from the previous step and the first step must be 0. Editing a running campaign is allowed and re-queues future sends; already-sent emails are untouched. Merge tags take ONE brace, e.g. {first_name}, {last_name}, {company_name}, {job_title}, plus any key set in a lead's customVariables; a fallback is {first_name}[there] or {first_name|there}. Placeholders written in another tool's syntax ({{first_name}}, [[first_name]], [First Name]) are rejected with a 400 that names the tag to write instead, because they are not substituted and would be sent exactly as typed (\"Hi [First Name],\").","consumes":["application/json"],"produces":["application/json"],"tags":["Campaigns"],"summary":"Replace a campaign's sequence","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Campaign ID","name":"id","in":"path","required":true},{"description":"Sequence steps","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.ReplaceSequenceRequest"}}],"responses":{"200":{"description":"Campaign sequence replaced successfully","schema":{"$ref":"#/definitions/models.ReplaceSequenceResponse"}},"400":{"description":"Invalid campaign ID or sequence payload","schema":{"$ref":"#/definitions/models.ErrorInvalidSequence"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Campaign not found","schema":{"$ref":"#/definitions/models.ErrorCampaignNotFound"}},"422":{"description":"Campaign flow does not support sequences","schema":{"$ref":"#/definitions/models.ErrorUnsupportedCampaignFlow"}},"500":{"description":"Failed to replace campaign sequence","schema":{"$ref":"#/definitions/models.ErrorFailedToReplaceSequence"}}}}},"/campaigns/{id}/stats":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Returns campaign performance in one call: totals, per-A/B-variant results, per-sequence-step results, and a daily (UTC) time series. Sent and bounced count outbound emails; replied and positive count distinct leads; totals.meetings counts the campaign's leads with a booked meeting recorded (see POST /leads/{id}/meeting). Variant sent counts initial emails only, while steps attribute replies to the exact email that was answered. There is no opens metric anywhere in this response: Emailchaser does not track opens, so its absence is deliberate, not an omission.","consumes":["application/json"],"produces":["application/json"],"tags":["Campaigns"],"summary":"Get campaign performance stats","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Campaign ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"Campaign performance stats","schema":{"$ref":"#/definitions/models.GetCampaignStatsResponse"}},"400":{"description":"Invalid campaign ID","schema":{"$ref":"#/definitions/models.ErrorInvalidCampaignID"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Campaign not found","schema":{"$ref":"#/definitions/models.ErrorCampaignNotFound"}},"500":{"description":"Failed to retrieve campaign stats","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/copilot/launch":{"post":{"security":[{"ApiKeyAuth":[]}],"description":"Creates a DRAFT Emailchaser campaign (with its email sequence) scoped to the caller's workspace. With a Sales Navigator search URL it also kicks off lead extraction; without one the draft holds the emails and leads can be added from Lead Finder or a CSV upload. salesNavData needs a salesNavSearchUrl. The campaign stays in DRAFT — nothing sends automatically.","consumes":["application/json"],"produces":["application/json"],"tags":["Copilot"],"summary":"Launch a draft outbound campaign and start lead finding","parameters":[{"description":"Campaign name, sequence and an optional Sales Nav search URL","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.CopilotLaunchRequest"}}],"responses":{"202":{"description":"Draft campaign created, with lead finding started when a search was given","schema":{"$ref":"#/definitions/models.CopilotLaunchResponse"}},"400":{"description":"Invalid request body","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"500":{"description":"Failed to create draft campaign","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/copilot/plan":{"post":{"security":[{"ApiKeyAuth":[]}],"description":"Fetches the company website, infers an Ideal Customer Profile with the managed AI model, and drafts a suggested cold-email sequence. Changes nothing, but needs a read and write API key. A website that shows a placeholder page instead of the business (a parked or not yet connected domain, a default server page) is refused with 422; send a description of what you sell to plan from it instead.","consumes":["application/json"],"produces":["application/json"],"tags":["Copilot"],"summary":"Plan an outbound campaign from a website","parameters":[{"description":"Website to analyse","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.CopilotPlanRequest"}}],"responses":{"200":{"description":"Suggested ICP and sequence","schema":{"$ref":"#/definitions/models.CopilotPlanResponse"}},"400":{"description":"Invalid request body","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"422":{"description":"The website shows a placeholder page, not the business","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"429":{"description":"Too many AI requests for this workspace","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"502":{"description":"Failed to analyse website","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/credits/balance":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Returns the workspace's credit wallet. A wallet is created empty on first use, so a new workspace sees zeros rather than an error; a workspace on a free trial sees its one-time trial credits, which its first balance read hands over. unlimited is true when Emailchaser has made this workspace's credits free: credit actions then never spend available and are never refused for lack of credits, so there is no need to check the balance or buy credits.","produces":["application/json"],"tags":["Credits"],"summary":"Get the credit balance","responses":{"200":{"description":"The wallet snapshot","schema":{"$ref":"#/definitions/models.CreditBalanceResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"500":{"description":"Failed to load the credit balance","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/credits/purchase":{"post":{"security":[{"ApiKeyAuth":[]}],"description":"CHARGES REAL MONEY: this immediately charges the workspace's saved default payment method (the card behind the active subscription), off-session, with no confirmation step beyond this call. Buys between 1000 and 10000 prospect credits at the tiered list price ($33 per 1000 credits, $20 per 1000 from 5000 credits) and grants them to the wallet on success. Responds with the credits bought, the exact amount charged in USD and the new available balance. Errors carry a machine-readable code: billing_required (402, no active subscription or saved card - fix billing in the app first), payment_failed (402, the charge was refused - no money moved), credits_free (409, this workspace's credits are free, so nothing is charged and there is nothing to buy), invalid_request (400), temporarily_unavailable (503, the purchase stopped before any charge - safe to retry), purchase_incomplete (500, charged but not credited, support already notified - do NOT retry) and purchase_unconfirmed (500, check /credits/transactions before retrying). The grant is idempotent on the Stripe invoice, so one charge can never double-credit - but every successful call is a NEW charge.","consumes":["application/json"],"produces":["application/json"],"tags":["Credits"],"summary":"Buy credits (charges real money)","parameters":[{"description":"How many credits to buy (1000..10000)","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.PurchaseCreditsRequest"}}],"responses":{"200":{"description":"The completed purchase","schema":{"$ref":"#/definitions/models.PurchaseCreditsResponse"}},"400":{"description":"Malformed body or credits out of bounds (code invalid_request)","schema":{"$ref":"#/definitions/models.PurchaseCreditsErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"402":{"description":"No usable billing (code billing_required) or the charge was refused (code payment_failed)","schema":{"$ref":"#/definitions/models.PurchaseCreditsErrorResponse"}},"409":{"description":"Credits are free for this workspace, nothing was charged (code credits_free)","schema":{"$ref":"#/definitions/models.PurchaseCreditsErrorResponse"}},"500":{"description":"Charged but not credited (code purchase_incomplete, do not retry) or outcome unknown (code purchase_unconfirmed)","schema":{"$ref":"#/definitions/models.PurchaseCreditsErrorResponse"}},"503":{"description":"Stopped before any charge, safe to retry (code temporarily_unavailable)","schema":{"$ref":"#/definitions/models.PurchaseCreditsErrorResponse"}}}}},"/credits/transactions":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Lists the workspace's credit ledger entries, newest first. Optional filters: kind (movement type), reason (what the credits were for), and since/until on the entry time (both inclusive, RFC3339 or YYYY-MM-DD where a bare date means midnight UTC at the start of that day). Without filters the full ledger is returned as before. An entry with free=true was written while the workspace's credits were free: its amount is what the action would have cost, and no credits moved.","produces":["application/json"],"tags":["Credits"],"summary":"List credit transactions","parameters":[{"type":"integer","description":"Page size (default 50, max 200)","name":"limit","in":"query"},{"type":"integer","description":"Page number, 1-based (default 1)","name":"page","in":"query"},{"type":"string","description":"Filter by movement type: grant, topup, reserve, commit, refund, expire or adjustment","name":"kind","in":"query"},{"type":"string","description":"Filter by reason: monthly_grant, prospect_reveal, ai_icp, ai_sequence, ai_reply, stripe_topup or manual_adjustment","name":"reason","in":"query"},{"type":"string","description":"Only entries at or after this time, RFC3339 or YYYY-MM-DD","name":"since","in":"query"},{"type":"string","description":"Only entries at or before this time, RFC3339 or YYYY-MM-DD (a bare date means midnight UTC at the start of that day)","name":"until","in":"query"}],"responses":{"200":{"description":"Ledger entries and total count (both reflect the filters)","schema":{"$ref":"#/definitions/models.ListCreditTransactionsResponse"}},"400":{"description":"Invalid filter value","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"500":{"description":"Failed to retrieve credit transactions","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/dfy/domains/check":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Checks whether one specific domain can be registered, rather than searching the names the registrar suggests. Use this to buy a domain the customer chose. Only .com and .org are supported.\n\nA domain that simply cannot be bought - taken, malformed, an extension we do not register - answers 200 with available false and a reason. A non-2xx means the check itself could not be made, which is not the same as the domain being taken.","produces":["application/json"],"tags":["DoneForYou"],"summary":"Check one exact domain","parameters":[{"type":"string","description":"The full domain to check, extension included","name":"domain","in":"query","required":true}],"responses":{"200":{"description":"Availability of that exact domain","schema":{"$ref":"#/definitions/models.CheckDfyDomainResponse"}},"400":{"description":"Missing domain parameter","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"502":{"description":"Domain search service unavailable","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}},"post":{"security":[{"ApiKeyAuth":[]}],"description":"Checks up to 100 exact domains in one call, for a caller who already has the list. One answer per unique domain, in the order given, each following the single check's rules. A registrar failure on one name marks it unconfirmed rather than failing the list; the call fails only when nothing could be checked.","consumes":["application/json"],"produces":["application/json"],"tags":["DoneForYou"],"summary":"Check a list of exact domains","parameters":[{"description":"The domains to check","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.CheckDfyDomainsRequest"}}],"responses":{"200":{"description":"Availability of each domain","schema":{"$ref":"#/definitions/models.CheckDfyDomainsResponse"}},"400":{"description":"Empty list, or more than 100 domains","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"502":{"description":"Domain search service unavailable","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/dfy/domains/search":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Checks availability and pricing of domains derived from a brand name. Only .com and .org are supported.","produces":["application/json"],"tags":["DoneForYou"],"summary":"Search available domains","parameters":[{"type":"string","description":"Brand name to derive domains from","name":"query","in":"query","required":true},{"type":"string","description":"Comma-separated TLDs (default: com,org)","name":"tlds","in":"query"},{"type":"integer","description":"Maximum suggestions to return","name":"limit","in":"query"}],"responses":{"200":{"description":"Availability results","schema":{"$ref":"#/definitions/models.SearchDfyDomainsResponse"}},"400":{"description":"Invalid search input","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"502":{"description":"Domain search service unavailable","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/dfy/orders":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Lists the workspace's done-for-you orders, newest first. Each order carries the same fields as GET /dfy/orders/{id}: status, cost breakdown and the caller-supplied order shape; internal billing and provider sub-records are never exposed.","produces":["application/json"],"tags":["DoneForYou"],"summary":"List done-for-you orders","parameters":[{"type":"integer","description":"Page size (default 50, max 200)","name":"limit","in":"query"},{"type":"integer","description":"Page number, 1-based (default 1)","name":"page","in":"query"}],"responses":{"200":{"description":"Orders and total count","schema":{"$ref":"#/definitions/models.ListDfyOrdersResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"500":{"description":"Failed to retrieve the orders","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}},"post":{"security":[{"ApiKeyAuth":[]}],"description":"Orders domains and pre-warmed mailboxes for the caller's workspace. The order is accepted and provisioned asynchronously; poll GET /dfy/orders/{id} for progress.\n\nThis call buys domains and charges the card, and it can take longer than the connection is held open — a timeout or a 502 does NOT mean the order failed. Send an Idempotency-Key header and repeat the identical request to find out: a repeat of a key that already placed an order returns that order instead of buying anything again. Reuse the key to retry safely; use a NEW key only when you intend a genuinely different order.\n\nWithout a key the order is still checked against the workspace's live orders, and one that would buy a domain or a mailbox address already bought is refused with 409 and the id of the order that has it.","consumes":["application/json"],"produces":["application/json"],"tags":["DoneForYou"],"summary":"Create a done-for-you order","parameters":[{"type":"string","description":"Repeat this key to retry the same order safely (max 255 characters)","name":"Idempotency-Key","in":"header"},{"description":"Domains, mailboxes and forwarding domain","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.CreateDfyOrderRequest"}}],"responses":{"202":{"description":"The accepted order, or the order a previous attempt with the same Idempotency-Key already placed","schema":{"$ref":"#/definitions/models.DfyOrderResult"}},"400":{"description":"Invalid order","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"409":{"description":"A live order already covers one of these domains or mailboxes","schema":{"$ref":"#/definitions/models.DfyOrderConflictResponse"}},"500":{"description":"Failed to create the order","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/dfy/orders/{id}":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Returns one order. Orders belonging to other workspaces are reported as not found.","produces":["application/json"],"tags":["DoneForYou"],"summary":"Get a done-for-you order","parameters":[{"type":"integer","description":"Order ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"The order","schema":{"$ref":"#/definitions/models.DfyOrderResult"}},"400":{"description":"Invalid order id","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Order not found","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"500":{"description":"Failed to retrieve the order","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/email-verification/jobs":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Returns the workspace's standalone verification jobs, newest first, with live counts and what each has billed.","produces":["application/json"],"tags":["Email Verification"],"summary":"List email verification jobs","parameters":[{"type":"integer","default":1,"description":"Page number, from 1","name":"page","in":"query"},{"type":"integer","default":25,"description":"Page size, max 200","name":"size","in":"query"},{"type":"string","description":"Filter by job name","name":"search","in":"query"}],"responses":{"200":{"description":"A page of jobs","schema":{"$ref":"#/definitions/models.ListEmailVerificationJobsResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"500":{"description":"Failed to list jobs","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}},"post":{"security":[{"ApiKeyAuth":[]}],"description":"SPENDS MONEY: every address checked adds metered usage to the workspace's next invoice. No campaign is created and nothing is sent. Each check runs up to two providers in a waterfall and every provider that returns a verdict is one billable verification credit, so an address costs ONE credit when the first provider rejects it outright and TWO otherwise. Call GET /email-verification/rates for the current per-credit price and use per_address_ceiling_usd to budget. An address that gets no verdict (a provider failure, a cancelled job, or the workspace's monthly verification allowance running out mid-job) is NOT billed and comes back with result unknown, still in the download. Duplicates are removed and unparseable entries are returned in invalid_emails before anything is charged. Requires an active or trialing subscription: a past_due workspace is refused with subscription_not_active, and a trialing workspace may submit up to 100 addresses in total across all its jobs, after which it is refused with trial_allowance_exceeded. Errors carry a machine-readable code: insufficient_allowance (402, with needed_usd and available_usd), trial_allowance_exceeded (402, with allowance and remaining), subscription_not_active (402), no_active_subscription (402) and invalid_request (400).","consumes":["application/json"],"produces":["application/json"],"tags":["Email Verification"],"summary":"Verify a list of email addresses (spends money)","parameters":[{"description":"The list to verify","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.CreateEmailVerificationJobRequest"}}],"responses":{"200":{"description":"The created job and its cost estimate","schema":{"$ref":"#/definitions/models.CreateEmailVerificationJobResponse"}},"400":{"description":"Malformed body, empty name, or no usable addresses","schema":{"$ref":"#/definitions/models.EmailVerificationErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"402":{"description":"Not enough monthly verification allowance, the free trial allowance is used up, or the subscription is not active","schema":{"$ref":"#/definitions/models.EmailVerificationErrorResponse"}},"500":{"description":"Failed to create the job","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/email-verification/jobs/{id}":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Returns one job's progress and what it has billed so far. Poll this to know when state becomes done.","produces":["application/json"],"tags":["Email Verification"],"summary":"Get an email verification job","parameters":[{"type":"string","description":"Job ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"The job","schema":{"$ref":"#/definitions/models.EmailVerificationJobResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Job not found","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"500":{"description":"Failed to retrieve the job","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/email-verification/jobs/{id}/cancel":{"post":{"security":[{"ApiKeyAuth":[]}],"description":"Stops a running job. Addresses already verified stay in the download and stay billed; addresses that never ran are never charged, so cancelling costs nothing further and needs no refund.","produces":["application/json"],"tags":["Email Verification"],"summary":"Cancel an email verification job","parameters":[{"type":"string","description":"Job ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"The job was canceled","schema":{"$ref":"#/definitions/models.CancelEmailVerificationJobResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Job not found","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"500":{"description":"Failed to cancel the job","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/email-verification/jobs/{id}/csv":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Streams one row per submitted address. Every address is in the file, including the ones that came back unknown because the monthly allowance ran out: they are marked rather than dropped, so the file never has a silent hole in it.","produces":["text/csv"],"tags":["Email Verification"],"summary":"Download an email verification job as CSV","parameters":[{"type":"string","description":"Job ID","name":"id","in":"path","required":true},{"enum":["valid","catchall_validated","invalid","unknown","deliverable"],"type":"string","description":"Filter by verdict","name":"result","in":"query"},{"type":"string","description":"Comma-separated subset of columns, in order. Defaults to all: Email, Result, First check result, Second check result, Status, Verification Credits, Error, Verified At.","name":"columns","in":"query"}],"responses":{"200":{"description":"The CSV file","schema":{"type":"string"}},"400":{"description":"Invalid filter or column","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Job not found","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/email-verification/jobs/{id}/records":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Returns one row per address with its verdict and what it billed. Filter with result: valid, catchall_validated, invalid, unknown, or deliverable (valid plus catch-all, which is what a campaign will send to).","produces":["application/json"],"tags":["Email Verification"],"summary":"List an email verification job's results","parameters":[{"type":"string","description":"Job ID","name":"id","in":"path","required":true},{"type":"integer","default":1,"description":"Page number, from 1","name":"page","in":"query"},{"type":"integer","default":25,"description":"Page size, max 200","name":"size","in":"query"},{"enum":["valid","catchall_validated","invalid","unknown","deliverable"],"type":"string","description":"Filter by verdict","name":"result","in":"query"}],"responses":{"200":{"description":"A page of results","schema":{"$ref":"#/definitions/models.ListEmailVerificationRecordsResponse"}},"400":{"description":"Invalid result filter","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Job not found","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"500":{"description":"Failed to list results","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/email-verification/rates":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Returns the current price of a verification, read from the billing plans the meter actually charges against. Read this rather than assuming a rate: an address costs one credit when the first provider rejects it outright and two when both providers answer, so per_address_ceiling_usd is the figure to budget against.","produces":["application/json"],"tags":["Email Verification"],"summary":"Get the email verification rate","responses":{"200":{"description":"The current rates","schema":{"$ref":"#/definitions/models.EmailVerificationRatesResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"500":{"description":"Failed to read the rates","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/icps":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Lists the caller's workspace ICPs, newest first.","produces":["application/json"],"tags":["ICPs"],"summary":"List Ideal Customer Profiles","parameters":[{"type":"integer","description":"Page size (default 50, max 200)","name":"limit","in":"query"},{"type":"integer","description":"Page number, 1-based (default 1)","name":"page","in":"query"}],"responses":{"200":{"description":"Profiles and total count","schema":{"$ref":"#/definitions/models.ListICPsResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"500":{"description":"Failed to retrieve profiles","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}},"post":{"security":[{"ApiKeyAuth":[]}],"description":"Stores a manually-authored ICP scoped to the caller's workspace. Set makePrimary to promote it to the workspace's active profile.","consumes":["application/json"],"produces":["application/json"],"tags":["ICPs"],"summary":"Create an Ideal Customer Profile","parameters":[{"description":"Profile fields","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.CreateICPRequest"}}],"responses":{"201":{"description":"The created profile","schema":{"$ref":"#/definitions/models.ICPResult"}},"400":{"description":"Invalid request body","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"500":{"description":"Failed to create the profile","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/icps/primary":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Returns the workspace's active profile, or 404 when none is set.","produces":["application/json"],"tags":["ICPs"],"summary":"Get the primary Ideal Customer Profile","responses":{"200":{"description":"The primary profile","schema":{"$ref":"#/definitions/models.ICPResult"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"No primary profile","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"500":{"description":"Failed to load the primary profile","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/icps/{id}":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Returns one profile. Profiles belonging to other workspaces are reported as not found.","produces":["application/json"],"tags":["ICPs"],"summary":"Get an Ideal Customer Profile","parameters":[{"type":"integer","description":"Profile ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"The profile","schema":{"$ref":"#/definitions/models.ICPResult"}},"400":{"description":"Invalid profile id","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Profile not found","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}},"put":{"security":[{"ApiKeyAuth":[]}],"description":"Applies a partial edit: omitted fields are unchanged, an empty list clears the list. Any edit marks the profile as human-authored.","consumes":["application/json"],"produces":["application/json"],"tags":["ICPs"],"summary":"Update an Ideal Customer Profile","parameters":[{"type":"integer","description":"Profile ID","name":"id","in":"path","required":true},{"description":"Fields to change","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.UpdateICPRequest"}}],"responses":{"200":{"description":"The updated profile","schema":{"$ref":"#/definitions/models.ICPResult"}},"400":{"description":"Invalid request body","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Profile not found","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}},"delete":{"security":[{"ApiKeyAuth":[]}],"description":"Deletes a profile. Deleting the primary leaves the workspace without one.","produces":["application/json"],"tags":["ICPs"],"summary":"Delete an Ideal Customer Profile","parameters":[{"type":"integer","description":"Profile ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"Deleted","schema":{"$ref":"#/definitions/models.DeleteICPResponse"}},"400":{"description":"Invalid profile id","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Profile not found","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/icps/{id}/refresh-audience-size":{"post":{"security":[{"ApiKeyAuth":[]}],"description":"Sizes the audience matching the profile's targeting criteria against the data provider and caches the result. Sizing is free.","produces":["application/json"],"tags":["ICPs"],"summary":"Refresh a profile's audience size","parameters":[{"type":"integer","description":"Profile ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"The refreshed profile and audience size","schema":{"$ref":"#/definitions/models.AudienceSizeResponse"}},"400":{"description":"The profile has no targeting criteria","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Profile not found","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"502":{"description":"Could not size the audience","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/icps/{id}/set-primary":{"post":{"security":[{"ApiKeyAuth":[]}],"description":"Promotes a profile to the workspace's active one, demoting any existing primary.","produces":["application/json"],"tags":["ICPs"],"summary":"Set the primary Ideal Customer Profile","parameters":[{"type":"integer","description":"Profile ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"The promoted profile","schema":{"$ref":"#/definitions/models.ICPResult"}},"400":{"description":"Invalid profile id","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Profile not found","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/imports/instantly":{"post":{"security":[{"ApiKeyAuth":[]}],"description":"Validates the provided Instantly API key and enqueues a background job that imports the caller's Instantly campaigns and leads into their workspace. Every imported campaign is created in DRAFT state — nothing is sent automatically.","consumes":["application/json"],"produces":["application/json"],"tags":["Imports"],"summary":"Import an Instantly.ai account","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"description":"Instantly API key","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.InstantlyImportRequest"}}],"responses":{"202":{"description":"Import job queued","schema":{"$ref":"#/definitions/models.InstantlyImportResponse"}},"400":{"description":"Invalid request body or Instantly API key","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"500":{"description":"Failed to start import","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/imports/instantly/accounts":{"post":{"security":[{"ApiKeyAuth":[]}],"description":"Fetches the sending accounts of the given Instantly workspace and returns them together with a pre-filled CSV for the bulk IMAP/SMTP connection flow. Mailbox credentials cannot be exported from any provider, so accounts are reconnected either per-mailbox via Google/Microsoft OAuth or in bulk via IMAP app passwords using the returned CSV. Changes nothing, but needs a read and write API key.","consumes":["application/json"],"produces":["application/json"],"tags":["Imports"],"summary":"List Instantly sending accounts","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"description":"Instantly API key","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.InstantlyAccountsRequest"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/models.InstantlyAccountsResponse"}},"400":{"description":"Invalid request body or Instantly API key","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"502":{"description":"Instantly API unavailable","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/inbox-placement/insights":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"What is wrong across the workspace right now, worst first, each with the action that fixes it. Combines the placement measured over the window with the latest health check of every connected mailbox: authentication records, blacklist listings and measured placement per mailbox.","produces":["application/json"],"tags":["Inbox Placement"],"summary":"Deliverability insights","parameters":[{"type":"integer","description":"How many days of runs to summarise (1..365, default 30)","name":"days","in":"query"}],"responses":{"200":{"description":"The findings","schema":{"$ref":"#/definitions/models.InboxPlacementInsightsResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"403":{"description":"The workspace does not hold the Inbox Placement add-on (code inbox_placement_required)","schema":{"$ref":"#/definitions/models.InboxPlacementForbiddenResponse"}},"500":{"description":"Failed to build the insights","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/inbox-placement/runs":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Lists inbox placement runs newest first, optionally filtered to one test with ?testId=. Counters on a run are only final once its status is completed. Note that every rate is -1 when nothing has been scored yet, which means NOT MEASURED and must not be rendered as 0%.","produces":["application/json"],"tags":["Inbox Placement"],"summary":"List inbox placement runs","parameters":[{"type":"integer","description":"Only runs of this test","name":"testId","in":"query"},{"type":"integer","description":"How many runs to return (1..200, default 50)","name":"limit","in":"query"}],"responses":{"200":{"description":"The runs","schema":{"$ref":"#/definitions/models.InboxPlacementListResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"403":{"description":"The workspace does not hold the Inbox Placement add-on (code inbox_placement_required)","schema":{"$ref":"#/definitions/models.InboxPlacementForbiddenResponse"}},"500":{"description":"Failed to load the runs","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/inbox-placement/runs/{id}":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Returns one run with its complete report: the headline split, the per-provider and per-sending-mailbox breakdowns, and the content spam rules the copy triggered. \"Promotions\" rolls up every Gmail category tab. \"Missing\" means the probe was never found in any folder, which usually indicates a silent block, and is reported apart from spam because it is a worse problem with a different fix.","produces":["application/json"],"tags":["Inbox Placement"],"summary":"Get an inbox placement run","parameters":[{"type":"integer","description":"Run ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"The run and its report","schema":{"$ref":"#/definitions/models.InboxPlacementRunDetailResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"403":{"description":"The workspace does not hold the Inbox Placement add-on (code inbox_placement_required)","schema":{"$ref":"#/definitions/models.InboxPlacementForbiddenResponse"}},"404":{"description":"No such run in this workspace","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"500":{"description":"Failed to load the run","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/inbox-placement/runs/{id}/results":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Returns one row per probe: which of your mailboxes sent it, which seed mailbox received it, where it landed and how long it took to arrive. deliverySeconds is worth watching on its own: greylisting and throttling show up there before they show up in a placement number.","produces":["application/json"],"tags":["Inbox Placement"],"summary":"List a run's individual probes","parameters":[{"type":"integer","description":"Run ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"The probes","schema":{"$ref":"#/definitions/models.InboxPlacementResultListResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"403":{"description":"The workspace does not hold the Inbox Placement add-on (code inbox_placement_required)","schema":{"$ref":"#/definitions/models.InboxPlacementForbiddenResponse"}},"404":{"description":"No such run in this workspace","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"500":{"description":"Failed to load the results","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/inbox-placement/sender-reputation":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"The latest health check for every connected mailbox: SPF, DKIM, DMARC and MX status, blacklist listings with their delisting links, measured inbox placement, and a combined 0-100 health score. blacklistsChecked is reported next to blacklistsListed so the count can never be read as a total. A placementScore or healthScore of -1 means not measured, not zero.","produces":["application/json"],"tags":["Inbox Placement"],"summary":"Sender reputation and health","responses":{"200":{"description":"The mailbox health table","schema":{"$ref":"#/definitions/models.SenderReputationListResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"403":{"description":"The workspace does not hold the Inbox Placement add-on (code inbox_placement_required)","schema":{"$ref":"#/definitions/models.InboxPlacementForbiddenResponse"}},"500":{"description":"Failed to load the mailbox health","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/inbox-placement/stats-by-date":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Rolls completed runs up by UTC calendar day over a window, for a trend chart. Days on which nothing ran are ABSENT from the series rather than returned as zeros: a zero-filled day plots as placement collapsing to 0%, which reads as an outage when it means nobody ran a test. Defaults to the last 30 days.","produces":["application/json"],"tags":["Inbox Placement"],"summary":"Inbox placement stats by date","parameters":[{"type":"string","description":"Start of the window, RFC3339. Defaults to 30 days ago.","name":"from","in":"query"},{"type":"string","description":"End of the window, RFC3339. Defaults to now.","name":"to","in":"query"}],"responses":{"200":{"description":"The daily series","schema":{"$ref":"#/definitions/models.InboxPlacementStatsByDateResponse"}},"400":{"description":"Malformed or reversed date range","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"403":{"description":"The workspace does not hold the Inbox Placement add-on (code inbox_placement_required)","schema":{"$ref":"#/definitions/models.InboxPlacementForbiddenResponse"}},"500":{"description":"Failed to load the history","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/inbox-placement/tests":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Lists every inbox placement test defined in the workspace, newest first. A test is the definition (content, which mailboxes to send from, and for a recurring test how often); each execution is a run, listed separately at /inbox-placement/runs.","produces":["application/json"],"tags":["Inbox Placement"],"summary":"List inbox placement tests","responses":{"200":{"description":"The workspace's tests","schema":{"$ref":"#/definitions/models.InboxPlacementTestListResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"403":{"description":"The workspace does not hold the Inbox Placement add-on (code inbox_placement_required)","schema":{"$ref":"#/definitions/models.InboxPlacementForbiddenResponse"}},"500":{"description":"Failed to load the tests","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/lead-finder/filters":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Returns the values the enum filters accept (seniorities, job functions, company sizes, revenue bands, industries, countries, headquarters countries, regions and continents), the limits that apply to this workspace (page sizes, deepest page, refs per import, first-N count, running imports, and the daily browsing allowance with what is left of it today), and creditsPerProspect, what adding one person to a campaign costs. Free: no call to the data provider is made.","produces":["application/json"],"tags":["Lead Finder"],"summary":"Get Lead Finder filter values and limits","responses":{"200":{"description":"Filter vocabulary, limits and price","schema":{"$ref":"#/definitions/models.LeadFinderFilters"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"403":{"description":"plan_required: the workspace's plan does not include Lead Finder","schema":{"$ref":"#/definitions/models.LeadFinderError"}},"409":{"description":"provider_unavailable: the contact database is switched off","schema":{"$ref":"#/definitions/models.LeadFinderError"}},"500":{"description":"internal","schema":{"$ref":"#/definitions/models.LeadFinderError"}}}}},"/lead-finder/imports":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Lists the workspace's Lead Finder imports, newest first, with their progress. Imports keep being listed after a plan change, so a workspace can always see what it started.","produces":["application/json"],"tags":["Lead Finder"],"summary":"List Lead Finder imports","parameters":[{"type":"integer","description":"How many imports to return, 1 to 50 (default 10)","name":"limit","in":"query"}],"responses":{"200":{"description":"Recent imports","schema":{"$ref":"#/definitions/models.LeadFinderImports"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"500":{"description":"internal","schema":{"$ref":"#/definitions/models.LeadFinderError"}}}},"post":{"security":[{"ApiKeyAuth":[]}],"description":"SPENDS CREDITS, AND BY DEFAULT METERED VERIFICATION. Reveals people from the contact database and adds them to a campaign as leads, in the background. mode=selected adds the people named by refs from search results (1 to 1,000; a ref that is no longer stored is counted in expired and dropped). mode=first_n adds up to count people matching filters who are not in the workspace yet (1 to 5,000). It continues after the last first_n import of the same filters (startsAt in the response says where; fromStart true starts from the top instead) and skips people already in the workspace for free, so repeating a first_n call adds and charges for the next count people: never repeat a call to retry, read GET /lead-finder/imports first. While a first_n import of the same filters is still running another is refused (409 import_running). It stops early when the audience runs out (endReason audience_exhausted, and the next first_n import of those filters starts from the top again) or once it has looked at five people for every one requested, at least 1,000 (endReason fetch_limit, and the next one continues from there). People already in the workspace that it passes count toward that limit although they cost nothing. Every person it looks at, added or skipped, also counts against the account's daily first_n allowance (25,000 people a UTC day by default). Each person added costs creditsPerProspect credits (1 at the time of writing). People already in the workspace, blocklisted, without a usable address, on a personal mailbox when the campaign only takes business addresses, or marked invalid by verification are skipped and cost no credits. verifyEmails (default true) checks every screened address with the paid verification waterfall of POST /email-verification/jobs, including the ones it marks invalid: one check when the first provider rejects the address and two otherwise, billed as metered usage on the next invoice at the GET /email-verification/rates price, within the account's monthly verification allowance. Catch-all and unconfirmed addresses are added and charged, and so is every address once that allowance is used up. Set verifyEmails to false to skip verification. The wallet must hold estimatedCredits for the import to start. Credits are held batch by batch and settled against the leads actually created. Leads added to a running or paused campaign get their emails at once (a paused campaign sends them when resumed); adding to a completed campaign resumes it, so it starts sending again; in a draft campaign the emails are created at launch. At most 3 imports run at once in a workspace. Poll GET /lead-finder/imports/{id} until finishedAt is set. Needs a read_write key.","consumes":["application/json"],"produces":["application/json"],"tags":["Lead Finder"],"summary":"Add people to a campaign from the contact database (spends credits and metered verification)","parameters":[{"description":"The campaign, and who to add","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.LeadFinderImportRequest"}}],"responses":{"202":{"description":"Import started","schema":{"$ref":"#/definitions/models.LeadFinderImportCreated"}},"400":{"description":"invalid_request (mode, refs or count) or invalid_filters","schema":{"$ref":"#/definitions/models.LeadFinderError"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"402":{"description":"insufficient_credits: needed and available are in credits; buy more with POST /credits/purchase","schema":{"$ref":"#/definitions/models.LeadFinderError"}},"403":{"description":"plan_required, or a read-only key: the auth layer refuses it as text/plain, error 'This API key is read-only; this endpoint requires the read_write scope'","schema":{"$ref":"#/definitions/models.LeadFinderError"}},"404":{"description":"campaign_not_found","schema":{"$ref":"#/definitions/models.LeadFinderError"}},"409":{"description":"provider_unavailable: the contact database is switched off; or import_running: a first_n import of the same filters is still running (importId), start this one when it has finished","schema":{"$ref":"#/definitions/models.LeadFinderError"}},"410":{"description":"results_expired: none of the refs is still stored; search again","schema":{"$ref":"#/definitions/models.LeadFinderError"}},"422":{"description":"contact_cap_reached: the import would pass the plan's contact limit","schema":{"$ref":"#/definitions/models.LeadFinderError"}},"429":{"description":"rate_limited (reason running_imports) or budget_exhausted (reason daily_import_limit, daily_budget, monthly_budget or fair_use_floor): retry after retryAfterSeconds","schema":{"$ref":"#/definitions/models.LeadFinderError"}},"500":{"description":"internal","schema":{"$ref":"#/definitions/models.LeadFinderError"}}}}},"/lead-finder/imports/{id}":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Returns one import's progress: how many people were added and skipped and why, what verification said, and the credits spent so far. Status is running, completed, failed or canceled, and added, creditsSpent and status can still change until finishedAt is set. A completed import's endReason is all_selected, requested_reached, audience_exhausted or fetch_limit; a canceled one's is canceled. A failed import has no endReason but can still have added and charged people: lastError says why it stopped (insufficient_credits, contact_cap_reached, results_expired, budget_exhausted, provider_blocked, invalid_filters, campaign_not_found or internal). A new first_n import to finish a partial one starts from the top again and counts the people already added toward its fetch limit, so it only reaches new people while those fit inside five times its count (at least 1,000); otherwise pick the missing people by ref from search results. Imports belonging to other workspaces are reported as not found.","produces":["application/json"],"tags":["Lead Finder"],"summary":"Get a Lead Finder import","parameters":[{"type":"integer","description":"Import ID (importId)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"The import","schema":{"$ref":"#/definitions/models.LeadFinderImport"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"not_found","schema":{"$ref":"#/definitions/models.LeadFinderError"}},"500":{"description":"internal","schema":{"$ref":"#/definitions/models.LeadFinderError"}}}}},"/lead-finder/imports/{id}/cancel":{"post":{"security":[{"ApiKeyAuth":[]}],"description":"Marks a running import canceled at once and stops it after the batch it is working on. A batch the worker is already writing is still added and charged; a batch still being screened or verified is dropped, though verification checks already made are billed. Credits held for the rest are released. Until finishedAt is set, added, creditsSpent and even status can still change: if that batch was the import's last, it ends completed or failed instead. A canceled import cannot be resumed. Canceling an import that has already finished changes nothing and returns it as it is. Needs a read_write key.","produces":["application/json"],"tags":["Lead Finder"],"summary":"Cancel a Lead Finder import","parameters":[{"type":"integer","description":"Import ID (importId)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"The import, as it stands","schema":{"$ref":"#/definitions/models.LeadFinderImport"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"403":{"description":"A read-only key: the auth layer refuses it as text/plain, error 'This API key is read-only; this endpoint requires the read_write scope'","schema":{"$ref":"#/definitions/models.LeadFinderError"}},"404":{"description":"not_found","schema":{"$ref":"#/definitions/models.LeadFinderError"}},"500":{"description":"internal","schema":{"$ref":"#/definitions/models.LeadFinderError"}}}}},"/lead-finder/searches":{"post":{"security":[{"ApiKeyAuth":[]}],"description":"Searches Emailchaser's B2B contact database with explicit filters and returns one page of matching people, masked: first name, last initial, title, seniority, job function, company name, industry, size and revenue band, a company logo (logoUrl, an image this API serves, whose URL does not contain the company's domain), and city, state and country. No email address, email or company domain, LinkedIn URL or phone number is ever returned: contact details are revealed only by adding people to a campaign with POST /lead-finder/imports. Searching spends no credits, but every result row counts against the account's daily browsing allowance (limits.rowsPerDay in GET /lead-finder/filters, 2,000 rows a UTC day by default and 250 on a trial; rowsLeftToday shows what is left). An account may start 20 searches a minute. Searches that find nobody also draw on a bucket of 120 that refills at one every 30 seconds; while it is empty, every new search is refused with rate_limited (reason empty_searches) until the next refill, usually within 30 seconds. The same page asked for again within 10 minutes comes from cache and spends nothing. The request waits up to about six seconds: 200 means the search finished (status done, or failed with error saying why, for example rate_limited when the shared request budget is busy), 202 means it is still running, so read it with GET /lead-finder/searches/{id} using the returned searchId. total is exact when totalIsExact is true; otherwise it is a lower bound until totalStatus is done. inWorkspace marks people Lead Finder added to this workspace whose lead is still there, so adding them again would be skipped for free. Each person's ref stays valid for 60 minutes after the page it came on was last shown. A read-only key may call this endpoint.","consumes":["application/json"],"produces":["application/json"],"tags":["Lead Finder"],"summary":"Search the contact database (free, masked results)","parameters":[{"description":"Filters and page","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.LeadFinderSearchRequest"}}],"responses":{"200":{"description":"Search finished (status done or failed)","schema":{"$ref":"#/definitions/models.LeadFinderSearch"}},"202":{"description":"Search still running: read it with GET /lead-finder/searches/{id}","schema":{"$ref":"#/definitions/models.LeadFinderSearch"}},"400":{"description":"invalid_filters (field, value and reason name the problem) or invalid_request (page or pageSize)","schema":{"$ref":"#/definitions/models.LeadFinderError"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"403":{"description":"plan_required: the workspace's plan does not include Lead Finder","schema":{"$ref":"#/definitions/models.LeadFinderError"}},"409":{"description":"provider_unavailable: the contact database is switched off","schema":{"$ref":"#/definitions/models.LeadFinderError"}},"429":{"description":"rate_limited (reason searches_per_minute, empty_searches or daily_preview_limit) or budget_exhausted (reason daily_budget, monthly_budget or fair_use_floor): retry after retryAfterSeconds, also sent as a Retry-After header","schema":{"$ref":"#/definitions/models.LeadFinderError"}},"500":{"description":"internal","schema":{"$ref":"#/definitions/models.LeadFinderError"}}}}},"/lead-finder/searches/{id}":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Returns a search started with POST /lead-finder/searches. While it is still running the request waits up to about six seconds for it to finish before answering, so calling it again right away is fine. A finished search's results can be read for 10 minutes: after that the search reads as status failed with error expired, and after 15 minutes it is not found. Reading a finished page again within those 10 minutes keeps its people's refs valid for another 60 minutes. Searches belonging to other workspaces are reported as not found.","produces":["application/json"],"tags":["Lead Finder"],"summary":"Get a Lead Finder search","parameters":[{"type":"string","description":"Search ID (searchId)","name":"id","in":"path","required":true}],"responses":{"200":{"description":"The search (status running, done or failed)","schema":{"$ref":"#/definitions/models.LeadFinderSearch"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"403":{"description":"plan_required: the workspace's plan does not include Lead Finder","schema":{"$ref":"#/definitions/models.LeadFinderError"}},"404":{"description":"not_found: no such search in this workspace, or it has expired","schema":{"$ref":"#/definitions/models.LeadFinderError"}},"409":{"description":"provider_unavailable: the contact database is switched off","schema":{"$ref":"#/definitions/models.LeadFinderError"}},"500":{"description":"internal","schema":{"$ref":"#/definitions/models.LeadFinderError"}}}}},"/leads":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Lists the leads in the API key's workspace, newest first, with pagination. Filter by exact email address to look up a single lead, or by campaign to list only the leads in one campaign. This is how a lead created through POST /leads is found again.","consumes":["application/json"],"produces":["application/json"],"tags":["Leads"],"summary":"List leads","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Page number (default: 1)","name":"page","in":"query"},{"type":"integer","description":"Page size (default: 20, maximum: 200)","name":"limit","in":"query"},{"type":"string","description":"Filter by exact email address","name":"email","in":"query"},{"type":"integer","description":"Filter by campaign ID","name":"campaignId","in":"query"}],"responses":{"200":{"description":"List of leads","schema":{"$ref":"#/definitions/models.ListLeadsResponse"}},"400":{"description":"Invalid query parameters","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"500":{"description":"Failed to retrieve leads","schema":{"$ref":"#/definitions/models.ErrorFailedToRetrieveLeads"}}}},"post":{"security":[{"ApiKeyAuth":[]}],"description":"Creates or updates up to 1,000 leads in one request. Leads are matched by email within the workspace. If a lead with that email already exists, it is updated, and every named field you leave out is cleared, so send the full lead each time. An update also clears the lead's city, country, industry and company description, which the API cannot set. middleName is only saved when a lead is created. customVariables, when sent, replaces the lead's variables; leave it out to keep them. Every lead needs a valid email: one invalid lead fails the whole request with 400, and nothing is saved. To add the leads to a campaign, pass campaignId at the top level of the request body, not inside a lead. The campaign must have at least one email account attached (POST /campaigns/{id}/sender-emails). If it has none, the leads are saved and added to the campaign, but the call fails with 500; attach an account and send the same request again. count in the response is the number of leads you sent, not the number created. Any attribute outside the named fields can be sent in customVariables and becomes a merge tag usable in email copy. PUT /leads is an alias for this endpoint and behaves identically.","consumes":["application/json"],"produces":["application/json"],"tags":["Leads"],"summary":"Create or update leads in bulk","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"description":"Leads to create or update","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.CreateOrUpdateLeadsRequest"}}],"responses":{"201":{"description":"Leads processed successfully","schema":{"$ref":"#/definitions/models.CreateLeadsResponse"}},"400":{"description":"Invalid request body, an invalid email, or no leads provided","schema":{"$ref":"#/definitions/models.ErrorInvalidRequest"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Campaign not found","schema":{"$ref":"#/definitions/models.ErrorCampaignNotFound"}},"422":{"description":"The new leads would go over the plan's contact limit","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"500":{"description":"Failed to save the leads, or the campaign has no email account attached","schema":{"$ref":"#/definitions/models.ErrorFailedToCreateLeads"}}}}},"/leads/{id}":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Retrieves one lead. The lead comes back as a flat object, not wrapped in a \"lead\" key. It has no middleName; GET /leads includes it.","consumes":["application/json"],"produces":["application/json"],"tags":["Leads"],"summary":"Get a lead by ID","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Lead ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"Lead details","schema":{"$ref":"#/definitions/models.GetLeadResponse"}},"400":{"description":"Invalid lead ID","schema":{"$ref":"#/definitions/models.ErrorInvalidLeadID"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"403":{"description":"Forbidden - lead does not belong to your space","schema":{"$ref":"#/definitions/models.ErrorForbidden"}},"404":{"description":"Lead not found","schema":{"$ref":"#/definitions/models.ErrorNotFound"}}}},"put":{"security":[{"ApiKeyAuth":[]}],"description":"Updates one lead. What happens depends on whether the lead is in a campaign. For a lead in no campaign, only the fields you send are changed, and customVariables, when sent, replaces the whole set. For a lead in a campaign, the lead is saved again from your request: every named field you leave out is cleared, so send the full lead each time. This also clears the lead's city, country, industry and company description, which the API cannot set. customVariables is kept if you leave it out. Sending a different email does not change this lead: it creates a lead with the new email in the same campaign, or overwrites the lead that already has that email, and this lead stays as it was. middleName is accepted but never saved. The response holds only the lead's id, email, firstName and lastName, plus customVariables for a lead in no campaign. Read the full lead with GET /leads/{id}.","consumes":["application/json"],"produces":["application/json"],"tags":["Leads"],"summary":"Update a lead by ID","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Lead ID","name":"id","in":"path","required":true},{"description":"Lead fields to update","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.UpdateLeadRequest"}}],"responses":{"200":{"description":"Lead updated successfully","schema":{"$ref":"#/definitions/models.UpdateLeadResponse"}},"400":{"description":"Invalid lead ID or request body","schema":{"$ref":"#/definitions/models.ErrorInvalidLeadID"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"403":{"description":"Forbidden - lead does not belong to your space","schema":{"$ref":"#/definitions/models.ErrorForbidden"}},"404":{"description":"Lead not found","schema":{"$ref":"#/definitions/models.ErrorNotFound"}},"500":{"description":"Failed to update lead","schema":{"$ref":"#/definitions/models.ErrorFailedToUpdateLead"}}}},"delete":{"security":[{"ApiKeyAuth":[]}],"description":"Deletes a lead and all associated unsent emails. If the lead is associated with campaigns, it will be removed from those campaigns first.","consumes":["application/json"],"produces":["application/json"],"tags":["Leads"],"summary":"Delete a lead by ID","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Lead ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"Lead deleted successfully","schema":{"$ref":"#/definitions/models.DeleteLeadResponse"}},"400":{"description":"Invalid lead ID","schema":{"$ref":"#/definitions/models.ErrorInvalidLeadID"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"403":{"description":"Forbidden - lead does not belong to your space","schema":{"$ref":"#/definitions/models.ErrorForbidden"}},"404":{"description":"Lead not found","schema":{"$ref":"#/definitions/models.ErrorNotFound"}},"500":{"description":"Failed to delete lead","schema":{"$ref":"#/definitions/models.ErrorFailedToDeleteLead"}}}}},"/leads/{id}/category":{"put":{"security":[{"ApiKeyAuth":[]}],"description":"Sets the lead's category (tag), typically to correct an AI misclassification of a reply. Validated against the category enum: interested, not_interested, bounced, out_of_office, delivery_incomplete, meeting_booked. Setting meeting_booked behaves exactly like POST /leads/{id}/meeting: it records meetingBookedAt when unset and keeps an existing one, because a meeting is an explicit mark set by you or your agent, never inferred by the system. A real category change fires the LeadCategoryUpdate webhook; setting the value the lead already has is a no-op. Changing the category away from meeting_booked does NOT clear the booked-meeting mark - use DELETE /leads/{id}/meeting for that.","consumes":["application/json"],"produces":["application/json"],"tags":["Leads"],"summary":"Set or correct a lead's category","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Lead ID","name":"id","in":"path","required":true},{"description":"Category to set","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.UpdateLeadCategoryRequest"}}],"responses":{"200":{"description":"Lead category updated","schema":{"$ref":"#/definitions/models.UpdateLeadCategoryResponse"}},"400":{"description":"Invalid lead ID, request body, or category","schema":{"$ref":"#/definitions/models.ErrorInvalidCategory"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Lead not found","schema":{"$ref":"#/definitions/models.ErrorNotFound"}},"500":{"description":"Failed to update lead category","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/leads/{id}/conversation":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Returns the lead's full email thread in chronological order: outbound emails (sent, scheduled, and unsent drafts) and inbound replies. Each item carries direction (inbound/outbound), status, and, for categorized inbound emails, the AI response category. Unsent drafts, including AI-suggested replies awaiting human review, are marked with isDraft. An AI reply draft (isDraft true, campaignId null) can be edited with PUT /reply-drafts/{id} and sent with POST /reply-drafts/{id}/send, using its id from this list. Drafts that belong to a campaign cannot be sent this way.","consumes":["application/json"],"produces":["application/json"],"tags":["Replies"],"summary":"Get a lead's conversation","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Lead ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"The lead's conversation thread","schema":{"$ref":"#/definitions/models.GetLeadConversationResponse"}},"400":{"description":"Invalid lead ID","schema":{"$ref":"#/definitions/models.ErrorInvalidLeadID"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Lead not found","schema":{"$ref":"#/definitions/models.ErrorNotFound"}},"500":{"description":"Failed to retrieve conversation","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/leads/{id}/meeting":{"post":{"security":[{"ApiKeyAuth":[]}],"description":"Records that a meeting was booked with this lead. A meeting is an explicit mark set by you or your agent when a call actually gets scheduled - Emailchaser never infers one from reply text or calendars, so this call (or PUT /leads/{id}/category with meeting_booked, which behaves identically) is the only way a meeting is recorded. Sets meetingBookedAt to now, sets the lead's category to meeting_booked, and fires the LeadCategoryUpdate webhook on the category change. Idempotent: repeating the call keeps the original meetingBookedAt and fires no second webhook. Marked meetings are counted in GET /campaigns/{id}/stats totals.meetings and GET /reports/outcomes.","consumes":["application/json"],"produces":["application/json"],"tags":["Meetings"],"summary":"Mark a meeting as booked on a lead","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Lead ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"Meeting marked as booked","schema":{"$ref":"#/definitions/models.LeadMeetingResponse"}},"400":{"description":"Invalid lead ID","schema":{"$ref":"#/definitions/models.ErrorInvalidLeadID"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Lead not found","schema":{"$ref":"#/definitions/models.ErrorNotFound"}},"500":{"description":"Failed to mark meeting","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}},"delete":{"security":[{"ApiKeyAuth":[]}],"description":"Removes the explicit booked-meeting mark: clears meetingBookedAt and, when the lead's category is still meeting_booked, reverts it to interested (firing the LeadCategoryUpdate webhook on the change). A category that was changed to something else since booking is left alone. Idempotent: unmarking a lead with no meeting is a no-op.","consumes":["application/json"],"produces":["application/json"],"tags":["Meetings"],"summary":"Unmark a booked meeting on a lead","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Lead ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"Meeting unmarked","schema":{"$ref":"#/definitions/models.LeadMeetingResponse"}},"400":{"description":"Invalid lead ID","schema":{"$ref":"#/definitions/models.ErrorInvalidLeadID"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Lead not found","schema":{"$ref":"#/definitions/models.ErrorNotFound"}},"500":{"description":"Failed to unmark meeting","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/prospects/source":{"post":{"security":[{"ApiKeyAuth":[]}],"description":"Queues a background job that searches Emailchaser's internal contact database with the targeting criteria of an Ideal Customer Profile, reveals matching people and adds them to the campaign as leads. The profile defaults to the workspace's primary ICP when icpId is omitted. Sourcing is asynchronous: poll GET /leads?campaignId= to watch the prospects arrive. Each stored prospect costs the reveal price in credits (1 at the time of writing), reported per request as creditsPerProspect with the batch ceiling as estimatedCredits; duplicates, blocklisted domains and contacts without an email address are filtered out before any credit is spent. Repeated calls for the same campaign and profile resume the same search at its provider cursor, so they page deeper into the audience instead of re-revealing (and re-paying for) the same people.","consumes":["application/json"],"produces":["application/json"],"tags":["Prospecting"],"summary":"Fill a campaign with prospects from the contact database","parameters":[{"description":"Campaign, optional profile and count","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.SourceProspectsRequest"}}],"responses":{"202":{"description":"Sourcing job queued","schema":{"$ref":"#/definitions/models.SourceProspectsResponse"}},"400":{"description":"Invalid request body, invalid count, no usable profile, or the profile has no targeting criteria","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"402":{"description":"The workspace has no prospect credits","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"404":{"description":"Campaign or profile not found","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"500":{"description":"Failed to queue prospect sourcing","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/replies":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Lists inbound emails (replies received from prospects) in the API key's workspace, newest first, 20 per page by default (?limit= up to 200). Filter by AI response category, campaign, lead, or a time floor. responseCategory is null while AI categorization is still pending; campaignId is null for standalone replies that could not be attributed to a campaign. Read-only: replying happens by editing the AI draft (PUT /reply-drafts/{id}) and sending it (POST /reply-drafts/{id}/send) - the draft's current subject and body are what goes out.","consumes":["application/json"],"produces":["application/json"],"tags":["Replies"],"summary":"List replies","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"string","description":"Filter by response category (interested, not_interested, wrong_person, bounced, out_of_office, delivery_incomplete, dmarc_report, mixmax, warmup_email, unsubscribe, newsletter)","name":"category","in":"query"},{"type":"integer","description":"Filter by campaign ID","name":"campaignId","in":"query"},{"type":"integer","description":"Filter by lead ID","name":"leadId","in":"query"},{"type":"string","description":"Only replies received at or after this time (RFC3339 or YYYY-MM-DD)","name":"since","in":"query"},{"type":"integer","description":"Page number (default: 1)","name":"page","in":"query"},{"type":"integer","description":"Page size (default: 20, maximum: 200)","name":"limit","in":"query"}],"responses":{"200":{"description":"List of replies","schema":{"$ref":"#/definitions/models.ListRepliesResponse"}},"400":{"description":"Invalid query parameters","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Campaign or lead filter not found","schema":{"$ref":"#/definitions/models.ErrorNotFound"}},"500":{"description":"Failed to retrieve replies","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/reply-drafts":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Lists AI-suggested reply drafts awaiting human review in the API key's workspace, newest first, 20 per page by default (?limit= up to 200). Each draft answers the inbound reply referenced by inReplyToEmailId, and to and cc show who it goes to when sent: Reply All, so everyone the prospect addressed is copied. Drafts can be edited over REST (PUT /reply-drafts/{id}) and sent (POST /reply-drafts/{id}/send) - both require the read_write scope.","consumes":["application/json"],"produces":["application/json"],"tags":["Replies"],"summary":"List AI reply drafts","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Page number (default: 1)","name":"page","in":"query"},{"type":"integer","description":"Page size (default: 20, maximum: 200)","name":"limit","in":"query"}],"responses":{"200":{"description":"List of AI reply drafts","schema":{"$ref":"#/definitions/models.ListReplyDraftsResponse"}},"400":{"description":"Invalid query parameters","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"500":{"description":"Failed to retrieve reply drafts","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/reply-drafts/{id}":{"put":{"security":[{"ApiKeyAuth":[]}],"description":"Updates the subject and/or body of an AI reply draft awaiting human review. At least one field must be provided and provided fields must be non-empty. This endpoint never changes the draft's status or sends anything - use POST /reply-drafts/{id}/send once the wording is ready. Emails that are not AI reply drafts - sent or scheduled emails, inbound replies, campaign sequence templates - are refused with 409.","consumes":["application/json"],"produces":["application/json"],"tags":["Replies"],"summary":"Edit an AI reply draft","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Reply draft ID","name":"id","in":"path","required":true},{"description":"Fields to update","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.UpdateReplyDraftRequest"}}],"responses":{"200":{"description":"The updated reply draft","schema":{"$ref":"#/definitions/models.ReplyDraftItem"}},"400":{"description":"Invalid draft ID or request body","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Reply draft not found","schema":{"$ref":"#/definitions/models.ErrorNotFound"}},"409":{"description":"Email exists but is not an editable AI reply draft","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"500":{"description":"Failed to update reply draft","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/reply-drafts/{id}/send":{"post":{"security":[{"ApiKeyAuth":[]}],"description":"Schedules an AI reply draft for delivery through the conversation's sender mailbox, in the same email thread. It goes out as Reply All: to the person who wrote the reply, with everyone else they addressed (their To and Cc lines, minus your workspace's own mailboxes) in Cc, as listed in to and cc. The draft's current subject and body are what goes out, so edit first (PUT /reply-drafts/{id}) if needed. Emails that are not sendable AI reply drafts - already sent or scheduled emails, inbound replies, campaign sequence templates - are refused with 409. A draft whose conversation has no connected sender mailbox or no resolvable recipient is refused with 422.","consumes":["application/json"],"produces":["application/json"],"tags":["Replies"],"summary":"Send an AI reply draft","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Reply draft ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"The draft is scheduled for delivery","schema":{"$ref":"#/definitions/models.SendReplyDraftResponse"}},"400":{"description":"Invalid draft ID","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Reply draft not found","schema":{"$ref":"#/definitions/models.ErrorNotFound"}},"409":{"description":"Email exists but is not a sendable AI reply draft","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"422":{"description":"Draft cannot be delivered (no sender mailbox or no recipient)","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"500":{"description":"Failed to send reply draft","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/reports/outcomes":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Puts spend against results for a window (since/until compare inclusively; both optional). Spend has two parts. (1) Settled credit debits from the workspace ledger, grouped by reason; in-flight reservations are excluded until they settle, credits used while Emailchaser had made the workspace's credits free are not spend and are excluded, and credits are valued at the current list price of one credit, the same per-credit price a top-up is quoted at before volume discounts, regardless of what was actually paid for them via bulk discounts or plan allowances. (2) Done-for-you order costs from each order's stored cost breakdown; orders are counted by creation time, and failed or canceled orders are excluded because their charges are unwound. Subscription (platform) fees are NOT included: the backend stores only the current subscription state, not per-window invoice history, so they cannot be attributed to a window honestly. Outcomes are sent emails, replied leads, positively-replied (interested) leads and leads marked meeting-booked in the window. costPerReply, costPerPositive and costPerMeeting divide total spend by each outcome count and are null when that count is zero. The optional campaignId narrows the OUTCOME side only - credit and order spend is workspace-level and cannot be attributed to one campaign - so per-campaign cost figures are partial attribution, not a true campaign cost.","produces":["application/json"],"tags":["Reports"],"summary":"Get the money-vs-outcomes report","parameters":[{"type":"string","description":"Window start, RFC3339 or YYYY-MM-DD (inclusive)","name":"since","in":"query"},{"type":"string","description":"Window end, RFC3339 or YYYY-MM-DD (inclusive; a bare date means midnight UTC at the start of that day)","name":"until","in":"query"},{"type":"integer","description":"Narrow the outcome side to one campaign (partial attribution; spend stays workspace-level)","name":"campaignId","in":"query"}],"responses":{"200":{"description":"The report","schema":{"$ref":"#/definitions/models.OutcomesReportResponse"}},"400":{"description":"Invalid query parameter","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Campaign not found","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"500":{"description":"Failed to build the report","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/sender-emails":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Lists all sender emails for the authenticated user's space with optional filtering by campaign ID and connection status. Supports pagination. Each sender email carries its health score (healthScore): 0-100, higher is better, the share of its warm-up emails over the last 7 full days that landed in the inbox rather than spam; it moves daily as warm-up emails land in the inbox (up) or in spam (down), and is null while warm-up is off or before 20 warm-up emails were checked. Use it to pick the accounts to add to a campaign.","consumes":["application/json"],"produces":["application/json"],"tags":["Sender Emails"],"summary":"List sender emails","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Page number (default: 1)","name":"page","in":"query"},{"type":"integer","description":"Page size (default: 20, maximum: 200)","name":"limit","in":"query"},{"type":"integer","description":"Filter by space ID","name":"spaceId","in":"query"},{"type":"integer","description":"Filter by campaign ID","name":"campaignId","in":"query"},{"type":"boolean","description":"Filter by connection status","name":"isConnected","in":"query"}],"responses":{"200":{"description":"List of sender emails","schema":{"$ref":"#/definitions/models.ListSenderEmailsResponse"}},"400":{"description":"Invalid query parameters","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"500":{"description":"Failed to retrieve sender emails","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}},"post":{"security":[{"ApiKeyAuth":[]}],"description":"Connects an SMTP/IMAP mailbox to the workspace using an app password, with no browser step. Google and Microsoft mailboxes are not supported here because both require an interactive consent screen; connect those in the app. The address must be a business domain.","consumes":["application/json"],"produces":["application/json"],"tags":["Sender Emails"],"summary":"Connect an SMTP/IMAP sender email","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"description":"Mailbox credentials","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.ConnectSenderEmailRequest"}}],"responses":{"201":{"description":"Sender email connected successfully","schema":{"$ref":"#/definitions/models.ConnectSenderEmailResponse"}},"400":{"description":"Invalid request body or non-business email address","schema":{"$ref":"#/definitions/models.ErrorInvalidSenderEmailAddress"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"402":{"description":"Plan does not allow another sender email","schema":{"$ref":"#/definitions/models.ErrorSenderEmailPlanLimit"}},"409":{"description":"Email already connected to another workspace","schema":{"$ref":"#/definitions/models.ErrorSenderEmailAlreadyConnected"}},"500":{"description":"Failed to connect sender email","schema":{"$ref":"#/definitions/models.ErrorFailedToConnectSenderEmail"}},"502":{"description":"Mail provider rejected the credentials","schema":{"$ref":"#/definitions/models.ErrorFailedToConnectSenderEmail"}}}}},"/sender-emails/{id}":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Retrieves detailed information about a specific sender email including connection status, limits, settings, its last 20 connection events and health score. The sender email comes back as a flat object, not wrapped in a \"senderEmail\" key. The health score (healthScore) is 0-100, higher is better: the share of the account's warm-up emails over the last 7 full days that landed in the inbox rather than spam. It moves daily as warm-up emails land in the inbox (up) or in spam (down), and is null while warm-up is off or before 20 warm-up emails were checked.","consumes":["application/json"],"produces":["application/json"],"tags":["Sender Emails"],"summary":"Get a sender email by ID","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Sender Email ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"Sender email details","schema":{"$ref":"#/definitions/models.SenderEmailResponse"}},"400":{"description":"Invalid sender email ID","schema":{"$ref":"#/definitions/models.ErrorInvalidSenderEmailID"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"403":{"description":"Forbidden - sender email does not belong to your space","schema":{"$ref":"#/definitions/models.ErrorForbidden"}},"404":{"description":"Sender email not found","schema":{"$ref":"#/definitions/models.ErrorSenderEmailNotFound"}}}},"put":{"security":[{"ApiKeyAuth":[]}],"description":"Updates an existing sender email's information. Only provided fields will be updated. Can update name, signature, and daily sending limits.","consumes":["application/json"],"produces":["application/json"],"tags":["Sender Emails"],"summary":"Update a sender email by ID","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Sender Email ID","name":"id","in":"path","required":true},{"description":"Sender email fields to update","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.UpdateSenderEmailRequest"}}],"responses":{"200":{"description":"Sender email updated successfully","schema":{"$ref":"#/definitions/models.UpdateSenderEmailResponse"}},"400":{"description":"Invalid sender email ID or request body","schema":{"$ref":"#/definitions/models.ErrorInvalidSenderEmailID"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"403":{"description":"Forbidden - sender email does not belong to your space","schema":{"$ref":"#/definitions/models.ErrorForbidden"}},"404":{"description":"Sender email not found","schema":{"$ref":"#/definitions/models.ErrorSenderEmailNotFound"}},"500":{"description":"Failed to update sender email","schema":{"$ref":"#/definitions/models.ErrorFailedToUpdateSenderEmail"}}}}},"/sender-emails/{id}/dns":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Checks the sender domain's SPF, DKIM, DMARC and MX records and reports each as OK, WARNING, MISSING or ERROR with human-readable issues, the same checks behind the app's DNS vitals view. DNS lookups run live at request time (a few seconds worst case), so the response always reflects current records - there is no cache to go stale. Also includes the mailbox's warm-up status: whether it is enrolled, today's ramp target (currentPerDay), the configured daily cap and its health score.","consumes":["application/json"],"produces":["application/json"],"tags":["Sender Emails"],"summary":"Get a sender email's DNS vitals and warm-up status","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Sender Email ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"DNS vitals and warm-up status","schema":{"$ref":"#/definitions/models.SenderEmailDNSResponse"}},"400":{"description":"Invalid sender email ID","schema":{"$ref":"#/definitions/models.ErrorInvalidSenderEmailID"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Sender email not found","schema":{"$ref":"#/definitions/models.ErrorSenderEmailNotFound"}},"422":{"description":"Sender email has no valid domain","schema":{"$ref":"#/definitions/models.ErrorSenderEmailNoDomain"}},"500":{"description":"Failed to check DNS records","schema":{"$ref":"#/definitions/models.ErrorFailedToCheckDNS"}}}}},"/sender-emails/{id}/warmup":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Returns the mailbox's warm-up configuration: whether warm-up is on, the ramp (startLimit warm-up emails on the first day, increaseBy more each day, up to capLimit a day), weekdays-only, timezone, today's ramp target (currentPerDay) and the account's health score (healthScore, 0-100, higher is better: the share of its warm-up emails over the last 7 full days that landed in the inbox rather than spam; null while warm-up is off or before 20 were checked). Warm-up volume is separate from the campaign sending limits on the sender email. When configured is false the mailbox has never been enrolled, and the values shown are the defaults that switching warm-up on would use.","consumes":["application/json"],"produces":["application/json"],"tags":["Sender Emails"],"summary":"Get a sender email's warm-up settings","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Sender Email ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"Warm-up settings","schema":{"$ref":"#/definitions/models.SenderEmailWarmupSettingsResponse"}},"400":{"description":"Invalid sender email ID","schema":{"$ref":"#/definitions/models.ErrorInvalidSenderEmailID"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Sender email not found","schema":{"$ref":"#/definitions/models.ErrorSenderEmailNotFound"}},"500":{"description":"Failed to get warm-up settings","schema":{"$ref":"#/definitions/models.ErrorFailedToGetWarmupSettings"}}}},"put":{"security":[{"ApiKeyAuth":[]}],"description":"Switches warm-up on or off and changes the warm-up ramp. Only the fields provided change. Warm-up sends startLimit emails on the first day and adds increaseBy each day until it reaches capLimit a day; this volume is separate from the campaign sending limits on the sender email. Switching warm-up on for a mailbox that has never been enrolled enrols it with the defaults (2 a day, 2 more each day, up to 10 a day, weekdays only, UTC) overridden by any fields sent, and the ramp starts today; the mailbox must be connected. A mailbox that has never been enrolled needs enabled: true in the same request to take any other setting. Switching warm-up off keeps the settings and stops a reconnect from switching it back on. Raising capLimit on a mailbox that is already warming takes effect from the next scheduling run without restarting the ramp.","consumes":["application/json"],"produces":["application/json"],"tags":["Sender Emails"],"summary":"Update a sender email's warm-up settings","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Sender Email ID","name":"id","in":"path","required":true},{"description":"Warm-up settings to change","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.UpdateSenderEmailWarmupRequest"}}],"responses":{"200":{"description":"Warm-up settings after the change","schema":{"$ref":"#/definitions/models.SenderEmailWarmupSettingsResponse"}},"400":{"description":"Invalid sender email ID, request body or warm-up settings","schema":{"$ref":"#/definitions/models.ErrorInvalidWarmupSettings"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Sender email not found","schema":{"$ref":"#/definitions/models.ErrorSenderEmailNotFound"}},"409":{"description":"Warm-up is off for this mailbox and the request does not switch it on, or the mailbox is disconnected","schema":{"$ref":"#/definitions/models.ErrorWarmupSettingsConflict"}},"500":{"description":"Failed to update warm-up settings","schema":{"$ref":"#/definitions/models.ErrorFailedToUpdateWarmupSettings"}}}}},"/setup/ping":{"post":{"security":[{"ApiKeyAuth":[]}],"description":"Called by the customer's AI assistant as its first request, to confirm the API key works. Records which assistant connected and when on the key itself, and completes the \"Set up with AI\" onboarding task for the key's workspace. Idempotent: repeat pings simply refresh the recorded assistant and timestamp. The optional agent field is the assistant's name in any form; it is normalized to a short lowercase identifier (e.g. chatgpt, claude, grok, gemini, copilot) and echoed back.","consumes":["application/json"],"produces":["application/json"],"tags":["Setup"],"summary":"Confirm your AI assistant is connected","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"description":"Optional assistant name","name":"request","in":"body","schema":{"$ref":"#/definitions/models.SetupPingRequest"}}],"responses":{"200":{"description":"Connection confirmed","schema":{"$ref":"#/definitions/models.SetupPingResponse"}},"400":{"description":"Malformed request body","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"500":{"description":"Failed to record ping","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/space/billing-profile":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Returns the registrant and postal contact details held for the workspace. These are the details filed with the registrar when the Done For You flow buys a domain, and the address a CAN-SPAM footer must carry. Returns 404 until a profile has been set.","produces":["application/json"],"tags":["Workspace"],"summary":"Get the workspace billing profile","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true}],"responses":{"200":{"description":"The workspace billing profile","schema":{"$ref":"#/definitions/models.BillingProfileResult"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"No billing profile set for this workspace","schema":{"$ref":"#/definitions/models.ErrorBillingProfileNotFound"}},"500":{"description":"Failed to retrieve the billing profile","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}},"put":{"security":[{"ApiKeyAuth":[]}],"description":"Creates or replaces the workspace's registrant and postal contact details. Idempotent: sending the same body twice leaves the same state. Every field except addressLineTwo is required, because incomplete registrant details are filed with the registrar just as readily as complete ones. Set this before ordering domains through /dfy/orders.","consumes":["application/json"],"produces":["application/json"],"tags":["Workspace"],"summary":"Set the workspace billing profile","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"description":"Registrant and postal details","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.UpdateBillingProfileRequest"}}],"responses":{"200":{"description":"The stored billing profile","schema":{"$ref":"#/definitions/models.BillingProfileResult"}},"400":{"description":"Invalid billing profile","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"500":{"description":"Failed to save the billing profile","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/space/members":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Returns the workspace behind the API key: campaign counts broken down by status, and the list of members with their roles. Useful for confirming which workspace a key belongs to.","consumes":["application/json"],"produces":["application/json"],"tags":["Workspace"],"summary":"Get workspace details and members","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true}],"responses":{"200":{"description":"Workspace details","schema":{"$ref":"#/definitions/models.SpaceDetails"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"500":{"description":"Failed to retrieve workspace details","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/webhooks":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Lists all registered webhook endpoints for the authenticated user's space.","consumes":["application/json"],"produces":["application/json"],"tags":["Webhooks"],"summary":"List webhooks","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true}],"responses":{"200":{"description":"List of webhooks","schema":{"$ref":"#/definitions/models.ListWebhooksResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"500":{"description":"Failed to retrieve webhooks","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}},"post":{"security":[{"ApiKeyAuth":[]}],"description":"Registers a webhook endpoint that will be called when the given event type occurs (e.g. LeadCreated, CampaignStatusChanged, EmailReply). The URL must use HTTPS. A signing secret is created for the webhook, but the API does not return it. Copy it in the app under Settings, Integrations & API, on the Webhooks tab, where you can also rotate it.","consumes":["application/json"],"produces":["application/json"],"tags":["Webhooks"],"summary":"Register a webhook","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"description":"Webhook to register","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.RegisterWebhookRequest"}}],"responses":{"201":{"description":"Webhook registered successfully","schema":{"$ref":"#/definitions/models.RegisterWebhookResponse"}},"400":{"description":"Invalid request body, webhook type or URL","schema":{"$ref":"#/definitions/models.ErrorInvalidWebhookType"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"500":{"description":"Failed to register webhook","schema":{"$ref":"#/definitions/models.ErrorFailedToRegisterWebhook"}}}}},"/webhooks/{id}":{"put":{"security":[{"ApiKeyAuth":[]}],"description":"Updates a registered webhook's name, URL, event type or enabled state. Only provided fields are changed. Disabling a webhook stops deliveries without deleting it.","consumes":["application/json"],"produces":["application/json"],"tags":["Webhooks"],"summary":"Update a webhook","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Webhook ID","name":"id","in":"path","required":true},{"description":"Webhook fields to update","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.UpdateWebhookRequest"}}],"responses":{"200":{"description":"Webhook updated successfully","schema":{"$ref":"#/definitions/models.UpdateWebhookResponse"}},"400":{"description":"Invalid webhook ID, request body, type or URL","schema":{"$ref":"#/definitions/models.ErrorInvalidWebhookType"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Webhook not found","schema":{"$ref":"#/definitions/models.ErrorWebhookNotFound"}},"500":{"description":"Failed to update webhook","schema":{"$ref":"#/definitions/models.ErrorFailedToUpdateWebhook"}}}},"delete":{"security":[{"ApiKeyAuth":[]}],"description":"Deletes a registered webhook endpoint. Events of its type will no longer be delivered to its URL.","consumes":["application/json"],"produces":["application/json"],"tags":["Webhooks"],"summary":"Delete a webhook","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Webhook ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"Webhook deleted successfully","schema":{"$ref":"#/definitions/models.DeleteWebhookResponse"}},"400":{"description":"Invalid webhook ID","schema":{"$ref":"#/definitions/models.ErrorInvalidWebhookID"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"404":{"description":"Webhook not found","schema":{"$ref":"#/definitions/models.ErrorWebhookNotFound"}},"500":{"description":"Failed to delete webhook","schema":{"$ref":"#/definitions/models.ErrorFailedToDeleteWebhook"}}}}},"/workspaces":{"get":{"security":[{"ApiKeyAuth":[]}],"description":"Lists the workspaces owned by the API key's workspace owner: the main workspace and its sub-workspaces. isCurrent marks the workspace the calling key is bound to.","produces":["application/json"],"tags":["Workspace"],"summary":"List workspaces","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true}],"responses":{"200":{"description":"Workspaces owned by the key's workspace owner","schema":{"$ref":"#/definitions/models.ListWorkspacesResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"500":{"description":"Failed to list workspaces","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}},"post":{"security":[{"ApiKeyAuth":[]}],"description":"Creates a sub-workspace under the API key's main workspace and, by default, mints a read+write API key bound to the new workspace. The key's fullKey is returned exactly once and cannot be retrieved again, and it never outlives the caller: when the calling key has an expiry, the minted key carries the same expiresAt. Must be called with a main workspace's key: keys bound to sub-workspaces are refused. API keys are included in every plan; workspaces are a Professional-plan feature.","consumes":["application/json"],"produces":["application/json"],"tags":["Workspace"],"summary":"Create a workspace","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"description":"Workspace to create","name":"request","in":"body","required":true,"schema":{"$ref":"#/definitions/models.CreateWorkspaceRequest"}}],"responses":{"201":{"description":"The created workspace and its API key","schema":{"$ref":"#/definitions/models.CreateWorkspaceResponse"}},"400":{"description":"Invalid request body or calling key is not bound to a main workspace","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"403":{"description":"The workspace's plan does not include workspaces, or the subscription is not active","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"500":{"description":"Failed to create workspace","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}},"/workspaces/{id}/api-keys":{"post":{"security":[{"ApiKeyAuth":[]}],"description":"Mints an API key bound to a workspace the calling key already owns: its own workspace, or one of its sub-workspaces when called with a main workspace's key. The key returned by POST /r/workspaces is shown once and cannot be retrieved again, so this is how a workspace whose key was lost gets a new one without a UI step. The new key authorizes the named workspace only and never outlives the caller: when the calling key has an expiry, the minted key carries the same expiresAt. fullKey is returned exactly once.","consumes":["application/json"],"produces":["application/json"],"tags":["Workspace"],"summary":"Create an API key for a workspace","parameters":[{"type":"string","description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"type":"integer","description":"Workspace ID","name":"id","in":"path","required":true},{"description":"Key options","name":"request","in":"body","schema":{"$ref":"#/definitions/models.CreateWorkspaceApiKeyRequest"}}],"responses":{"201":{"description":"The workspace and its new API key","schema":{"$ref":"#/definitions/models.CreateWorkspaceApiKeyResponse"}},"400":{"description":"Invalid workspace ID or request body","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"401":{"description":"Unauthorized - invalid or missing API key","schema":{"$ref":"#/definitions/models.ErrorUnauthorized"}},"403":{"description":"The workspace's plan does not include the API, or the subscription is not active","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"404":{"description":"No such workspace under this account","schema":{"$ref":"#/definitions/models.ErrorResponse"}},"500":{"description":"Failed to create API key","schema":{"$ref":"#/definitions/models.ErrorResponse"}}}}}},"definitions":{"models.AttachCampaignSendersRequest":{"type":"object","required":["senderEmailIds"],"properties":{"senderEmailIds":{"description":"SenderEmailIDs are the sender emails to attach. Every ID must be a\nconnected sender email in your workspace; the call is refused whole if\nany is not.","type":"array","minItems":1,"items":{"type":"integer"},"example":[1,2]}}},"models.AttachCampaignSendersResponse":{"type":"object","properties":{"alreadyAttached":{"description":"AlreadyAttached lists the requested sender emails that were attached\nbefore this call (the call is idempotent: they are left untouched).","type":"array","items":{"type":"integer"},"example":[3]},"attached":{"description":"Attached lists the sender emails newly attached by this call.","type":"array","items":{"type":"integer"},"example":[1,2]},"campaignId":{"type":"integer","example":10},"message":{"type":"string","example":"sender emails attached successfully"},"senderEmailIds":{"description":"SenderEmailIDs lists every sender email attached to the campaign after\nthis call.","type":"array","items":{"type":"integer"},"example":[1,2,3]}}},"models.AudienceCountResponse":{"type":"object","properties":{"audienceSize":{"type":"integer","example":35627}}},"models.AudienceSizeRequest":{"type":"object","properties":{"companySizes":{"type":"array","items":{"type":"string"},"example":["11-50","51-200"]},"industries":{"type":"array","items":{"type":"string"},"example":["Software"]},"keywords":{"type":"array","items":{"type":"string"},"example":["b2b saas"]},"locations":{"type":"array","items":{"type":"string"},"example":["United States"]},"seniorities":{"type":"array","items":{"type":"string"},"example":["owner","director"]},"titles":{"type":"array","items":{"type":"string"},"example":["CEO","Head of Sales"]}}},"models.AudienceSizeResponse":{"type":"object","properties":{"audienceSize":{"type":"integer","example":12345},"icp":{"$ref":"#/definitions/models.ICPResponse"}}},"models.AutopilotAuditEvent":{"type":"object","properties":{"actor":{"type":"string","example":"system"},"at":{"type":"string","example":"2026-07-01T10:30:00Z"},"message":{"type":"string","example":"found prospects to reveal; no credits spent, waiting for approval before revealing them"},"stage":{"type":"string","example":"awaiting_approval"}}},"models.AutopilotPlanRequest":{"type":"object","properties":{"budgetUsd":{"description":"BudgetUsd is the total monthly budget in dollars, inclusive of the\nplatform subscription. Required, must be greater than zero.","type":"number","example":500},"maxMailboxes":{"description":"MaxMailboxes caps infrastructure regardless of budget. 0 means no cap.","type":"integer","example":20},"platformUsd":{"description":"PlatformUsd is the subscription cost to reserve before sizing\ninfrastructure. Defaults to 0.","type":"number","example":99}}},"models.AutopilotPlanResponse":{"type":"object","properties":{"credits":{"type":"integer","example":2310},"creditsUsd":{"type":"number","example":57.75},"domains":{"type":"integer","example":5},"expectedMeetingsPerMonth":{"description":"ExpectedMeetingsPerMonth is a planning ESTIMATE on pessimistic funnel\nassumptions, not a promise.","type":"number","example":6.1},"feasible":{"description":"Feasible reports whether the budget covers a usable setup at all.","type":"boolean","example":true},"firstMonthUsd":{"description":"FirstMonthUsd includes the one-off setup on top of the recurring cost.","type":"number","example":539.7},"mailboxes":{"type":"integer","example":14},"mailboxesMonthlyUsd":{"type":"number","example":70},"monthlySends":{"type":"integer","example":9240},"notes":{"description":"Notes explains anything worth surfacing to a human.","type":"array","items":{"type":"string"}},"prospectsPerMonth":{"type":"integer","example":2310},"recurringUsd":{"description":"RecurringUsd is the steady-state monthly cost.","type":"number","example":427.75},"setupUsd":{"description":"SetupUsd is the one-off cost in the first month (mailbox setup fees and\nannual domain registrations).","type":"number","example":111.95}}},"models.AutopilotRunResponse":{"type":"object","properties":{"approvedAt":{"description":"ApprovedAt is when a human approved the run (RFC3339), or null.","type":"string","example":"2026-07-01T10:30:00Z"},"approvedByUserId":{"description":"ApprovedByUserID is who approved the run, or null.","type":"integer","example":3},"audit":{"type":"array","items":{"$ref":"#/definitions/models.AutopilotAuditEvent"}},"autoOptimizeVariants":{"type":"boolean"},"autoTopupProspects":{"type":"boolean"},"campaignId":{"type":"integer","example":1234},"createdAt":{"type":"string","example":"2026-07-01T10:30:00Z"},"icpId":{"type":"integer","example":12},"id":{"type":"integer","example":7},"killSwitch":{"description":"KillSwitch reports whether the run has been permanently halted.","type":"boolean","example":false},"lastAdvancedAt":{"description":"LastAdvancedAt is when the run last made progress (RFC3339), or null.","type":"string","example":"2026-07-01T10:30:00Z"},"lastError":{"type":"string"},"prospectSearchId":{"type":"integer","example":56},"replyMode":{"type":"string","example":"draft"},"status":{"description":"Status is one of: pending, building_icp, writing_sequence,\nsourcing_prospects, awaiting_approval, provisioning_infrastructure,\nrunning, paused, completed, failed. A run is at sourcing_prospects twice:\nbefore approval it checks for free that prospects match, after approval\n(approvedAt set) it reveals the first batch, which spends credits.","type":"string","example":"awaiting_approval"},"targetProspectCount":{"type":"integer","example":500},"website":{"type":"string","example":"https://acme.com"}}},"models.AutopilotRunResult":{"type":"object","properties":{"plan":{"$ref":"#/definitions/models.AutopilotPlanResponse"},"run":{"$ref":"#/definitions/models.AutopilotRunResponse"}}},"models.BillingProfile":{"type":"object","properties":{"addressLineOne":{"type":"string","example":"1 Example Street"},"addressLineTwo":{"type":"string","example":"Suite 200"},"city":{"type":"string","example":"New York"},"company":{"type":"string","example":"Acme Ltd"},"country":{"description":"Country is an ISO 3166-1 alpha-2 code, uppercase.","type":"string","example":"US"},"firstName":{"type":"string","example":"Jane"},"lastName":{"type":"string","example":"Doe"},"phone":{"type":"string","example":"2125550142"},"phoneCc":{"description":"PhoneCc is the telephone country calling code without the plus.","type":"string","example":"1"},"postalAddress":{"description":"PostalAddress is the address rendered on one line, ready to paste into a\ncompliance footer. Read-only; it is derived from the fields above.","type":"string","example":"Acme Ltd, 1 Example Street, Suite 200, New York, NY 10001, US"},"postalCode":{"type":"string","example":"10001"},"state":{"type":"string","example":"NY"},"updatedAt":{"description":"UpdatedAt is when the profile was last written (RFC3339).","type":"string","example":"2026-08-03T10:30:00Z"}}},"models.BillingProfileResult":{"type":"object","properties":{"billingProfile":{"$ref":"#/definitions/models.BillingProfile"}}},"models.BlacklistListingResponse":{"type":"object","properties":{"answer":{"type":"string","example":"127.0.0.2"},"delistUrl":{"description":"DelistURL is where to request removal. Always present: a listing you\ncannot act on is trivia.","type":"string","example":"https://check.spamhaus.org/"},"kind":{"type":"string","example":"ip"},"name":{"type":"string","example":"Spamhaus ZEN"},"reason":{"type":"string","example":"On the SBL: the IP is on Spamhaus's main spam-source list."},"txt":{"type":"string"},"value":{"type":"string","example":"203.0.113.5"},"zone":{"type":"string","example":"zen.spamhaus.org"}}},"models.BlocklistAddRequest":{"type":"object","required":["values"],"properties":{"scope":{"description":"Scope is \"workspace\" (default) or \"global\". A global entry suppresses\nacross every workspace on the account and can only be written with a\nmain workspace's API key.","type":"string","enum":["workspace","global"],"example":"workspace"},"values":{"description":"Values holds the email addresses and/or domains to block, up to 1000 per\nrequest.","type":"array","items":{"type":"string"},"example":["competitor.com","jane@acme.com"]}}},"models.BlocklistAddResponse":{"type":"object","properties":{"created":{"description":"Created is the number of new suppression entries written.","type":"integer","example":42},"scope":{"description":"Scope is the list the entries were written to.","type":"string","enum":["workspace","global"],"example":"workspace"},"skipped":{"description":"Skipped is the number of values ignored because they already existed or\nfailed validation.","type":"integer","example":3}}},"models.BlocklistDeleteEntryResponse":{"type":"object","properties":{"id":{"type":"integer","example":17},"message":{"type":"string","example":"blocklist entry deleted successfully"}}},"models.BlocklistDeleteRequest":{"type":"object","properties":{"ids":{"description":"Ids are entry ids as returned by GET /blocklist.","type":"array","items":{"type":"integer"}},"scope":{"description":"Scope limits the delete to one list: \"workspace\" (default), \"global\", or\n\"all\". Deleting from \"global\" requires a main workspace's key.","type":"string","enum":["workspace","global","all"],"example":"workspace"},"values":{"description":"Values are domains or email addresses to unblock, matched\ncase-insensitively. Values not present are counted as skipped.","type":"array","items":{"type":"string"},"example":["competitor.com","jane@acme.com"]}}},"models.BlocklistDeleteResponse":{"type":"object","properties":{"deleted":{"description":"Deleted is the number of entries removed.","type":"integer","example":12},"skipped":{"description":"Skipped is the number of requested values or ids that matched nothing\nthe caller may delete.","type":"integer","example":1}}},"models.BlocklistEntry":{"type":"object","properties":{"createdAt":{"type":"string"},"domain":{"type":"string","example":"competitor.com"},"emailAddress":{"description":"EmailAddress is the blocked address when the entry blocks one address\nrather than a whole domain.","type":"string","example":"jane@acme.com"},"id":{"type":"integer","example":17},"scope":{"description":"Scope is \"workspace\" for the calling workspace's own entry, \"global\" for\nan account-wide one.","type":"string","enum":["workspace","global"],"example":"workspace"},"workspaceId":{"description":"WorkspaceId is the workspace the entry is stored on. For a global entry\nread from a sub-workspace this is the main workspace, not the caller.","type":"integer","example":12}}},"models.BlocklistListResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/definitions/models.BlocklistEntry"}},"totalCount":{"type":"integer","example":214}}},"models.BlocklistUpdateRequest":{"type":"object","properties":{"scope":{"description":"Scope moves the entry between the workspace list and the account-wide\nlist. Moving an entry to \"global\" requires a main workspace's key.","type":"string","enum":["workspace","global"],"example":"global"},"value":{"description":"Value is the new domain or email address. Which of the two the entry\nholds is re-inferred from whether the value contains \"@\", so an entry\ncan be converted between a domain block and an address block.","type":"string","example":"competitor.com"}}},"models.CampaignAccountHealth":{"type":"object","properties":{"address":{"type":"string","example":"john@example.com"},"healthScore":{"description":"Health score 0-100, higher is better: the share of this account's\nwarm-up emails over the last 7 full days that landed in the inbox rather\nthan spam. Null while warm-up is off or before 20 warm-up emails were\nchecked.","type":"integer","example":72},"heldBack":{"description":"HeldBack is true when the campaign's minimum holds this account back:\nit sends nothing in this campaign until its score is back at or above\nthe minimum.","type":"boolean","example":true},"reason":{"description":"Reason says why it is held back, in plain words. Null when it sends.","type":"string","example":"Health score 72% is below this campaign's minimum of 80%."},"senderEmailId":{"type":"integer","example":123}}},"models.CampaignDetailStats":{"type":"object","properties":{"bouncedLeadsCount":{"type":"integer"},"contactedLeadsCount":{"type":"integer"},"emailsSentCount":{"type":"integer"},"leadsRespondedPositivelyCount":{"type":"integer"},"repliedLeadsCount":{"type":"integer"}}},"models.CampaignEmailCounts":{"type":"object","properties":{"blocked":{"type":"integer","example":2},"bounced":{"type":"integer","example":20},"canceled":{"type":"integer","example":15},"catchAll":{"type":"integer","example":12},"deferred":{"type":"integer","example":5},"delivered":{"type":"integer","example":280},"draft":{"type":"integer","example":20},"dropped":{"type":"integer","example":3},"errorOnSent":{"type":"integer","example":5},"followUp":{"type":"integer","example":50},"followUpCanceled":{"type":"integer","example":10},"followUpDraft":{"type":"integer","example":10},"invalid":{"type":"integer","example":8},"pending":{"type":"integer","example":100},"replied":{"type":"integer","example":50},"scheduled":{"type":"integer","example":500},"sent":{"type":"integer","example":300},"spamReport":{"type":"integer","example":2},"total":{"type":"integer","example":1000},"waitingForReschedule":{"type":"integer","example":5}}},"models.CampaignListItem":{"type":"object","properties":{"createdAt":{"type":"string"},"emoji":{"type":"string"},"flow":{"type":"string"},"id":{"type":"integer"},"name":{"type":"string"},"stats":{"$ref":"#/definitions/models.CampaignStats"},"status":{"type":"string"},"updatedAt":{"type":"string"}}},"models.CampaignMinimumHealth":{"type":"object","properties":{"accounts":{"description":"Accounts are the campaign's connected email accounts, the held-back ones\nfirst.","type":"array","items":{"$ref":"#/definitions/models.CampaignAccountHealth"}},"allHeldBack":{"description":"AllHeldBack is true when a minimum is set and every connected account is\nbelow it, so the campaign sends nothing.","type":"boolean","example":false},"heldBackCount":{"description":"HeldBackCount is how many of those accounts the minimum holds back.","type":"integer","example":1},"message":{"description":"Message says what the minimum is doing, in plain words. Null when it\nholds nothing back.","type":"string","example":"1 of 3 email accounts is below this campaign's minimum health score of 80% and sends nothing in it until its score is back up."},"minimumHealthScore":{"description":"MinimumHealthScore is the campaign's minimum, 1-100. Null when it has\nnone.","type":"integer","example":80}}},"models.CampaignSchedule":{"type":"object","properties":{"cronSchedule":{"type":"string","example":"0 9 * * 1-5"},"daysSchedule":{"description":"DaysSchedule is the sending days as one comma-separated string of\nweekday numbers, where Sunday is 0: \"1,2,3,4,5\" is Monday to Friday.\nIt is not an array. Empty until days are set with\nPUT /campaigns/{id}/schedule.","type":"string","example":"1,2,3,4,5"},"endSchedule":{"type":"string","example":"17:00"},"everySchedule":{"type":"integer","example":30},"maximumTimeBetweenEmails":{"type":"integer","example":15},"minimumTimeBetweenEmails":{"type":"integer","example":5},"startSchedule":{"type":"string","example":"09:00"}}},"models.CampaignSettings":{"type":"object","properties":{"allowNonBusinessEmails":{"type":"boolean","example":false},"dailyLimit":{"description":"DailyLimit is the campaign-level cap on total emails scheduled per\ncalendar day (campaign timezone). null means no campaign-level cap.","type":"integer","example":200},"ignoreOutOfOfficeReplies":{"type":"boolean","example":true},"isEnabledCatchallValidated":{"type":"boolean","example":true},"isEnabledEmailVerifier":{"type":"boolean","example":false},"isEnabledIgnoreHardBouncedLeads":{"type":"boolean","example":true},"isEnabledIgnoreLeadsWhoAlreadyResponded":{"type":"boolean","example":true},"isEnabledLlm":{"type":"boolean","example":true},"isEnabledSkipLeadIfAlreadyExists":{"type":"boolean","example":false},"isEnabledStopFollowUpsAcrossCampaigns":{"type":"boolean","example":true},"isEnabledStopFollowUpsForSameCompany":{"type":"boolean","example":false},"isEnabledStopFollowUpsOnReply":{"type":"boolean","example":true},"maximumSendingLimitPerSenderEmail":{"type":"integer","example":50},"maximumSendingLimitPerSenderEmailVariation":{"type":"integer","example":10},"minimumHealthScore":{"description":"MinimumHealthScore is the lowest email account health score that may\nsend in this campaign, 1-100. null means no minimum.","type":"integer","example":80}}},"models.CampaignStats":{"type":"object","properties":{"bouncedLeadsCount":{"type":"integer"},"contactedLeadsCount":{"type":"integer"},"emailsSentCount":{"type":"integer"},"leadsRespondedPositivelyCount":{"type":"integer"},"repliedLeadsCount":{"type":"integer"},"senderEmailsConnected":{"type":"integer"},"senderEmailsDisconnected":{"type":"integer"},"senderEmailsTotal":{"type":"integer"}}},"models.CampaignStatsDay":{"type":"object","properties":{"date":{"type":"string","example":"2026-07-01"},"positive":{"type":"integer","example":1},"replied":{"type":"integer","example":3},"sent":{"type":"integer","example":40}}},"models.CampaignStatsStep":{"type":"object","properties":{"index":{"type":"integer","example":0},"replied":{"type":"integer","example":45},"sent":{"type":"integer","example":600}}},"models.CampaignStatsTotals":{"type":"object","properties":{"bounced":{"type":"integer","example":14},"meetings":{"type":"integer","example":7},"positive":{"type":"integer","example":32},"replied":{"type":"integer","example":85},"sent":{"type":"integer","example":1200}}},"models.CampaignStatsVariant":{"type":"object","properties":{"label":{"type":"string","example":"A"},"positive":{"type":"integer","example":18},"replied":{"type":"integer","example":45},"sent":{"type":"integer","example":600}}},"models.CancelEmailVerificationJobResponse":{"type":"object","properties":{"message":{"type":"string","example":"job canceled"}}},"models.CheckDfyDomainResponse":{"type":"object","properties":{"available":{"type":"boolean","example":true},"domain":{"type":"string","example":"acme-outreach.com"},"price":{"type":"number","example":13.99},"reason":{"description":"Reason says why the domain cannot be bought, in words that can be shown\nto a person. Empty when available is true.","type":"string","example":"That domain is already registered."},"tld":{"type":"string","example":"com"},"unconfirmed":{"description":"Unconfirmed is true when we could not get an answer at all rather than\nthe answer \"no\". Do not report these as taken; offer a retry.","type":"boolean","example":false}}},"models.CheckDfyDomainsRequest":{"type":"object","required":["domains"],"properties":{"domains":{"type":"array","items":{"type":"string"},"example":["acme-outreach.com","acme-outreach.org"]}}},"models.CheckDfyDomainsResponse":{"type":"object","properties":{"domains":{"type":"array","items":{"$ref":"#/definitions/models.CheckDfyDomainResponse"}}}},"models.ConnectSenderEmailRequest":{"type":"object","required":["email","imapServerUrl","password","smtpServerUrl"],"properties":{"email":{"type":"string"},"firstName":{"type":"string","maxLength":255},"imapServerUrl":{"description":"ImapServerUrl and SmtpServerUrl accept the provider's host, optionally\nwith a port, for example \"imap.fastmail.com:993\".","type":"string","minLength":1},"lastName":{"type":"string","maxLength":255},"loginString":{"description":"LoginString defaults to the email address when omitted, which is what\nmost providers expect.","type":"string"},"password":{"type":"string","minLength":1},"smtpServerUrl":{"type":"string","minLength":1}}},"models.ConnectSenderEmailResponse":{"type":"object","properties":{"message":{"type":"string","example":"sender email connected successfully"},"senderEmail":{"$ref":"#/definitions/models.SenderEmailResponse"}}},"models.ConnectionHistoryItem":{"type":"object","properties":{"campaignId":{"type":"integer","example":10},"createdAt":{"type":"string","example":"2024-01-15T10:30:00Z"},"disconnectionReason":{"type":"string","example":"token_expired"},"errorCode":{"type":"string","example":"AUTH_FAILED"},"errorMessage":{"type":"string","example":"Token has expired"},"eventType":{"type":"string","example":"disconnected"},"id":{"type":"integer","example":456},"userId":{"type":"integer","example":5}}},"models.ConversationItem":{"type":"object","properties":{"body":{"type":"string","example":"Hi Jane, I noticed that..."},"campaignId":{"type":"integer","example":12},"createdAt":{"type":"string","example":"2026-07-30T08:55:00Z"},"direction":{"description":"Direction is \"inbound\" (received from the prospect) or \"outbound\"\n(sent, scheduled, or drafted by the workspace).","type":"string","example":"outbound"},"fromAddress":{"type":"string","example":"jane@prospect.com"},"id":{"type":"integer","example":456},"isDraft":{"description":"IsDraft marks unsent drafts (status draft or followup_draft), including\nAI-suggested replies awaiting human review. An AI reply draft (campaignId\nnull) can be edited with PUT /reply-drafts/{id} and sent with\nPOST /reply-drafts/{id}/send, using this id. Drafts that belong to a\ncampaign cannot be sent this way.","type":"boolean","example":false},"responseCategory":{"description":"ResponseCategory is set on categorized inbound emails and null\neverywhere else.","type":"string","example":"interested"},"sentAt":{"description":"SentAt is the scheduled or actual send time and null when the email has\nnone (e.g. an AI draft that was never scheduled).","type":"string","example":"2026-07-30T09:00:00Z"},"status":{"type":"string","example":"sent"},"subject":{"type":"string","example":"Quick question"},"threadId":{"type":"string","example":"19842fa1b2c3d4e5"}}},"models.CopilotICP":{"type":"object","properties":{"companySizes":{"type":"array","items":{"type":"string"}},"icpName":{"type":"string","example":"Mid-market SaaS RevOps leaders"},"industries":{"type":"array","items":{"type":"string"}},"personas":{"type":"array","items":{"$ref":"#/definitions/models.CopilotPersona"}},"seniorities":{"type":"array","items":{"type":"string"}},"suggestedSalesNavKeywords":{"type":"array","items":{"type":"string"}},"summary":{"type":"string"},"titles":{"type":"array","items":{"type":"string"}}}},"models.CopilotLaunchRequest":{"type":"object","required":["name","sequence"],"properties":{"icp":{"description":"ICP is optional and carried through for reference only.","allOf":[{"$ref":"#/definitions/models.CopilotICP"}]},"name":{"description":"Name is the campaign name.","type":"string","example":"Acme outbound Q3"},"salesNavData":{"description":"SalesNavData is the optional base64 browser-extension payload that carries\nthe LinkedIn session required to actually run the search. When omitted the\nlead engine cannot authenticate to LinkedIn (see docs), so provide it to\nstart real extraction. It needs SalesNavSearchURL.","type":"string"},"salesNavSearchUrl":{"description":"SalesNavSearchURL is the optional LinkedIn Sales Navigator search URL the\nlead engine should extract leads from. Leave it out to create the draft\nwith its emails only and add leads from Lead Finder or a CSV upload. It is\nrequired when SalesNavData is given.","type":"string","example":"https://www.linkedin.com/sales/search/people?..."},"sequence":{"description":"Sequence is the cold-email sequence to seed the draft campaign with.","type":"array","items":{"$ref":"#/definitions/models.CopilotSequenceStep"}}}},"models.CopilotLaunchResponse":{"type":"object","properties":{"campaignId":{"type":"integer","example":1234},"jobId":{"type":"string","example":"5678"},"message":{"type":"string","example":"draft campaign created; lead finding started"},"status":{"type":"string","example":"draft"}}},"models.CopilotPersona":{"type":"object","properties":{"seniority":{"type":"string","example":"VP"},"title":{"type":"string","example":"VP of Sales"}}},"models.CopilotPlanRequest":{"type":"object","required":["website"],"properties":{"context":{"description":"Context is optional extra guidance from the caller (e.g. \"we sell to\ndentists in the US\").","type":"string","example":"we sell to dental clinics in the US"},"description":{"description":"Description is what the company sells and to whom, in its own words.\nWhen given, the plan is built from it and the website is not read: send\nit when the website shows a placeholder page (the 422 answer).","type":"string","example":"Bookkeeping and payroll for independent restaurants"},"website":{"description":"Website is the company homepage to analyse (with or without scheme).","type":"string","example":"https://acme.com"}}},"models.CopilotPlanResponse":{"type":"object","properties":{"icp":{"$ref":"#/definitions/models.CopilotICP"},"suggestedSequence":{"type":"array","items":{"$ref":"#/definitions/models.CopilotSequenceStep"}}}},"models.CopilotSequenceStep":{"type":"object","required":["body","subject"],"properties":{"body":{"type":"string","example":"Hi {first_name}, ..."},"followUpAfter":{"description":"FollowUpAfter is the number of days after the previous step. 0 for the\ninitial email; 1-30 for follow-ups.","type":"integer","example":3},"subject":{"type":"string","example":"quick question about {company_name}"}}},"models.CreateCampaignRequest":{"type":"object","required":["name"],"properties":{"allowNonBusinessEmails":{"type":"boolean"},"dailyLimit":{"description":"DailyLimit caps the total emails (initial + follow-ups) this campaign may\nschedule per calendar day in the campaign timezone. Omit for no\ncampaign-level cap; per-inbox limits still apply either way.","type":"integer","maximum":10000,"minimum":1},"emoji":{"type":"string","maxLength":10,"minLength":1},"flow":{"description":"Flow defaults to multiple_leads_scheduled, which is what the app creates,\nso a campaign made through the API behaves identically to one made in the\nUI. The api flow skips lead validation and the processing-leads guard at\nlaunch; pass it only if you want that.","type":"string","enum":["multiple_leads_scheduled","api"]},"ignoreOutOfOfficeReplies":{"type":"boolean"},"isEnabledCatchallValidated":{"type":"boolean"},"isEnabledEmailVerifier":{"type":"boolean"},"isEnabledIgnoreHardBouncedLeads":{"type":"boolean"},"isEnabledIgnoreLeadsWhoAlreadyResponded":{"type":"boolean"},"isEnabledLlm":{"description":"Settings - all optional, mirroring UpdateCampaignRequest","type":"boolean"},"isEnabledSkipLeadIfAlreadyExists":{"type":"boolean"},"isEnabledStopFollowUpsForSameCompany":{"type":"boolean"},"isEnabledStopFollowUpsOnReply":{"type":"boolean"},"maximumSendingLimitPerSenderEmail":{"type":"integer","maximum":10000,"minimum":1},"maximumSendingLimitPerSenderEmailVariation":{"type":"integer","maximum":100,"minimum":0},"maximumTimeBetweenEmails":{"type":"integer","maximum":30,"minimum":2},"minimumHealthScore":{"description":"MinimumHealthScore, 1-100, is the lowest email account health score\n(see healthScore on the sender emails) that may send in this campaign.\nAn account below it, or with no score yet, sends nothing here, first\nemails and follow-ups alike, until its score is back at or above it; its\nconversations wait for it and never move to another account. Omit or\nsend 0 for no minimum.","type":"integer","maximum":100,"minimum":0,"example":80},"minimumTimeBetweenEmails":{"type":"integer","maximum":30,"minimum":2},"name":{"type":"string","maxLength":255,"minLength":1},"timezone":{"type":"string"}}},"models.CreateCampaignResponse":{"type":"object","properties":{"campaign":{"$ref":"#/definitions/models.CreatedCampaign"},"message":{"type":"string","example":"campaign created successfully"}}},"models.CreateDfyOrderRequest":{"type":"object","properties":{"domains":{"type":"array","items":{"$ref":"#/definitions/models.DfyDomainInput"}},"forwardingDomain":{"description":"ForwardingDomain is where the purchased domains redirect visitors.","type":"string","example":"acme.com"},"mailboxes":{"type":"array","items":{"$ref":"#/definitions/models.DfyMailboxInput"}}}},"models.CreateEmailVerificationJobRequest":{"type":"object","required":["emails","name"],"properties":{"emails":{"description":"Emails are the addresses to verify. Duplicates are removed before\nanything is charged, and syntactically invalid entries are returned in\ninvalid_emails rather than billed.","type":"array","items":{"type":"string"},"example":["ada@example.com","grace@example.com"]},"name":{"description":"Name is what the job is called in the app. Required.","type":"string","example":"Q3 conference list"}}},"models.CreateEmailVerificationJobResponse":{"type":"object","properties":{"duplicate_rows":{"description":"DuplicateRows is how many repeats were removed. Each one would have been\na second paid check for an answer already bought.","type":"integer","example":12},"estimate_high_usd":{"description":"EstimateHighUsd assumes every address runs both providers. This is the\ncommon case and the number to plan against.","type":"number","example":8.5},"estimate_low_usd":{"description":"EstimateLowUsd assumes every address settles on the first provider.","type":"number","example":4.25},"id":{"description":"ID of the created job.","type":"string","example":"1234"},"invalid_emails":{"description":"InvalidEmails are the submitted entries that are not addresses at all.\nThey were not charged for and are not in the job.","type":"array","items":{"type":"string"}},"total":{"description":"Total is how many addresses were accepted, after removing duplicates\nand unparseable entries.","type":"integer","example":2500}}},"models.CreateICPRequest":{"type":"object","required":["name"],"properties":{"companySizes":{"type":"array","items":{"type":"string"}},"industries":{"type":"array","items":{"type":"string"}},"keywords":{"type":"array","items":{"type":"string"}},"locations":{"type":"array","items":{"type":"string"}},"makePrimary":{"description":"MakePrimary promotes this profile to the workspace's active one,\ndemoting any existing primary.","type":"boolean"},"name":{"type":"string","example":"Mid-market SaaS RevOps leaders"},"seniorities":{"type":"array","items":{"type":"string"}},"summary":{"description":"Summary is one to three sentences describing who the workspace sells to.","type":"string"},"titles":{"type":"array","items":{"type":"string"}}}},"models.CreateLeadsResponse":{"type":"object","properties":{"count":{"type":"integer","example":5},"leads":{"type":"array","items":{"$ref":"#/definitions/models.CreatedLead"}},"message":{"type":"string","example":"leads processed successfully"}}},"models.CreateOrUpdateLeadsRequest":{"type":"object","required":["leads"],"properties":{"campaignId":{"type":"integer"},"leads":{"type":"array","maxItems":1000,"minItems":1,"items":{"$ref":"#/definitions/models.LeadInput"}}}},"models.CreateWorkspaceApiKeyRequest":{"type":"object","properties":{"name":{"description":"Name shown in the app's API key list. Defaults to\n\"<workspace name> key\". Trimmed; up to 60 characters.","type":"string","example":"VoiceDrop outbound key"},"readOnly":{"description":"ReadOnly mints a key that can call GET routes and four POSTs that buy\nand send nothing (/r/audience/size, /r/setup/ping,\n/r/dfy/domains/check and /r/lead-finder/searches), and nothing else.\nDefaults to false, which mints a read+write key.","type":"boolean","example":false}}},"models.CreateWorkspaceApiKeyResponse":{"type":"object","properties":{"apiKey":{"$ref":"#/definitions/models.CreatedApiKey"},"note":{"description":"Note reminds integrators that fullKey is not retrievable later.","type":"string","example":"Store apiKey.fullKey now: it cannot be retrieved again."},"workspace":{"$ref":"#/definitions/models.WorkspaceItem"}}},"models.CreateWorkspaceRequest":{"type":"object","required":["name"],"properties":{"generateApiKey":{"description":"GenerateApiKey mints a read+write API key bound to the new workspace.\nDefaults to true; pass false to create the workspace only.","type":"boolean","example":true},"name":{"description":"Name of the new workspace. Trimmed; 1-60 characters.","type":"string","example":"Acme Outbound"}}},"models.CreateWorkspaceResponse":{"type":"object","properties":{"apiKey":{"description":"ApiKey is null when generateApiKey was false.","allOf":[{"$ref":"#/definitions/models.CreatedApiKey"}]},"note":{"description":"Note reminds integrators that fullKey is not retrievable later.","type":"string","example":"Store apiKey.fullKey now: it cannot be retrieved again."},"workspace":{"$ref":"#/definitions/models.WorkspaceItem"}}},"models.CreatedApiKey":{"type":"object","properties":{"expiresAt":{"description":"ExpiresAt is set when the key expires: it inherits the calling key's\nown expiry, so a temporary key never mints a permanent one. Null for\nkeys minted by a non-expiring key.","type":"string","example":"2026-09-07T12:00:00Z"},"fullKey":{"type":"string","example":"run_abc12345_..."},"id":{"type":"integer","example":12},"name":{"type":"string","example":"Acme Outbound key"},"scopes":{"type":"array","items":{"type":"string"},"example":["read","read_write"]}}},"models.CreatedCampaign":{"type":"object","properties":{"createdAt":{"type":"string","example":"2024-01-15T10:30:00Z"},"emoji":{"type":"string","example":"📥"},"flow":{"type":"string","example":"multiple_leads_scheduled"},"id":{"type":"integer","example":8589949820},"name":{"type":"string","example":"RB2B High Intent"},"status":{"type":"string","example":"draft"},"timezone":{"type":"string","example":"America/New_York"},"updatedAt":{"type":"string","example":"2024-01-15T10:30:00Z"}}},"models.CreatedLead":{"type":"object","properties":{"email":{"type":"string","example":"john.doe@example.com"},"id":{"type":"integer","example":123}}},"models.CreditBalanceResponse":{"type":"object","properties":{"available":{"type":"integer","example":1500},"lifetimeGranted":{"type":"integer","example":5000},"lifetimeUsed":{"type":"integer","example":3400},"monthlyGrant":{"type":"integer","example":2000},"reserved":{"type":"integer","example":100},"unlimited":{"description":"Unlimited is true when this workspace's credits are free: every credit\naction goes through without spending available, nothing is refused for\nlack of credits, and POST /credits/purchase is refused (code\ncredits_free) because there is nothing to buy.","type":"boolean","example":false}}},"models.CreditTransactionItem":{"type":"object","properties":{"amount":{"description":"Amount is the signed credit delta: negative for spend, positive for\ngrants and refunds.","type":"integer","example":2000},"balanceAfter":{"description":"BalanceAfter is the available balance immediately after this entry.","type":"integer","example":2000},"createdAt":{"type":"string","example":"2026-07-01T10:30:00Z"},"description":{"type":"string","example":"Monthly plan credits"},"free":{"description":"Free is true for an entry written while this workspace's credits were\nfree. Its amount is what the action would have cost, no credits moved,\nand balanceAfter is the balance it found.","type":"boolean","example":false},"id":{"type":"integer","example":42},"kind":{"description":"Kind is the movement type: grant, topup, reserve, commit, refund,\nexpire or adjustment.","type":"string","example":"grant"},"reason":{"description":"Reason is what the credits were spent on or granted for.","type":"string","example":"monthly_grant"},"reference":{"description":"Reference correlates the entry to what caused it, e.g. 'prospect_search:123'.","type":"string"}}},"models.DeleteCampaignResponse":{"type":"object","properties":{"id":{"type":"integer","example":123},"message":{"type":"string","example":"campaign deleted successfully"}}},"models.DeleteICPResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true}}},"models.DeleteLeadResponse":{"type":"object","properties":{"id":{"type":"integer","example":123},"message":{"type":"string","example":"lead deleted successfully"}}},"models.DeleteWebhookResponse":{"type":"object","properties":{"id":{"type":"integer","example":123},"message":{"type":"string","example":"webhook deleted successfully"}}},"models.DetachCampaignSenderResponse":{"type":"object","properties":{"campaignId":{"type":"integer","example":10},"detached":{"description":"Detached is false when the sender email was not attached to the\ncampaign (the call is idempotent and does nothing in that case).","type":"boolean","example":true},"message":{"type":"string","example":"sender email detached successfully"},"senderEmailIds":{"description":"SenderEmailIDs lists every sender email still attached to the campaign\nafter this call.","type":"array","items":{"type":"integer"},"example":[2,3]}}},"models.DfyDomainInput":{"type":"object","required":["domainName"],"properties":{"domainName":{"type":"string","example":"acme-mail.com"}}},"models.DfyDomainSuggestion":{"type":"object","properties":{"available":{"type":"boolean","example":true},"domain":{"type":"string","example":"acme-mail.com"},"price":{"type":"number","example":13.99},"tld":{"type":"string","example":"com"}}},"models.DfyMailboxInput":{"type":"object","required":["domainName","firstName","lastName","username"],"properties":{"domainName":{"type":"string","example":"acme-mail.com"},"firstName":{"type":"string","example":"John"},"lastName":{"type":"string","example":"Doe"},"profilePicture":{"type":"string"},"username":{"type":"string","example":"john"}}},"models.DfyOrderConflictResponse":{"type":"object","properties":{"error":{"type":"string","example":"acme-mail.com is already on order 9."},"existingOrderId":{"type":"integer","example":9}}},"models.DfyOrderResponse":{"type":"object","properties":{"completedAt":{"description":"CompletedAt is when the order completed (RFC3339), or null.","type":"string","example":"2026-07-01T10:35:00Z"},"cost_breakdown":{},"createdAt":{"type":"string","example":"2026-07-01T10:30:00Z"},"domains":{},"externalOrderId":{"type":"string","example":"cmr_order_456"},"failureReason":{"type":"string"},"forwardingDomain":{},"id":{"type":"integer","example":9},"mailboxes":{},"processedAt":{"description":"ProcessedAt is when the order started processing (RFC3339), or null.","type":"string","example":"2026-07-01T10:30:00Z"},"status":{"description":"Status is one of: created, pending_approval, processing, completed,\nfailed, partially_completed, canceled.","type":"string","example":"created"}}},"models.DfyOrderResult":{"type":"object","properties":{"order":{"$ref":"#/definitions/models.DfyOrderResponse"}}},"models.DkimCheckResult":{"type":"object","properties":{"checkedSelectors":{"description":"CheckedSelectors is every selector name looked up, in the order tried.","type":"array","items":{"type":"string"}},"issues":{"type":"array","items":{"type":"string"}},"record":{"type":"string","example":"v=DKIM1; k=rsa; p=MIGfMA0GCSq"},"selector":{"description":"Selector is the DKIM selector the record was found under, null when no\nrecord was found.","type":"string","example":"google"},"selectorSource":{"description":"SelectorSource says where selector came from: SIGNATURE (read off the\nDKIM-Signature of this domain's warm-up mail), CUSTOMER (set in the app)\nor COMMON (a provider's default name). Null when none was found.","type":"string","example":"COMMON"},"status":{"description":"Status is one of OK, WARNING, MISSING, ERROR. MISSING means no key was\nfound under the selectors in checkedSelectors: a provider that picks its\nown selector name can have DKIM working while this says MISSING. ERROR\nmeans a DNS lookup timed out; try again.","type":"string","example":"OK"}}},"models.DmarcCheckResult":{"type":"object","properties":{"issues":{"type":"array","items":{"type":"string"}},"policy":{"description":"Policy is the record's p= tag, null when no record was found.","type":"string","example":"reject"},"record":{"type":"string","example":"v=DMARC1; p=reject; rua=mailto:dmarc@example.com"},"status":{"description":"Status is one of OK, WARNING, MISSING, ERROR.","type":"string","example":"OK"}}},"models.EmailVerificationErrorResponse":{"type":"object","properties":{"allowance":{"description":"Allowance is set on trial_allowance_exceeded: how many addresses a free\ntrial may submit in total.","type":"integer","example":100},"available_usd":{"description":"AvailableUsd is set on insufficient_allowance: what is left this month.","type":"number","example":2.1},"error":{"description":"Error is the machine-readable code: insufficient_allowance,\ntrial_allowance_exceeded, subscription_not_active,\nno_active_subscription or invalid_request.","type":"string","example":"insufficient_allowance"},"message":{"description":"Message is the human-readable explanation.","type":"string","example":"This job needs $8.50 of monthly verification allowance and $2.10 is left."},"needed_usd":{"description":"NeededUsd is set on insufficient_allowance: what the job would cost at\nthe ceiling.","type":"number","example":8.5},"remaining":{"description":"Remaining is set on trial_allowance_exceeded: how many of those\naddresses are still available. Zero is a real value, so it is a pointer.","type":"integer","example":25}}},"models.EmailVerificationJobResponse":{"type":"object","properties":{"catchall":{"description":"Catchall accepts everything at the domain, so deliverability is likely\nbut not proven.","type":"integer","example":400},"end_time":{"description":"EndTime in RFC3339, zero while the job is still running.","type":"string","example":"2026-09-10T09:14:00Z"},"error_code":{"description":"ErrorCode is empty on a healthy job. monthly_limit_reached means the\nallowance ran out and the remaining addresses came back unknown rather\nthan missing; they are still in the download, marked.","type":"string","example":""},"id":{"type":"string","example":"1234"},"invalid":{"description":"Invalid will bounce.","type":"integer","example":280},"meter_events":{"description":"MeterEvents is what the job has billed so far. Every check runs up to\ntwo providers and each one that answers is one event, so an address\ncosts one event or two.","type":"integer","example":3100},"name":{"description":"Name the job was created with.","type":"string","example":"Q3 conference list"},"processed":{"description":"Processed is how many have a verdict yet.","type":"integer","example":1800},"spent_usd":{"description":"SpentUsd is MeterEvents priced at the meter rate: what this job adds to\nthe invoice, not an estimate.","type":"number","example":5.27},"start_time":{"description":"StartTime in RFC3339.","type":"string","example":"2026-09-10T09:00:00Z"},"state":{"description":"State is pending, running, done, failed or canceled.","type":"string","example":"running"},"total":{"description":"Total addresses in the job.","type":"integer","example":2500},"unknown":{"description":"Unknown got no verdict, so it was NOT billed. Usually a provider\nfailure or the workspace's monthly verification allowance running out\nmid-job, in which case error_code says which.","type":"integer","example":20},"valid":{"description":"Valid is deliverable.","type":"integer","example":1100}}},"models.EmailVerificationRatesResponse":{"type":"object","properties":{"first_check_usd":{"description":"What one first check costs.","type":"number","example":0.0017},"per_address_ceiling_usd":{"description":"PerAddressCeilingUsd is the most an address can cost: both providers\nanswering. This is the common case, so budget against this one.","type":"number","example":0.0034},"per_address_floor_usd":{"description":"PerAddressFloorUsd is the least an address can cost: the first provider\nalone, which happens when it rejects the address outright.","type":"number","example":0.0017},"second_check_usd":{"description":"What one second check costs.","type":"number","example":0.0017}}},"models.EmailVerificationRecordResponse":{"type":"object","properties":{"email":{"type":"string","example":"ada@example.com"},"error":{"description":"Error is set when the address could not be answered.","type":"string","example":""},"first_check_result":{"description":"What the first check said.","type":"string","example":"valid"},"id":{"type":"string","example":"98765"},"meter_events":{"description":"MeterEvents is what this address billed: 0, 1 or 2.","type":"integer","example":2},"result":{"description":"Result is valid, catchall_validated, invalid or unknown. The same four\nvalues a campaign's send-time gate uses, so an address checked here and\none checked inside a campaign answer the same.","type":"string","example":"valid"},"second_check_result":{"description":"What the second check said. unknown means it never ran, which is what\nhappens when the first check rejected the address outright, and nothing\nwas billed for it.","type":"string","example":"valid"},"status":{"description":"Status is done, failed, skipped or pending.","type":"string","example":"done"},"verified_at":{"description":"VerifiedAt in RFC3339.","type":"string","example":"2026-09-10T09:02:11Z"}}},"models.ErrorBillingProfileNotFound":{"type":"object","properties":{"error":{"type":"string","example":"no billing profile set for this workspace"}}},"models.ErrorCampaignNotFound":{"type":"object","properties":{"error":{"type":"string","example":"campaign not found"}}},"models.ErrorCampaignValidation":{"type":"object","properties":{"error":{"type":"string","example":"campaign schedule days not configured"}}},"models.ErrorFailedToCheckDNS":{"type":"object","properties":{"error":{"type":"string","example":"failed to check dns records"}}},"models.ErrorFailedToConnectSenderEmail":{"type":"object","properties":{"error":{"type":"string","example":"failed to connect sender email"}}},"models.ErrorFailedToCreateCampaign":{"type":"object","properties":{"error":{"type":"string","example":"failed to create campaign"}}},"models.ErrorFailedToCreateLeads":{"type":"object","properties":{"error":{"type":"string","example":"failed to create leads"}}},"models.ErrorFailedToDeleteCampaign":{"type":"object","properties":{"error":{"type":"string","example":"failed to delete campaign"}}},"models.ErrorFailedToDeleteLead":{"type":"object","properties":{"error":{"type":"string","example":"failed to delete lead"}}},"models.ErrorFailedToDeleteWebhook":{"type":"object","properties":{"error":{"type":"string","example":"failed to delete webhook"}}},"models.ErrorFailedToGetWarmupSettings":{"type":"object","properties":{"error":{"type":"string","example":"failed to get warm-up settings"}}},"models.ErrorFailedToLaunchCampaign":{"type":"object","properties":{"error":{"type":"string","example":"failed to launch/resume campaign"}}},"models.ErrorFailedToMoveLead":{"type":"object","properties":{"error":{"type":"string","example":"failed to move lead"}}},"models.ErrorFailedToPauseCampaign":{"type":"object","properties":{"error":{"type":"string","example":"failed to pause campaign"}}},"models.ErrorFailedToRegisterWebhook":{"type":"object","properties":{"error":{"type":"string","example":"failed to register webhook"}}},"models.ErrorFailedToReplaceSequence":{"type":"object","properties":{"error":{"type":"string","example":"failed to replace campaign sequence"}}},"models.ErrorFailedToRetrieveLeads":{"type":"object","properties":{"error":{"type":"string","example":"failed to retrieve leads"}}},"models.ErrorFailedToRetrieveSequence":{"type":"object","properties":{"error":{"type":"string","example":"failed to retrieve campaign sequence"}}},"models.ErrorFailedToUpdateCampaign":{"type":"object","properties":{"error":{"type":"string","example":"failed to update campaign"}}},"models.ErrorFailedToUpdateLead":{"type":"object","properties":{"error":{"type":"string","example":"failed to update lead"}}},"models.ErrorFailedToUpdateSenderEmail":{"type":"object","properties":{"error":{"type":"string","example":"failed to update sender email"}}},"models.ErrorFailedToUpdateWarmupSettings":{"type":"object","properties":{"error":{"type":"string","example":"failed to update warm-up settings"}}},"models.ErrorFailedToUpdateWebhook":{"type":"object","properties":{"error":{"type":"string","example":"failed to update webhook"}}},"models.ErrorForbidden":{"type":"object","properties":{"error":{"type":"string","example":"forbidden"}}},"models.ErrorInvalidCampaignID":{"type":"object","properties":{"error":{"type":"string","example":"invalid campaign ID"}}},"models.ErrorInvalidCategory":{"type":"object","properties":{"error":{"type":"string","example":"invalid category, must be one of: interested, not_interested, bounced, out_of_office, delivery_incomplete, meeting_booked"}}},"models.ErrorInvalidLeadID":{"type":"object","properties":{"error":{"type":"string","example":"invalid lead ID"}}},"models.ErrorInvalidRequest":{"type":"object","properties":{"error":{"type":"string","example":"invalid request body"}}},"models.ErrorInvalidSenderEmailAddress":{"type":"object","properties":{"error":{"type":"string","example":"invalid email address. You can only connect business email accounts (name@company.com)"}}},"models.ErrorInvalidSenderEmailID":{"type":"object","properties":{"error":{"type":"string","example":"invalid sender email ID"}}},"models.ErrorInvalidSequence":{"type":"object","properties":{"error":{"type":"string","example":"the first step must have a delayDays of 0"}}},"models.ErrorInvalidWarmupSettings":{"type":"object","properties":{"error":{"type":"string","example":"startLimit (50) cannot be higher than capLimit (40)"}}},"models.ErrorInvalidWebhookID":{"type":"object","properties":{"error":{"type":"string","example":"invalid webhook ID"}}},"models.ErrorInvalidWebhookType":{"type":"object","properties":{"error":{"type":"string","example":"invalid webhook type. Valid values: EmailSent, EmailReply, EmailBounce, EmailUnsubscribe, LeadCategoryUpdate, LeadCreated, CampaignStatusChanged, CreditBalanceLow, AutopilotRunStatusChanged, DfyOrderCompleted"}}},"models.ErrorNotFound":{"type":"object","properties":{"error":{"type":"string","example":"lead not found"}}},"models.ErrorResponse":{"type":"object","properties":{"error":{"type":"string","example":"error message"}}},"models.ErrorSenderEmailAlreadyConnected":{"type":"object","properties":{"error":{"type":"string","example":"this email address is already connected to another workspace"}}},"models.ErrorSenderEmailNoDomain":{"type":"object","properties":{"error":{"type":"string","example":"sender email has no valid domain"}}},"models.ErrorSenderEmailNotConnected":{"type":"object","properties":{"error":{"type":"string","example":"sender emails not connected: 42"}}},"models.ErrorSenderEmailNotFound":{"type":"object","properties":{"error":{"type":"string","example":"sender email not found"}}},"models.ErrorSenderEmailPlanLimit":{"type":"object","properties":{"error":{"type":"string","example":"your plan does not allow connecting another sender email"}}},"models.ErrorUnauthorized":{"type":"object","properties":{"error":{"type":"string","example":"unauthorized"}}},"models.ErrorUnsupportedCampaignFlow":{"type":"object","properties":{"error":{"type":"string","example":"campaign flow does not support sequences. Valid flows: multiple_leads_scheduled, api"}}},"models.ErrorWarmupSettingsConflict":{"type":"object","properties":{"error":{"type":"string","example":"warm-up is off for this mailbox. Send enabled: true in the same request to switch it on with these settings"}}},"models.ErrorWebhookNotFound":{"type":"object","properties":{"error":{"type":"string","example":"webhook not found"}}},"models.GetCampaignResponse":{"type":"object","properties":{"createdAt":{"type":"string","example":"2024-01-10T10:30:00Z"},"emails":{"$ref":"#/definitions/models.CampaignEmailCounts"},"emoji":{"type":"string","example":"🚀"},"flow":{"type":"string","example":"multiple_leads_scheduled"},"id":{"type":"integer","example":123},"launchAt":{"description":"\"0001-01-01T00:00:00Z\" until the campaign is first launched.","type":"string","example":"2024-01-15T09:00:00Z"},"minimumHealth":{"description":"MinimumHealth is what the campaign's minimum health score is doing: each\nconnected account's score, which accounts it holds back and why. Null\nonly when the scores could not be read this time.","allOf":[{"$ref":"#/definitions/models.CampaignMinimumHealth"}]},"name":{"type":"string","example":"Q1 Outreach Campaign"},"schedule":{"$ref":"#/definitions/models.CampaignSchedule"},"sendAt":{"description":"\"0001-01-01T00:00:00Z\" while not set.","type":"string","example":"2024-01-15T10:00:00Z"},"settings":{"$ref":"#/definitions/models.CampaignSettings"},"stats":{"$ref":"#/definitions/models.CampaignDetailStats"},"status":{"type":"string","example":"running"},"timezone":{"type":"string","example":"America/New_York"},"updatedAt":{"type":"string","example":"2024-01-15T10:30:00Z"}}},"models.GetCampaignStatsResponse":{"type":"object","properties":{"campaignId":{"type":"integer","example":123},"daily":{"type":"array","items":{"$ref":"#/definitions/models.CampaignStatsDay"}},"steps":{"type":"array","items":{"$ref":"#/definitions/models.CampaignStatsStep"}},"totals":{"$ref":"#/definitions/models.CampaignStatsTotals"},"variants":{"type":"array","items":{"$ref":"#/definitions/models.CampaignStatsVariant"}}}},"models.GetLeadConversationResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/definitions/models.ConversationItem"}},"leadId":{"type":"integer","example":123},"total":{"type":"integer","example":5}}},"models.GetLeadResponse":{"type":"object","properties":{"company":{"type":"string","example":"Acme Corp"},"createdAt":{"type":"string","example":"2024-01-15T10:30:00Z"},"customVariables":{"description":"CustomVariables are the lead's merge tags beyond the named fields. Always\npresent; null or empty when the lead has none.","type":"object","additionalProperties":true},"email":{"type":"string","example":"john.doe@example.com"},"firstName":{"type":"string","example":"John"},"id":{"type":"integer","example":123},"lastName":{"type":"string","example":"Doe"},"linkedin":{"type":"string","example":"https://linkedin.com/in/johndoe"},"meetingBookedAt":{"description":"MeetingBookedAt is when a meeting was explicitly marked as booked with\nthis lead (see POST /leads/{id}/meeting) and null while no meeting is\nmarked.","type":"string","example":"2026-07-30T14:05:00Z"},"phone":{"type":"string","example":"+1234567890"},"tag":{"description":"Tag is the lead's engagement category (interested, not_interested,\nbounced, out_of_office, delivery_incomplete, meeting_booked) and null\nwhile the lead has never been categorized.","type":"string","example":"interested"},"title":{"type":"string","example":"Software Engineer"},"updatedAt":{"type":"string","example":"2024-01-15T10:30:00Z"},"website":{"type":"string","example":"https://example.com"}}},"models.GetSequenceResponse":{"type":"object","properties":{"campaignId":{"type":"integer","example":8589949820},"signature":{"type":"string","example":"<p>Robby Frank</p>"},"steps":{"type":"array","items":{"$ref":"#/definitions/models.SequenceStep"}},"total":{"type":"integer","example":3}}},"models.ICPPersona":{"type":"object","properties":{"seniority":{"type":"string","example":"VP"},"title":{"type":"string","example":"VP of Sales"}}},"models.ICPResponse":{"type":"object","properties":{"audienceSizedAt":{"description":"AudienceSizedAt is when the audience size was last refreshed (RFC3339),\nor null when it has never been sized.","type":"string","example":"2026-07-01T10:30:00Z"},"companySizes":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string","example":"2026-07-01T10:30:00Z"},"estimatedAudienceSize":{"type":"integer","example":12345},"id":{"type":"integer","example":12},"industries":{"type":"array","items":{"type":"string"}},"isGenerated":{"description":"IsGenerated is true while the profile is exactly what the AI proposed;\nany human edit clears it.","type":"boolean"},"isPrimary":{"description":"IsPrimary marks the workspace's active profile. At most one per space.","type":"boolean"},"keywords":{"type":"array","items":{"type":"string"}},"locations":{"type":"array","items":{"type":"string"}},"name":{"type":"string","example":"Mid-market SaaS RevOps leaders"},"personas":{"type":"array","items":{"$ref":"#/definitions/models.ICPPersona"}},"seniorities":{"type":"array","items":{"type":"string"}},"sourceWebsite":{"type":"string","example":"https://acme.com"},"summary":{"type":"string"},"titles":{"type":"array","items":{"type":"string"}}}},"models.ICPResult":{"type":"object","properties":{"icp":{"$ref":"#/definitions/models.ICPResponse"}}},"models.InboxPlacementDailyStatResponse":{"type":"object","properties":{"date":{"description":"Date is the UTC day.","type":"string"},"inbox":{"type":"integer","example":180},"inboxRate":{"type":"integer","example":81},"missing":{"type":"integer","example":4},"promotions":{"type":"integer","example":12},"runs":{"type":"integer","example":2},"spam":{"type":"integer","example":24}}},"models.InboxPlacementFindingResponse":{"type":"object","properties":{"action":{"description":"Action is what to do about it. Every finding has one: a report that only\nsays what is wrong makes the reader's day worse without improving their\ndelivery.","type":"string","example":"Publish a DMARC record. Start with p=none to observe, then move to quarantine."},"detail":{"type":"string","example":"acme.com publishes no DMARC record."},"senderAddress":{"type":"string","example":"tom@acme.com"},"senderEmailId":{"type":"integer","example":3},"severity":{"description":"Severity is critical, warning or info.","type":"string","example":"critical"},"title":{"type":"string","example":"DMARC record missing"}}},"models.InboxPlacementForbiddenResponse":{"type":"object","properties":{"code":{"description":"Code is inbox_placement_required or inbox_placement_hypergrowth_required.","type":"string","example":"inbox_placement_required"},"error":{"description":"Error is written for a person to read and can be shown as-is.","type":"string","example":"Inbox Placement is a paid add-on. Add it in Settings to test where your emails land."},"tier":{"description":"Tier is the tier the workspace currently holds: none, growth or\nhypergrowth.","type":"string","example":"none"}}},"models.InboxPlacementInsightsResponse":{"type":"object","properties":{"findings":{"type":"array","items":{"$ref":"#/definitions/models.InboxPlacementFindingResponse"}},"inboxRate":{"type":"integer","example":80},"mailboxesAtRisk":{"type":"integer","example":2},"mailboxesChecked":{"type":"integer","example":9},"missingRate":{"type":"integer","example":3},"providerBreakdown":{"type":"array","items":{"$ref":"#/definitions/models.InboxPlacementProviderResponse"}},"runsInWindow":{"type":"integer","example":14},"spamRate":{"type":"integer","example":12},"windowDays":{"description":"WindowDays is how far back the rates below were computed over.","type":"integer","example":30}}},"models.InboxPlacementListResponse":{"type":"object","properties":{"count":{"type":"integer","example":14},"items":{"type":"array","items":{"$ref":"#/definitions/models.InboxPlacementRunResponse"}}}},"models.InboxPlacementProviderResponse":{"type":"object","properties":{"inbox":{"type":"integer","example":14},"inboxRate":{"type":"integer","example":77},"label":{"type":"string","example":"Gmail / Google Workspace"},"missing":{"type":"integer","example":0},"promotions":{"type":"integer","example":2},"provider":{"type":"string","example":"google"},"seeds":{"type":"integer","example":18},"spam":{"type":"integer","example":2}}},"models.InboxPlacementResultListResponse":{"type":"object","properties":{"count":{"type":"integer","example":120},"items":{"type":"array","items":{"$ref":"#/definitions/models.InboxPlacementResultResponse"}},"runId":{"type":"integer","example":481}}},"models.InboxPlacementResultResponse":{"type":"object","properties":{"deliverySeconds":{"description":"DeliverySeconds is how long the probe took to appear. -1 when never\ndetected. A slow delivery is itself a signal: greylisting and throttling\nboth show up here before they show up in a placement number.","type":"integer","example":46},"detectedAt":{"type":"string"},"error":{"type":"string"},"id":{"type":"integer","example":90211},"placement":{"description":"Placement is pending, inbox, spam, promotions, social, updates, missing\nor failed.","type":"string","example":"inbox"},"seedAddress":{"type":"string","example":"seed-104@example.net"},"seedProvider":{"type":"string","example":"google"},"senderAddress":{"type":"string","example":"tom@acme.com"},"sentAt":{"type":"string"}}},"models.InboxPlacementRunDetailResponse":{"type":"object","properties":{"completedAt":{"type":"string"},"createdAt":{"type":"string"},"error":{"type":"string"},"failed":{"type":"integer","example":0},"id":{"type":"integer","example":481},"inbox":{"type":"integer","example":96},"inboxRate":{"description":"InboxRate is the whole-percentage share of SCORED probes that reached the\nprimary inbox. -1 when nothing has been scored.","type":"integer","example":80},"messagesExpected":{"type":"integer","example":120},"messagesSent":{"type":"integer","example":120},"missing":{"description":"Missing counts probes never found in any folder, which usually means a\nsilent block. Reported apart from spam because it is a worse problem with\na different fix.","type":"integer","example":4},"promotions":{"description":"Promotions counts every Gmail category tab: promotions, social and\nupdates. Delivered, but filtered away from the reader.","type":"integer","example":8},"providerBreakdown":{"type":"array","items":{"$ref":"#/definitions/models.InboxPlacementProviderResponse"}},"seedsTargeted":{"type":"integer","example":40},"senderBreakdown":{"type":"array","items":{"$ref":"#/definitions/models.InboxPlacementSenderResponse"}},"sendersTargeted":{"type":"integer","example":3},"spam":{"type":"integer","example":12},"spamScore":{"description":"SpamScore is the content spam score in tenths of a point, so 47 is 4.7.\n-1 when not scored.","type":"integer","example":12},"spamScoreRules":{"type":"array","items":{"$ref":"#/definitions/models.InboxPlacementSpamRuleResponse"}},"startedAt":{"type":"string"},"status":{"description":"Status is pending, sending, collecting, completed, failed or cancelled.\nCounters are only final once status is completed.","type":"string","example":"completed"},"testId":{"type":"integer","example":12},"trigger":{"description":"Trigger is manual or scheduled.","type":"string","example":"scheduled"}}},"models.InboxPlacementRunResponse":{"type":"object","properties":{"completedAt":{"type":"string"},"createdAt":{"type":"string"},"error":{"type":"string"},"failed":{"type":"integer","example":0},"id":{"type":"integer","example":481},"inbox":{"type":"integer","example":96},"inboxRate":{"description":"InboxRate is the whole-percentage share of SCORED probes that reached the\nprimary inbox. -1 when nothing has been scored.","type":"integer","example":80},"messagesExpected":{"type":"integer","example":120},"messagesSent":{"type":"integer","example":120},"missing":{"description":"Missing counts probes never found in any folder, which usually means a\nsilent block. Reported apart from spam because it is a worse problem with\na different fix.","type":"integer","example":4},"promotions":{"description":"Promotions counts every Gmail category tab: promotions, social and\nupdates. Delivered, but filtered away from the reader.","type":"integer","example":8},"seedsTargeted":{"type":"integer","example":40},"sendersTargeted":{"type":"integer","example":3},"spam":{"type":"integer","example":12},"spamScore":{"description":"SpamScore is the content spam score in tenths of a point, so 47 is 4.7.\n-1 when not scored.","type":"integer","example":12},"startedAt":{"type":"string"},"status":{"description":"Status is pending, sending, collecting, completed, failed or cancelled.\nCounters are only final once status is completed.","type":"string","example":"completed"},"testId":{"type":"integer","example":12},"trigger":{"description":"Trigger is manual or scheduled.","type":"string","example":"scheduled"}}},"models.InboxPlacementSenderResponse":{"type":"object","properties":{"failed":{"type":"integer","example":0},"inbox":{"type":"integer","example":31},"inboxRate":{"type":"integer","example":77},"missing":{"type":"integer","example":1},"promotions":{"type":"integer","example":2},"senderAddress":{"type":"string","example":"tom@acme.com"},"senderEmailId":{"type":"integer","example":3},"sent":{"type":"integer","example":40},"spam":{"type":"integer","example":6}}},"models.InboxPlacementSpamRuleResponse":{"type":"object","properties":{"advice":{"type":"string","example":"Write the subject in sentence case."},"description":{"type":"string","example":"The subject line is in capitals."},"name":{"type":"string","example":"SUBJECT_ALL_CAPS"},"points":{"description":"Points is the rule's cost in tenths of a point, so 15 is 1.5 points.","type":"integer","example":15}}},"models.InboxPlacementStatsByDateResponse":{"type":"object","properties":{"days":{"type":"array","items":{"$ref":"#/definitions/models.InboxPlacementDailyStatResponse"}},"from":{"type":"string"},"to":{"type":"string"}}},"models.InboxPlacementTestListResponse":{"type":"object","properties":{"count":{"type":"integer","example":3},"items":{"type":"array","items":{"$ref":"#/definitions/models.InboxPlacementTestResponse"}}}},"models.InboxPlacementTestResponse":{"type":"object","properties":{"createdAt":{"type":"string"},"id":{"type":"integer","example":12},"intervalHours":{"description":"IntervalHours is how often a recurring test repeats. Null for one-time.","type":"integer","example":24},"isPaused":{"type":"boolean","example":false},"kind":{"description":"Kind is one_time or recurring.","type":"string","example":"recurring"},"lastRunAt":{"type":"string"},"name":{"type":"string","example":"Q3 outbound - main sequence"},"nextRunAt":{"type":"string"},"seedProviders":{"description":"SeedProviders restricts the seed panel. Empty means every provider.","type":"array","items":{"type":"string"},"example":["google","microsoft"]},"senderEmailIds":{"description":"SenderEmailIDs are the mailboxes the test sends from.","type":"array","items":{"type":"integer"},"example":[1,2,3]},"subject":{"type":"string","example":"Question about your onboarding flow"}}},"models.InstantlyAccount":{"type":"object","properties":{"dailyLimit":{"type":"integer","example":30},"email":{"type":"string","example":"jane@acme.com"},"firstName":{"type":"string","example":"Jane"},"lastName":{"type":"string","example":"Doe"},"provider":{"description":"google | microsoft | other","type":"string","example":"google"},"status":{"type":"integer","example":1}}},"models.InstantlyAccountsRequest":{"type":"object","required":["apiKey"],"properties":{"apiKey":{"description":"APIKey is the caller's Instantly.ai API key.","type":"string","example":"insta_xxx"}}},"models.InstantlyAccountsResponse":{"type":"object","properties":{"accounts":{"type":"array","items":{"$ref":"#/definitions/models.InstantlyAccount"}},"imapCsvTemplate":{"description":"ImapCsvTemplate is a ready-to-fill CSV (one row per account, password\ncolumn empty) for the bulk IMAP/SMTP connection flow.","type":"string"}}},"models.InstantlyImportRequest":{"type":"object","required":["apiKey"],"properties":{"apiKey":{"description":"APIKey is the caller's Instantly.ai API key. It is validated with a\nlightweight call to Instantly before the import job is enqueued.","type":"string","example":"insta_xxx"}}},"models.InstantlyImportResponse":{"type":"object","properties":{"jobId":{"type":"string","example":"9f8b1c2a-1234-4a5b-9c8d-0e1f2a3b4c5d"},"message":{"type":"string","example":"instantly import started"},"status":{"type":"string","example":"queued"}}},"models.InsufficientCreditsErrorResponse":{"type":"object","properties":{"code":{"type":"string","example":"insufficient_credits"},"error":{"type":"string","example":"starting this run needs 50 prospect credits and the workspace has 0; buy credits via POST /credits/purchase, then start the run again"},"have":{"type":"integer","example":0},"need":{"type":"integer","example":50}}},"models.KillAutopilotRunRequest":{"type":"object","properties":{"reason":{"description":"Reason is recorded in the run's audit trail. Trimmed and capped at 200\ncharacters.","type":"string","example":"customer requested stop"}}},"models.LaunchCampaignResponse":{"type":"object","properties":{"campaign":{"type":"object","properties":{"id":{"type":"integer","example":123},"name":{"type":"string","example":"Q1 Outreach Campaign"},"status":{"type":"string","example":"running"}}},"message":{"type":"string","example":"campaign launched/resumed successfully"}}},"models.LeadCategoryState":{"type":"object","properties":{"id":{"type":"integer","example":123},"meetingBookedAt":{"description":"MeetingBookedAt is when the meeting was marked as booked and null while\nno meeting is marked.","type":"string","example":"2026-07-30T14:05:00Z"},"tag":{"description":"Tag is the lead's engagement category and null while the lead has never\nbeen categorized.","type":"string","example":"meeting_booked"}}},"models.LeadFinderError":{"type":"object","properties":{"available":{"type":"integer","example":10},"cap":{"type":"integer"},"count":{"type":"integer"},"error":{"description":"Error is the machine code: invalid_filters or invalid_request (400),\ninsufficient_credits (402), plan_required (403), not_found or\ncampaign_not_found (404), provider_unavailable or import_running (409),\nresults_expired (410), contact_cap_reached (422), rate_limited or\nbudget_exhausted (429), internal (500).","type":"string","example":"invalid_filters"},"field":{"type":"string","example":"seniorities"},"importId":{"description":"ImportID is the running import an import_running refusal waits on.","type":"integer","example":8813},"message":{"type":"string"},"needed":{"type":"integer","example":25},"reason":{"description":"Reason details the code. invalid_filters: unknown_value,\ntoo_many_values, value_too_long, invalid_domain, mixed_geo_levels,\nlocation_needs_country, no_filters, unknown_field or malformed. 429:\nsearches_per_minute, empty_searches, daily_preview_limit,\nrunning_imports, daily_import_limit, daily_budget, monthly_budget or\nfair_use_floor.","type":"string","example":"unknown_value"},"retryAfterSeconds":{"type":"integer","example":30},"value":{"type":"string","example":"Boss"}}},"models.LeadFinderFilterSet":{"type":"object","properties":{"cities":{"type":"array","items":{"type":"string"},"example":["Dublin"]},"companyKeywords":{"description":"CompanyKeywords is one phrase matched against what the employer does.","type":"string"},"companyName":{"description":"CompanyName is one phrase matched against the employer's name.","type":"string"},"companySizes":{"description":"CompanySizes are headcount bands from GET /lead-finder/filters.","type":"array","items":{"type":"string"},"example":["51 to 200"]},"continents":{"type":"array","items":{"type":"string"}},"countries":{"description":"Countries, Regions and Continents are values from GET /lead-finder/filters,\nwhere the person is. Use one of the three at a time.","type":"array","items":{"type":"string"},"example":["Ireland"]},"domains":{"description":"Domains and ExcludeDomains are company domains, up to 1,000 each.","type":"array","items":{"type":"string"},"example":["acme.com"]},"excludeCountries":{"type":"array","items":{"type":"string"}},"excludeDomains":{"type":"array","items":{"type":"string"}},"excludeHeadquartersCountries":{"type":"array","items":{"type":"string"}},"excludeIndustries":{"type":"array","items":{"type":"string"}},"excludeJobTitles":{"type":"array","items":{"type":"string"},"example":["Intern"]},"headquartersCountries":{"description":"HeadquartersCountries are where the employer is headquartered.","type":"array","items":{"type":"string"}},"industries":{"description":"Industries are values from GET /lead-finder/filters.","type":"array","items":{"type":"string"},"example":["Software Development"]},"jobFunctions":{"description":"JobFunctions are values from GET /lead-finder/filters.","type":"array","items":{"type":"string"},"example":["Sales & Business Development"]},"jobTitles":{"description":"JobTitles are free text, matched as OR terms.","type":"array","items":{"type":"string"},"example":["Head of Sales","VP Sales"]},"regions":{"type":"array","items":{"type":"string"}},"revenue":{"description":"Revenue is revenue bands from GET /lead-finder/filters.","type":"array","items":{"type":"string"}},"seniorities":{"description":"Seniorities are values from GET /lead-finder/filters.","type":"array","items":{"type":"string"},"example":["Director"]},"states":{"description":"States and Cities are free text, matched inside the person's location.","type":"array","items":{"type":"string"}},"technologies":{"description":"Technologies are free text: tools the employer uses.","type":"array","items":{"type":"string"},"example":["HubSpot"]}}},"models.LeadFinderFilters":{"type":"object","properties":{"companySizes":{"type":"array","items":{"type":"string"}},"continents":{"type":"array","items":{"type":"string"}},"countries":{"type":"array","items":{"type":"string"}},"creditsPerProspect":{"description":"CreditsPerProspect is what adding one person to a campaign costs, read\nfrom the pricing table at request time.","type":"integer","example":1},"headquartersCountries":{"type":"array","items":{"type":"string"}},"industries":{"type":"array","items":{"type":"string"}},"jobFunctions":{"type":"array","items":{"type":"string"}},"limits":{"$ref":"#/definitions/models.LeadFinderLimits"},"regions":{"type":"array","items":{"type":"string"}},"revenue":{"type":"array","items":{"type":"string"}},"seniorities":{"type":"array","items":{"type":"string"}}}},"models.LeadFinderImport":{"type":"object","properties":{"added":{"type":"integer","example":22},"audienceKey":{"description":"AudienceKey names the filters the people came from, as on a search.","type":"string"},"campaignId":{"type":"integer","example":123},"campaignName":{"type":"string","example":"Irish sales leaders"},"createdAt":{"type":"string"},"creditsPerProspect":{"type":"integer","example":1},"creditsSpent":{"description":"CreditsSpent is what the import has charged so far. People added while\nthe workspace's credits were free add nothing to it.","type":"integer","example":22},"endReason":{"description":"EndReason says how an import ended: all_selected or requested_reached\n(finished), audience_exhausted or fetch_limit (a first_n import stopped\nearly), or canceled. A failed import has none; see LastError.","type":"string"},"expired":{"type":"integer","example":0},"finishedAt":{"type":"string"},"importId":{"type":"integer","example":8812},"lastError":{"description":"LastError says why a failed import stopped: insufficient_credits,\ncontact_cap_reached, results_expired, budget_exhausted,\nprovider_blocked, provider_unavailable (the people database was down or\nin maintenance; start the import again later), rate_limited,\ninvalid_filters, campaign_not_found or internal. A failed import can\nstill have added and charged people.","type":"string"},"mode":{"type":"string","example":"selected"},"requested":{"type":"integer","example":25},"skipReasons":{"description":"SkipReasons counts skipped people by reason: already_in_workspace,\nblocklisted, reveal_returned_no_address, consumer_mailbox,\ninvalid_email or contact_cap_reached.","type":"object","additionalProperties":{"type":"integer"}},"skipped":{"type":"integer","example":3},"startsAt":{"description":"StartsAt is the position in the results a first_n import started from.","type":"integer","example":201},"status":{"description":"Status is running, completed, failed or canceled. Added, CreditsSpent\nand Status can still change until FinishedAt is set.","type":"string","example":"completed"},"topUpOf":{"description":"TopUpOf is set on an import a saved search's weekly top-up started (set\nup in the app): the saved search's id.","type":"integer"},"verification":{"$ref":"#/definitions/models.LeadFinderVerification"},"verifyEmails":{"type":"boolean","example":true}}},"models.LeadFinderImportCreated":{"type":"object","properties":{"audienceKey":{"description":"AudienceKey names the import's filters, as on a search.","type":"string"},"available":{"description":"Available is the wallet's spendable balance when the import started.\nWhile the workspace's credits are free (unlimited on GET\n/credits/balance) it is the workspace's own balance, which the import\ndoes not use.","type":"integer","example":4975},"creditsPerProspect":{"type":"integer","example":1},"estimatedCredits":{"description":"EstimatedCredits is Requested x CreditsPerProspect, the most this\nimport can cost in credits. Skipped people cost no credits; metered\nverification is billed separately.","type":"integer","example":25},"expired":{"description":"Expired counts refs that were no longer stored and were dropped.","type":"integer","example":0},"importId":{"type":"integer","example":8812},"requested":{"description":"Requested is how many people the import will try to add.","type":"integer","example":25},"startsAt":{"description":"StartsAt is the position in the results a first_n import starts from:\n1 is the first person. Left out for a selected import.","type":"integer","example":201},"status":{"type":"string","example":"running"}}},"models.LeadFinderImportRequest":{"type":"object","properties":{"campaignId":{"description":"CampaignID is the campaign the people are added to.","type":"integer","example":123},"count":{"description":"Count is how many people to add, for mode first_n: 1 to 5,000.","type":"integer","example":200},"filters":{"description":"Filters are the filters to add from, for mode first_n. At least one\ninclude filter is required; the rules are those of a search. For mode\nselected they are optional: the search the refs came from, so the\npeople count towards what those filters have added (progress on a\nsearch).","allOf":[{"$ref":"#/definitions/models.LeadFinderFilterSet"}]},"fromStart":{"description":"FromStart makes a first_n import start from the top of the results\ninstead of after the last first_n import of the same filters.","type":"boolean","example":false},"mode":{"description":"Mode is \"selected\" (add the people named in Refs) or \"first_n\" (add up\nto Count people matching Filters who are not in the workspace yet,\ncontinuing after the last first_n import of the same filters).","type":"string","enum":["selected","first_n"],"example":"selected"},"refs":{"description":"Refs are result refs from searches, for mode selected: 1 to 1,000.","type":"array","items":{"type":"string"},"example":["r_Q2x9LmVwZ3JhY2VIb3BwZX"]},"verifyEmails":{"description":"VerifyEmails checks each address with the paid verification waterfall\nbefore it is added: one or two metered checks per address, billed on the\nnext invoice (GET /email-verification/rates), including addresses that\nturn out invalid. Invalid addresses are skipped and cost no credits;\ncatch-all and unconfirmed ones are added and charged, and so is every\naddress once the monthly verification allowance is used up. Defaults to\ntrue.","type":"boolean","example":true}}},"models.LeadFinderImports":{"type":"object","properties":{"imports":{"type":"array","items":{"$ref":"#/definitions/models.LeadFinderImport"}}}},"models.LeadFinderLimits":{"type":"object","properties":{"defaultPageSize":{"type":"integer","example":25},"maxChipsPerField":{"type":"integer","example":50},"maxDomainsPerField":{"type":"integer","example":1000},"maxFirstN":{"type":"integer","example":5000},"maxPage":{"type":"integer","example":100},"maxRefsPerImport":{"type":"integer","example":1000},"maxRunningImports":{"type":"integer","example":3},"maxSavedSearchName":{"type":"integer","example":80},"maxSavedSearches":{"description":"MaxSavedSearches and MaxSavedSearchName bound the searches a workspace\nsaves in the app, and TopUpHourUTC is the hour of the day, in UTC, a\nsaved search's weekly top-up runs.","type":"integer","example":100},"maxValueLength":{"type":"integer","example":100},"pageSizes":{"type":"array","items":{"type":"integer"},"example":[25,50]},"rowsLeftToday":{"description":"RowsLeftToday is what is left of it this UTC day.","type":"integer","example":1950},"rowsPerDay":{"description":"RowsPerDay is the account's daily browsing allowance in result rows.","type":"integer","example":2000},"topUpHourUtc":{"type":"integer","example":14}}},"models.LeadFinderPerson":{"type":"object","properties":{"company":{"$ref":"#/definitions/models.LeadFinderPersonCompany"},"firstName":{"type":"string","example":"Grace"},"inWorkspace":{"description":"InWorkspace is true for someone Lead Finder added to this workspace\nwhose lead is still there, so adding them again is skipped for free. A\nperson who is a lead from another source shows false; adding them is\nstill skipped and costs nothing.","type":"boolean","example":false},"jobFunction":{"type":"string","example":"Sales & Business Development"},"lastInitial":{"type":"string","example":"H"},"location":{"$ref":"#/definitions/models.LeadFinderPersonLocation"},"ref":{"description":"Ref identifies the person for POST /lead-finder/imports. It stays\nvalid for 60 minutes after the page it came on was last shown.","type":"string","example":"r_Q2x9LmVwZ3JhY2VIb3BwZX"},"seniority":{"type":"string","example":"Director"},"title":{"type":"string","example":"Head of Sales"}}},"models.LeadFinderPersonCompany":{"type":"object","properties":{"industry":{"type":"string","example":"Software Development"},"logoUrl":{"description":"LogoURL is the company's icon, an image of at most 64 pixels served by\nthis API with no key needed. The URL does not contain the company's\ndomain. It is left out when the company has no website on record, and\nanswers 404 when the website has no icon.","type":"string","example":"https://api.emailchaser.com/lead-finder/logos/l_2mEuK4r1Xw0pVn8qFh7ZcT5bLd9sYoJ3aGk6"},"name":{"type":"string","example":"Hopper Ltd"},"revenueBand":{"type":"string"},"sizeBand":{"type":"string","example":"51 to 200"}}},"models.LeadFinderPersonLocation":{"type":"object","properties":{"city":{"type":"string","example":"Dublin"},"country":{"type":"string","example":"Ireland"},"state":{"type":"string"}}},"models.LeadFinderProgress":{"type":"object","properties":{"added":{"description":"Added is how many people they added.","type":"integer","example":1000},"adds":{"description":"Adds is how many imports there were, selected ones included.","type":"integer","example":2},"lastAddAt":{"description":"LastAddAt is when the latest import from these filters started.","type":"string"},"nextFrom":{"description":"NextFrom is the position in the results (1 is the first person) the next\nfirst_n import of these filters starts from.","type":"integer","example":901},"runningImportId":{"description":"RunningImportID is a first_n import of these filters still running.\nAnother first_n import of the same filters is refused (409\nimport_running) until it ends.","type":"integer","example":8813}}},"models.LeadFinderSearch":{"type":"object","properties":{"audienceKey":{"description":"AudienceKey names the filters: the same picks in any order give the\nsame key. Imports from these filters carry it too.","type":"string","example":"4f1c0e9a2b7d4c3e8f6a5b1d0c9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e"},"creditsPerProspect":{"type":"integer","example":1},"error":{"description":"Error is set on a failed search: rate_limited, provider_blocked,\nprovider_unavailable (the people database is down or in maintenance;\ntry again later), timeout, invalid_filters, budget_exhausted, expired\nor internal.","type":"string"},"hasMore":{"type":"boolean","example":true},"maxPage":{"type":"integer","example":100},"message":{"type":"string"},"page":{"type":"integer","example":1},"pageSize":{"type":"integer","example":25},"progress":{"description":"Progress is what earlier imports from these filters did in the\nworkspace. Left out before the first one.","allOf":[{"$ref":"#/definitions/models.LeadFinderProgress"}]},"results":{"description":"Results are the people on this page, masked.","type":"array","items":{"$ref":"#/definitions/models.LeadFinderPerson"}},"rowsLeftToday":{"description":"RowsLeftToday is the account's browsing allowance left this UTC day.","type":"integer","example":1975},"searchId":{"type":"string","example":"s_c32d55f8dbd501c71bea43ee"},"status":{"description":"Status is running, done or failed.","type":"string","example":"done"},"total":{"description":"Total is how many people match; TotalIsExact says whether it is a\ncount or a lower bound, and TotalStatus whether the count has landed\n(pending, done or failed).","type":"integer","example":2147},"totalIsExact":{"type":"boolean","example":true},"totalStatus":{"type":"string","example":"done"}}},"models.LeadFinderSearchRequest":{"type":"object","properties":{"filters":{"description":"Filters: at least one include filter is required. Enum fields take\nvalues from GET /lead-finder/filters. Free-text fields take up to 50\nvalues of up to 100 characters each, and a value holding commas is\nsplit into one term per comma. Use only one of countries, regions and\ncontinents; states and cities go with countries or on their own. A\nbroken rule is a 400 invalid_filters naming the field, value and reason.","allOf":[{"$ref":"#/definitions/models.LeadFinderFilterSet"}]},"page":{"description":"Page is 1-based, up to limits.maxPage. Defaults to 1.","type":"integer","example":1},"pageSize":{"description":"PageSize is 25 (the default) or 50.","type":"integer","example":25}}},"models.LeadFinderVerification":{"type":"object","properties":{"catchAll":{"type":"integer","example":2},"invalid":{"type":"integer","example":1},"limitReached":{"type":"integer","example":0},"unknown":{"type":"integer","example":0},"valid":{"type":"integer","example":20}}},"models.LeadInput":{"type":"object","required":["email"],"properties":{"company":{"type":"string"},"customVariables":{"description":"CustomVariables holds any attribute the eight named fields do not cover,\nfor example the page a visitor landed on. Each key becomes a merge tag\nusable in email copy as {key}. Keys are matched case-insensitively with\nspaces treated as underscores, so \"Page Visited\" and \"page_visited\" are\nthe same tag. Values are stored as given; non-string values are rendered\nwith their JSON representation at send time.","type":"object","additionalProperties":true},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"linkedin":{"type":"string"},"middleName":{"description":"Saved when the lead is created. Not changed when a lead with this email already exists.","type":"string"},"phone":{"type":"string"},"title":{"type":"string"},"website":{"type":"string"}}},"models.LeadMeetingResponse":{"type":"object","properties":{"lead":{"$ref":"#/definitions/models.LeadCategoryState"},"message":{"type":"string","example":"meeting marked as booked"}}},"models.LeadResponse":{"type":"object","properties":{"company":{"type":"string","example":"Acme Corp"},"createdAt":{"type":"string","example":"2024-01-15T10:30:00Z"},"customVariables":{"description":"CustomVariables are the lead's merge tags beyond the named fields.","type":"object","additionalProperties":true},"email":{"type":"string","example":"john.doe@example.com"},"firstName":{"type":"string","example":"John"},"id":{"type":"integer","example":123},"lastName":{"type":"string","example":"Doe"},"linkedin":{"type":"string","example":"https://linkedin.com/in/johndoe"},"meetingBookedAt":{"description":"MeetingBookedAt is when a meeting was explicitly marked as booked with\nthis lead (see POST /leads/{id}/meeting) and null while no meeting is\nmarked.","type":"string","example":"2026-07-30T14:05:00Z"},"middleName":{"type":"string","example":"A."},"phone":{"type":"string","example":"+1234567890"},"tag":{"description":"Tag is the lead's engagement category (interested, not_interested,\nbounced, out_of_office, delivery_incomplete, meeting_booked) and null\nwhile the lead has never been categorized.","type":"string","example":"interested"},"title":{"type":"string","example":"Software Engineer"},"updatedAt":{"type":"string","example":"2024-01-15T10:30:00Z"},"website":{"type":"string","example":"https://example.com"}}},"models.ListAutopilotRunsResponse":{"type":"object","properties":{"runs":{"type":"array","items":{"$ref":"#/definitions/models.AutopilotRunResponse"}},"total":{"type":"integer","example":2}}},"models.ListCampaignsResponse":{"type":"object","properties":{"campaigns":{"type":"array","items":{"$ref":"#/definitions/models.CampaignListItem"}},"hasMore":{"type":"boolean"},"limit":{"type":"integer"},"page":{"type":"integer"},"total":{"type":"integer"}}},"models.ListCreditTransactionsResponse":{"type":"object","properties":{"total":{"type":"integer","example":12},"transactions":{"type":"array","items":{"$ref":"#/definitions/models.CreditTransactionItem"}}}},"models.ListDfyOrdersResponse":{"type":"object","properties":{"orders":{"type":"array","items":{"$ref":"#/definitions/models.DfyOrderResponse"}},"total":{"type":"integer","example":3}}},"models.ListEmailVerificationJobsResponse":{"type":"object","properties":{"count":{"type":"integer","example":3},"next":{"type":"boolean","example":false},"results":{"type":"array","items":{"$ref":"#/definitions/models.EmailVerificationJobResponse"}}}},"models.ListEmailVerificationRecordsResponse":{"type":"object","properties":{"count":{"type":"integer","example":2500},"next":{"type":"boolean","example":true},"results":{"type":"array","items":{"$ref":"#/definitions/models.EmailVerificationRecordResponse"}}}},"models.ListICPsResponse":{"type":"object","properties":{"icps":{"type":"array","items":{"$ref":"#/definitions/models.ICPResponse"}},"total":{"type":"integer","example":3}}},"models.ListLeadsResponse":{"type":"object","properties":{"hasMore":{"type":"boolean","example":true},"leads":{"type":"array","items":{"$ref":"#/definitions/models.LeadResponse"}},"limit":{"type":"integer","example":20},"page":{"type":"integer","example":1},"total":{"type":"integer","example":42}}},"models.ListRepliesResponse":{"type":"object","properties":{"hasMore":{"type":"boolean","example":true},"limit":{"type":"integer","example":20},"page":{"type":"integer","example":1},"replies":{"type":"array","items":{"$ref":"#/definitions/models.ReplyItem"}},"total":{"type":"integer","example":42}}},"models.ListReplyDraftsResponse":{"type":"object","properties":{"drafts":{"type":"array","items":{"$ref":"#/definitions/models.ReplyDraftItem"}},"hasMore":{"type":"boolean","example":false},"limit":{"type":"integer","example":20},"page":{"type":"integer","example":1},"total":{"type":"integer","example":3}}},"models.ListSenderEmailsResponse":{"type":"object","properties":{"hasMore":{"type":"boolean"},"limit":{"type":"integer"},"page":{"type":"integer"},"senderEmails":{"type":"array","items":{"$ref":"#/definitions/models.SenderEmailListItem"}},"total":{"type":"integer"}}},"models.ListWebhooksResponse":{"type":"object","properties":{"total":{"type":"integer","example":2},"webhooks":{"type":"array","items":{"$ref":"#/definitions/models.WebhookResponse"}}}},"models.ListWorkspacesResponse":{"type":"object","properties":{"total":{"type":"integer","example":3},"workspaces":{"type":"array","items":{"$ref":"#/definitions/models.WorkspaceItem"}}}},"models.MemberInfo":{"type":"object","properties":{"created_at":{"type":"string"},"email_address":{"type":"string"},"name":{"type":"string"}}},"models.MoveLeadRequest":{"type":"object","required":["targetCampaignId"],"properties":{"targetCampaignId":{"type":"integer"}}},"models.MoveLeadResponse":{"type":"object","properties":{"leadId":{"type":"integer","example":123},"message":{"type":"string","example":"lead moved successfully"},"sourceCampaignId":{"type":"integer","example":1},"targetCampaignId":{"type":"integer","example":2}}},"models.MxCheckResult":{"type":"object","properties":{"hosts":{"description":"Hosts are the domain's mail servers as \"preference host\", lowest\npreference (highest priority) first.","type":"array","items":{"type":"string"},"example":["1 aspmx.l.google.com"]},"issues":{"type":"array","items":{"type":"string"}},"status":{"description":"Status is one of OK, WARNING, MISSING, ERROR.","type":"string","example":"OK"}}},"models.OutcomesReportCreditReason":{"type":"object","properties":{"credits":{"type":"integer","example":120},"reason":{"type":"string","example":"prospect_reveal"}}},"models.OutcomesReportOutcomes":{"type":"object","properties":{"meetings":{"description":"Meetings counts leads marked as having booked a meeting in the window.","type":"integer","example":2},"positiveReplies":{"description":"PositiveReplies counts leads whose reply was categorized as interested.","type":"integer","example":5},"replies":{"type":"integer","example":12},"sent":{"type":"integer","example":400}}},"models.OutcomesReportResponse":{"type":"object","properties":{"campaignId":{"description":"CampaignID echoes the campaign filter when one was given. Only the\noutcome side is narrowed by it; spend stays workspace-level.","type":"integer","example":12},"costPerMeeting":{"description":"CostPerMeeting is total spend divided by meetings, null when there are\nnone.","type":"number","example":11.48},"costPerPositive":{"description":"CostPerPositive is total spend divided by positive replies, null when\nthere are none.","type":"number","example":4.59},"costPerReply":{"description":"CostPerReply is total spend divided by replies, null when there are none.","type":"number","example":1.91},"outcomes":{"$ref":"#/definitions/models.OutcomesReportOutcomes"},"since":{"description":"Since echoes the window start, or null when the window is open-ended.","type":"string","example":"2026-07-01T00:00:00Z"},"spend":{"$ref":"#/definitions/models.OutcomesReportSpend"},"until":{"description":"Until echoes the window end, or null when the window is open-ended.","type":"string","example":"2026-07-31T00:00:00Z"}}},"models.OutcomesReportSpend":{"type":"object","properties":{"credits":{"description":"Credits is the total of settled credit debits in the window. In-flight\nreservations are excluded until they settle.","type":"integer","example":120},"creditsByReason":{"type":"array","items":{"$ref":"#/definitions/models.OutcomesReportCreditReason"}},"creditsUsd":{"description":"CreditsUsd values the spent credits at the list price per credit.","type":"number","example":3.96},"dfyOrders":{"description":"DfyOrders is how many done-for-you orders were placed in the window\n(failed and canceled orders are excluded).","type":"integer","example":1},"dfyOrdersUsd":{"type":"number","example":18.99},"totalUsd":{"type":"number","example":22.95}}},"models.PauseCampaignResponse":{"type":"object","properties":{"campaign":{"type":"object","properties":{"id":{"type":"integer","example":123},"name":{"type":"string","example":"Q1 Outreach Campaign"},"status":{"type":"string","example":"paused"}}},"message":{"type":"string","example":"campaign paused successfully"}}},"models.PurchaseCreditsErrorResponse":{"type":"object","properties":{"code":{"description":"Code is machine-readable:\n  - invalid_request: malformed body or credits outside 1000..10000; fix the request. No charge was made.\n  - billing_required: the workspace has no active subscription with a saved default card; fix billing in the Emailchaser app, then retry. No charge was made.\n  - payment_failed: the charge was attempted and refused (declined, expired, insufficient funds); fix the card, then retry. No money moved.\n  - credits_free: this workspace's credits are free, so there is nothing to buy. No charge was made.\n  - temporarily_unavailable: the purchase stopped before any charge because a check could not be read. No charge was made; retry shortly.\n  - purchase_incomplete: the card WAS charged but crediting failed; support is already notified. Do NOT retry - a retry charges again.\n  - purchase_unconfirmed: the outcome is unknown, or the purchase completed but the new balance could not be read; check /credits/transactions for a stripe_topup entry before retrying.","type":"string","example":"billing_required"},"error":{"type":"string","example":"an active subscription with a saved default payment method is required to buy credits"}}},"models.PurchaseCreditsRequest":{"type":"object","required":["credits"],"properties":{"credits":{"description":"Credits is how many prospect credits to buy. Minimum 1000, maximum\n10000 per call.","type":"integer","example":5000}}},"models.PurchaseCreditsResponse":{"type":"object","properties":{"amountUsd":{"description":"AmountUsd is what the saved default payment method was charged, in\nUS dollars.","type":"number","example":100},"creditsPurchased":{"description":"CreditsPurchased is how many credits were bought and granted.","type":"integer","example":5000},"newBalance":{"description":"NewBalance is the available credit balance after the grant.","type":"integer","example":5000}}},"models.RegisterWebhookRequest":{"type":"object","required":["type","url"],"properties":{"name":{"type":"string"},"type":{"type":"string"},"url":{"type":"string"}}},"models.RegisterWebhookResponse":{"type":"object","properties":{"message":{"type":"string","example":"webhook registered successfully"},"webhook":{"$ref":"#/definitions/models.WebhookResponse"}}},"models.ReplaceSequenceRequest":{"type":"object","required":["steps"],"properties":{"signature":{"description":"Signature omitted keeps the campaign's current signature; pass an empty\nstring to clear it. Editing one body should not silently drop a\nsignature the caller never asked about.","type":"string"},"steps":{"type":"array","maxItems":20,"minItems":1,"items":{"$ref":"#/definitions/models.SequenceStepInput"}}}},"models.ReplaceSequenceResponse":{"type":"object","properties":{"campaignId":{"type":"integer","example":8589949820},"message":{"type":"string","example":"campaign sequence replaced successfully"},"signature":{"type":"string","example":"<p>Robby Frank</p>"},"steps":{"type":"array","items":{"$ref":"#/definitions/models.SequenceStep"}},"total":{"type":"integer","example":3}}},"models.ReplyDraftItem":{"type":"object","properties":{"body":{"type":"string","example":"Thanks for getting back to me - would Tuesday work for a quick call?"},"cc":{"type":"array","items":{"type":"string"},"example":["sam@acme.com"]},"createdAt":{"type":"string","example":"2026-07-30T14:06:00Z"},"id":{"type":"integer","example":789},"inReplyToEmailId":{"description":"InReplyToEmailID is the inbound reply this draft answers. It is null in\nthe rare case the draft's conversation can no longer be resolved (e.g.\nthe inbound email was deleted).","type":"integer","example":456},"leadId":{"type":"integer","example":123},"subject":{"type":"string","example":"Re: Quick question"},"to":{"description":"To and Cc are who the draft goes to when sent, as Reply All does it:\nthe person who wrote the reply in To, and everyone else they addressed\n(their To and Cc lines, minus your workspace's own mailboxes) in Cc.","type":"array","items":{"type":"string"},"example":["jane@acme.com"]}}},"models.ReplyItem":{"type":"object","properties":{"body":{"type":"string","example":"Sounds interesting - can you send more details?"},"campaignId":{"description":"CampaignID is null for standalone replies that could not be attributed\nto a campaign.","type":"integer","example":12},"fromAddress":{"type":"string","example":"jane@prospect.com"},"id":{"type":"integer","example":456},"leadId":{"type":"integer","example":123},"receivedAt":{"type":"string","example":"2026-07-30T14:05:00Z"},"responseCategory":{"description":"ResponseCategory is null while AI categorization is still pending.","type":"string","example":"interested"},"subject":{"type":"string","example":"Re: Quick question"},"threadId":{"type":"string","example":"19842fa1b2c3d4e5"}}},"models.SearchDfyDomainsResponse":{"type":"object","properties":{"domains":{"type":"array","items":{"$ref":"#/definitions/models.DfyDomainSuggestion"}},"totalAvailable":{"type":"integer","example":7},"totalChecked":{"type":"integer","example":10}}},"models.SendReplyDraftResponse":{"type":"object","properties":{"body":{"type":"string","example":"Thanks for getting back to me - would Tuesday work for a quick call?"},"cc":{"type":"array","items":{"type":"string"},"example":["sam@acme.com"]},"id":{"type":"integer","example":789},"leadId":{"type":"integer","example":123},"recipient":{"type":"string","example":"jane@acme.com"},"status":{"type":"string","example":"scheduled"},"subject":{"type":"string","example":"Re: Quick question"},"to":{"description":"To and Cc are who the reply is being sent to (see ReplyDraftItem).","type":"array","items":{"type":"string"},"example":["jane@acme.com"]}}},"models.SenderEmailDNSResponse":{"type":"object","properties":{"checkedAt":{"description":"CheckedAt is when the live DNS lookups ran (this request).","type":"string","example":"2026-07-31T10:30:00Z"},"dkim":{"$ref":"#/definitions/models.DkimCheckResult"},"dmarc":{"$ref":"#/definitions/models.DmarcCheckResult"},"domain":{"type":"string","example":"example.com"},"mx":{"$ref":"#/definitions/models.MxCheckResult"},"senderEmailId":{"type":"integer","example":123},"spf":{"$ref":"#/definitions/models.SpfCheckResult"},"warmup":{"$ref":"#/definitions/models.SenderEmailWarmupStatus"}}},"models.SenderEmailListItem":{"type":"object","properties":{"address":{"type":"string","example":"john@example.com"},"campaignIds":{"type":"array","items":{"type":"integer"},"example":[1,2,3]},"createdAt":{"type":"string","example":"2024-01-10T10:30:00Z"},"currentDailyLimit":{"type":"integer","example":35},"familyName":{"type":"string","example":"Doe"},"givenName":{"type":"string","example":"John"},"healthScore":{"description":"Health score 0-100, higher is better: the share of this account's\nwarm-up emails over the last 7 full days that landed in the inbox rather\nthan spam; 90+ is the app's bar for campaigns. It moves daily as warm-up\nemails land in the inbox (up) or in spam (down). Null while warm-up is\noff or before 20 warm-up emails were checked. The same number the app\nshows as the account's health score.","type":"integer","example":96},"id":{"type":"integer","example":123},"isConnected":{"type":"boolean","example":true},"manuallyDisconnected":{"type":"boolean","example":false},"maximumSendingsLimitPerDay":{"type":"integer","example":50},"minimumSendingsLimitPerDay":{"type":"integer","example":20},"provider":{"type":"string","example":"google"},"toggleGradualBuildUp":{"type":"boolean","example":true},"updatedAt":{"type":"string","example":"2024-01-15T10:30:00Z"}}},"models.SenderEmailResponse":{"type":"object","properties":{"address":{"type":"string","example":"john@example.com"},"campaignIds":{"type":"array","items":{"type":"integer"},"example":[1,2,3]},"connectionHistory":{"description":"The account's last 20 connection events, newest first. Null in the response to PUT /sender-emails/{id}.","type":"array","items":{"$ref":"#/definitions/models.ConnectionHistoryItem"}},"createdAt":{"type":"string","example":"2024-01-10T10:30:00Z"},"currentDailyLimit":{"type":"integer","example":35},"errorCode":{"type":"string","example":"AUTH_FAILED"},"errorMessage":{"type":"string","example":"Authentication failed"},"familyName":{"type":"string","example":"Doe"},"givenName":{"type":"string","example":"John"},"healthScore":{"description":"Health score 0-100, higher is better: the share of this account's\nwarm-up emails over the last 7 full days that landed in the inbox rather\nthan spam; 90+ is the app's bar for campaigns. It moves daily as warm-up\nemails land in the inbox (up) or in spam (down). Null while warm-up is\noff or before 20 warm-up emails were checked. The same number the app\nshows as the account's health score.","type":"integer","example":96},"id":{"type":"integer","example":123},"isConnected":{"type":"boolean","example":true},"lastDisconnectedAt":{"type":"string","example":"2024-01-15T10:30:00Z"},"lastReconnectedAt":{"type":"string","example":"2024-01-16T09:00:00Z"},"manuallyDisconnected":{"type":"boolean","example":false},"maximumSendingsLimitPerDay":{"type":"integer","example":50},"minimumSendingsLimitPerDay":{"type":"integer","example":20},"pictureUrl":{"type":"string","example":"https://example.com/avatar.jpg"},"provider":{"type":"string","example":"google"},"provisioningStatus":{"type":"string","example":"complete"},"signature":{"type":"string","example":"Best regards,\nJohn Doe"},"toggleGradualBuildUp":{"type":"boolean","example":true},"updatedAt":{"type":"string","example":"2024-01-15T10:30:00Z"}}},"models.SenderEmailWarmupSettingsResponse":{"type":"object","properties":{"activatedAt":{"description":"ActivatedAt is when warm-up was first switched on; the ramp counts days\nfrom here. Null until then.","type":"string","example":"2026-09-10T12:00:00Z"},"capLimit":{"description":"CapLimit is the most warm-up emails a day; the ramp stops here.","type":"integer","example":40},"configured":{"description":"Configured is true once the mailbox has warm-up settings of its own.\nWhile false the mailbox has never been enrolled, and the values below\nare the defaults that switching warm-up on would use.","type":"boolean","example":true},"currentPerDay":{"description":"CurrentPerDay is today's ramp target, 0 while warm-up is off. On a\nweekend a weekdays-only mailbox still shows its ramp value, although no\nwarm-up emails send that day.","type":"integer","example":6},"enableReplies":{"description":"EnableReplies lets warm-up recipients reply, adding reply signals.","type":"boolean","example":false},"enabled":{"description":"Enabled is true while the mailbox is warming.","type":"boolean","example":true},"healthScore":{"description":"Health score 0-100, higher is better: the share of this account's\nwarm-up emails over the last 7 full days that landed in the inbox rather\nthan spam; 90+ is the app's bar for campaigns. It moves daily as warm-up\nemails land in the inbox (up) or in spam (down). Null while warm-up is\noff or before 20 warm-up emails were checked.","type":"integer","example":96},"increaseBy":{"description":"IncreaseBy is how many warm-up emails are added each day until CapLimit.","type":"integer","example":2},"senderEmailId":{"type":"integer","example":123},"startLimit":{"description":"StartLimit is how many warm-up emails go out on the first day.","type":"integer","example":2},"timezone":{"description":"Timezone is the IANA timezone the warm-up send window runs in.","type":"string","example":"UTC"},"weekdaysOnly":{"description":"WeekdaysOnly limits warm-up sending to Monday through Friday.","type":"boolean","example":true}}},"models.SenderEmailWarmupStatus":{"type":"object","properties":{"cap":{"description":"Cap is the configured maximum warm-up emails per day; the ramp stops\nthere. 0 while the mailbox has never been enrolled.","type":"integer","example":10},"currentPerDay":{"description":"CurrentPerDay is today's warm-up ramp target (warm-up emails per day),\n0 while not enrolled. On weekends of a weekdays-only account the ramp\nvalue still shows even though no warm-up emails send that day.","type":"integer","example":6},"enrolled":{"description":"Enrolled is true while the mailbox has warm-up enabled.","type":"boolean","example":true},"healthScore":{"description":"Health score 0-100, higher is better: the share of this account's\nwarm-up emails over the last 7 full days that landed in the inbox rather\nthan spam; 90+ is the app's bar for campaigns. It moves daily as warm-up\nemails land in the inbox (up) or in spam (down). Null while warm-up is\noff or before 20 warm-up emails were checked.","type":"integer","example":96}}},"models.SenderReputationListResponse":{"type":"object","properties":{"count":{"type":"integer","example":9},"items":{"type":"array","items":{"$ref":"#/definitions/models.SenderReputationResponse"}}}},"models.SenderReputationResponse":{"type":"object","properties":{"blacklistListings":{"type":"array","items":{"$ref":"#/definitions/models.BlacklistListingResponse"}},"blacklistsChecked":{"description":"BlacklistsChecked is how many zones were queried, reported next to the\nlisting count so the number can never be read as a total.","type":"integer","example":23},"blacklistsListed":{"type":"integer","example":1},"checkedAt":{"type":"string"},"dkimStatus":{"type":"string","example":"OK"},"dmarcPolicy":{"type":"string","example":"quarantine"},"dmarcStatus":{"type":"string","example":"MISSING"},"dnsIssues":{"type":"array","items":{"type":"string"}},"domain":{"type":"string","example":"acme.com"},"healthScore":{"description":"HealthScore is 0-100 combining placement, authentication and blacklist\nstanding. -1 when it cannot be computed.","type":"integer","example":71},"mxStatus":{"type":"string","example":"OK"},"placementScore":{"description":"PlacementScore is the inbox rate over recent completed runs. -1 when the\nmailbox has never been tested.","type":"integer","example":77},"senderAddress":{"type":"string","example":"tom@acme.com"},"senderEmailId":{"type":"integer","example":3},"sendingIp":{"description":"SendingIP is empty for mailboxes on a shared provider (Gmail, Microsoft),\nwhich have no dedicated IP of their own to check.","type":"string","example":"203.0.113.5"},"spfStatus":{"description":"Each status is OK, WARNING, MISSING or ERROR.","type":"string","example":"OK"}}},"models.SequenceStep":{"type":"object","properties":{"body":{"type":"string","example":"<p>Hi {first_name},</p>"},"contentType":{"type":"string","example":"html"},"delayDays":{"type":"integer","example":0},"order":{"type":"integer","example":1},"subject":{"type":"string","example":"Quick question, {first_name}"},"useSameThread":{"type":"boolean","example":true},"variants":{"type":"array","items":{"$ref":"#/definitions/models.SequenceVariant"}}}},"models.SequenceStepInput":{"type":"object","required":["body","subject"],"properties":{"body":{"type":"string","minLength":1},"contentType":{"type":"string","enum":["html","text"]},"delayDays":{"type":"integer","maximum":365,"minimum":0},"order":{"type":"integer","minimum":1},"subject":{"type":"string","maxLength":998,"minLength":1},"useSameThread":{"type":"boolean"},"variants":{"type":"array","maxItems":25,"items":{"$ref":"#/definitions/models.SequenceVariantInput"}}}},"models.SequenceVariant":{"type":"object","properties":{"body":{"type":"string","example":"<p>Hi {first_name},</p>"},"label":{"type":"string","example":"B"},"subject":{"type":"string","example":"Quick question about {company_name}"}}},"models.SequenceVariantInput":{"type":"object","required":["label","subject"],"properties":{"body":{"type":"string"},"label":{"type":"string","maxLength":2,"minLength":1},"subject":{"type":"string","maxLength":998,"minLength":1}}},"models.SetupPingRequest":{"type":"object","properties":{"agent":{"description":"Agent is the assistant's self-reported name, in any form; the backend\nnormalizes it to a short lowercase identifier.","type":"string"}}},"models.SetupPingResponse":{"type":"object","properties":{"agent":{"type":"string"},"ok":{"type":"boolean"}}},"models.SourceProspectsRequest":{"type":"object","required":["campaignId"],"properties":{"campaignId":{"description":"CampaignID is the campaign the sourced prospects are added to.","type":"integer","example":123},"count":{"description":"Count is how many prospects to add in this request. Defaults to 50,\ncapped at 500. A stored prospect costs the reveal price in credits (1\nat the time of writing); the response states the price it charged and\nthe total the batch is expected to cost, so nothing has to trust this\ncomment to stay current.","type":"integer","example":50},"icpId":{"description":"IcpID names the Ideal Customer Profile whose targeting criteria drive\nthe search. Omitted, the workspace's primary profile is used.","type":"integer","example":7}}},"models.SourceProspectsResponse":{"type":"object","properties":{"campaignId":{"type":"integer","example":123},"creditsPerProspect":{"description":"CreditsPerProspect is what one stored prospect debits, read from the\npricing table at request time. It is reported because the price has\nchanged twice (1 to 5 in August 2026, back to 1 in September 2026 when\nthe contact data moved to a flat monthly plan) and every caller that\nbudgets from a hardcoded number budgets wrong the day it changes again.","type":"integer","example":1},"estimatedCredits":{"description":"EstimatedCredits is Requested x CreditsPerProspect: the ceiling this\nbatch can cost. The run settles against prospects actually stored, so\nthe real debit is this or less.","type":"integer","example":50},"icpId":{"description":"IcpID is the profile that was used, echoed back so callers relying on\nthe primary-profile default can see which one it resolved to.","type":"integer","example":7},"prospectSearchId":{"description":"ProspectSearchID identifies the search being advanced. Repeated requests\nfor the same campaign and profile return the same id: the search resumes\nfrom its provider cursor instead of re-revealing the same people.","type":"integer","example":42},"requested":{"type":"integer","example":50},"status":{"type":"string","example":"queued"}}},"models.SpaceDetails":{"type":"object","properties":{"completed_campaigns":{"type":"integer"},"draft_campaigns":{"type":"integer"},"members":{"type":"array","items":{"$ref":"#/definitions/models.MemberInfo"}},"not_started_campaigns":{"type":"integer"},"paused_campaigns":{"type":"integer"},"running_campaigns":{"type":"integer"},"space_id":{"type":"integer"},"total_campaigns":{"type":"integer"},"total_members":{"type":"integer"}}},"models.SpfCheckResult":{"type":"object","properties":{"issues":{"type":"array","items":{"type":"string"}},"record":{"type":"string","example":"v=spf1 include:_spf.google.com ~all"},"status":{"description":"Status is one of OK, WARNING, MISSING, ERROR.","type":"string","example":"OK"}}},"models.StartAutopilotRunRequest":{"type":"object","required":["website"],"properties":{"budgetUsd":{"description":"BudgetUsd, when given, sizes a budget plan that is snapshotted on the\nrun. The plan's domain and mailbox counts are a ceiling for what the run\nbuys after approval (one order holds at most 10 domains and can place\nfewer mailboxes), and its dollar figures are an estimate.","type":"number","example":500},"maxMailboxes":{"description":"MaxMailboxes caps the budget plan's infrastructure. 0 means no cap.","type":"integer","example":20},"replyMode":{"description":"ReplyMode is how inbound replies are handled: off, draft (default),\napprove, or auto. Auto sends AI reply drafts for interested replies\nwithout review - opt in deliberately.","type":"string","example":"draft"},"targetProspects":{"description":"TargetProspects is how many prospects the run reveals per sourcing\nbatch: the first batch once the run is approved, then each top-up. No\nprospect is revealed and no credit is spent before approval. When\nomitted and a budget is given, it is derived from the budget plan; when\n0 or omitted without a budget, the runner default batch applies.","type":"integer","example":500},"website":{"description":"Website is the company website the run is seeded from.","type":"string","example":"https://acme.com"}}},"models.UpdateBillingProfileRequest":{"type":"object","required":["addressLineOne","city","company","country","firstName","lastName","phone","phoneCc","postalCode","state"],"properties":{"addressLineOne":{"type":"string","example":"1 Example Street"},"addressLineTwo":{"type":"string","example":"Suite 200"},"city":{"type":"string","example":"New York"},"company":{"type":"string","example":"Acme Ltd"},"country":{"description":"Country is an ISO 3166-1 alpha-2 code, e.g. US. Case-insensitive on input.","type":"string","example":"US"},"firstName":{"type":"string","example":"Jane"},"lastName":{"type":"string","example":"Doe"},"phone":{"type":"string","example":"2125550142"},"phoneCc":{"description":"PhoneCc is the telephone country calling code without the plus, e.g. 1.","type":"string","example":"1"},"postalCode":{"type":"string","example":"10001"},"state":{"type":"string","example":"NY"}}},"models.UpdateCampaignRequest":{"type":"object","properties":{"allowNonBusinessEmails":{"type":"boolean"},"dailyLimit":{"description":"DailyLimit caps the total emails (initial + follow-ups) this campaign may\nschedule per calendar day in the campaign timezone. Omit to leave the cap\nunchanged; it cannot be cleared through this endpoint.","type":"integer","maximum":10000,"minimum":1},"emoji":{"type":"string","maxLength":10,"minLength":1},"ignoreOutOfOfficeReplies":{"type":"boolean"},"isEnabledCatchallValidated":{"type":"boolean"},"isEnabledEmailVerifier":{"type":"boolean"},"isEnabledIgnoreHardBouncedLeads":{"type":"boolean"},"isEnabledIgnoreLeadsWhoAlreadyResponded":{"type":"boolean"},"isEnabledLlm":{"description":"Settings - all optional boolean flags","type":"boolean"},"isEnabledSkipLeadIfAlreadyExists":{"type":"boolean"},"isEnabledStopFollowUpsAcrossCampaigns":{"type":"boolean"},"isEnabledStopFollowUpsForSameCompany":{"type":"boolean"},"isEnabledStopFollowUpsOnReply":{"type":"boolean"},"maximumSendingLimitPerSenderEmail":{"type":"integer","maximum":10000,"minimum":1},"maximumSendingLimitPerSenderEmailVariation":{"type":"integer","maximum":100,"minimum":0},"maximumTimeBetweenEmails":{"type":"integer","maximum":30,"minimum":2},"minimumHealthScore":{"description":"MinimumHealthScore, 1-100, is the lowest email account health score\n(see healthScore on the sender emails) that may send in this campaign.\nAn account below it, or with no score yet, sends nothing here, first\nemails and follow-ups alike, until its score is back at or above it; its\nconversations wait for it and never move to another account. Send 0 to\nremove the minimum; omit to leave it unchanged. A running campaign\nre-plans its unsent emails at once.","type":"integer","maximum":100,"minimum":0,"example":80},"minimumTimeBetweenEmails":{"type":"integer","maximum":30,"minimum":2},"name":{"type":"string","maxLength":255,"minLength":1},"timezone":{"type":"string"}}},"models.UpdateCampaignResponse":{"type":"object","properties":{"campaign":{"type":"object","properties":{"emoji":{"type":"string","example":"🚀"},"id":{"type":"integer","example":123},"minimumHealthScore":{"description":"MinimumHealthScore is the campaign's minimum email account health\nscore after this update, null when it has none.","type":"integer","example":80},"name":{"type":"string","example":"Q1 Outreach Campaign"}}},"message":{"type":"string","example":"campaign updated successfully"}}},"models.UpdateCampaignScheduleRequest":{"type":"object","required":["timezone"],"properties":{"daysSchedule":{"description":"DaysSchedule is the set of weekdays the campaign may send on. Accepts\nweekday numbers as strings, where Sunday is 0 (\"1\" is Monday), or weekday\nnames such as \"monday\". Names are matched case-insensitively and common\nshort forms (\"mon\", \"tues\") work. It is sent as an array, but stored as\nnumbers and returned as one comma-separated string: a request sending\nMonday to Friday by name reads back as \"1,2,3,4,5\".\n\nFor multiple leads scheduled campaigns (flow 3) and API campaigns.","type":"array","items":{"type":"string"},"example":["1","2","3","4","5"]},"endSchedule":{"type":"string"},"everySchedule":{"type":"integer","minimum":1},"maximumTimeBetweenEmails":{"type":"integer","maximum":30,"minimum":2},"minimumTimeBetweenEmails":{"type":"integer","maximum":30,"minimum":2},"sendAt":{"description":"For single lead scheduled campaigns (flow 2)","type":"string"},"startSchedule":{"type":"string"},"timezone":{"description":"Common field for all flows","type":"string"}}},"models.UpdateCampaignScheduleResponse":{"type":"object","properties":{"campaign":{"type":"object","properties":{"daysSchedule":{"description":"DaysSchedule comes back as one comma-separated string of weekday\nnumbers, where Sunday is 0, even though the request sends an array.","type":"string","example":"1,2,3,4,5"},"endSchedule":{"type":"string","example":"17:00"},"everySchedule":{"type":"integer","example":30},"id":{"type":"integer","example":123},"maximumTimeBetweenEmails":{"type":"integer","example":15},"minimumTimeBetweenEmails":{"type":"integer","example":5},"name":{"type":"string","example":"Q1 Outreach Campaign"},"startSchedule":{"type":"string","example":"09:00"},"timezone":{"type":"string","example":"America/New_York"}}},"message":{"type":"string","example":"campaign schedule updated successfully"}}},"models.UpdateICPRequest":{"type":"object","properties":{"companySizes":{"type":"array","items":{"type":"string"}},"industries":{"type":"array","items":{"type":"string"}},"keywords":{"type":"array","items":{"type":"string"}},"locations":{"type":"array","items":{"type":"string"}},"name":{"type":"string"},"seniorities":{"type":"array","items":{"type":"string"}},"summary":{"type":"string"},"titles":{"type":"array","items":{"type":"string"}}}},"models.UpdateLeadCategoryRequest":{"type":"object","required":["category"],"properties":{"category":{"description":"Category must be one of the lead category values: interested,\nnot_interested, bounced, out_of_office, delivery_incomplete,\nmeeting_booked.","type":"string","example":"interested"}}},"models.UpdateLeadCategoryResponse":{"type":"object","properties":{"lead":{"$ref":"#/definitions/models.LeadCategoryState"},"message":{"type":"string","example":"lead category updated successfully"}}},"models.UpdateLeadRequest":{"type":"object","properties":{"company":{"type":"string"},"customVariables":{"description":"CustomVariables replaces the lead's whole custom variable map when\nprovided. Omit it to leave the existing variables untouched.","type":"object","additionalProperties":true},"email":{"description":"For a lead in a campaign, a different email does not change this lead: it creates a lead with the new email in the same campaign, or overwrites the lead that already has it.","type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"linkedin":{"type":"string"},"middleName":{"description":"Accepted but never saved by this endpoint.","type":"string"},"phone":{"type":"string"},"title":{"type":"string"},"website":{"type":"string"}}},"models.UpdateLeadResponse":{"type":"object","properties":{"lead":{"$ref":"#/definitions/models.UpdatedLead"},"message":{"type":"string","example":"lead updated successfully"}}},"models.UpdateReplyDraftRequest":{"type":"object","properties":{"body":{"type":"string","example":"Thanks for getting back to me - would Tuesday work for a quick call?"},"subject":{"type":"string","example":"Re: Quick question"}}},"models.UpdateSenderEmailRequest":{"type":"object","properties":{"currentDailyLimit":{"type":"integer","maximum":1000,"minimum":0},"familyName":{"type":"string","maxLength":255},"givenName":{"type":"string","maxLength":255,"minLength":1},"maximumSendingsLimitPerDay":{"description":"Daily sending limits","type":"integer","maximum":1000,"minimum":1},"minimumSendingsLimitPerDay":{"type":"integer","maximum":1000,"minimum":1},"signature":{"type":"string"},"toggleGradualBuildUp":{"description":"Gradual build-up toggle. While on, daily sending is capped at min(5 x weeks since the account was connected, 50, maximumSendingsLimitPerDay); turn it off to send above 50 a day.","type":"boolean"}}},"models.UpdateSenderEmailResponse":{"type":"object","properties":{"message":{"type":"string","example":"sender email updated successfully"},"senderEmail":{"$ref":"#/definitions/models.SenderEmailResponse"}}},"models.UpdateSenderEmailWarmupRequest":{"type":"object","properties":{"capLimit":{"description":"CapLimit is the most warm-up emails a day; the ramp stops here.","type":"integer","maximum":200,"minimum":1,"example":40},"enableReplies":{"description":"EnableReplies lets warm-up recipients reply, adding reply signals.","type":"boolean","example":false},"enabled":{"description":"Enabled switches warm-up on (true) or off (false). A mailbox that has\nnever been enrolled needs enabled: true to take any other setting.","type":"boolean","example":true},"increaseBy":{"description":"IncreaseBy is how many warm-up emails are added each day until CapLimit.","type":"integer","maximum":50,"minimum":1,"example":2},"startLimit":{"description":"StartLimit is how many warm-up emails go out on the first day.","type":"integer","maximum":200,"minimum":1,"example":2},"timezone":{"description":"Timezone is the IANA timezone the warm-up send window runs in.","type":"string","example":"America/New_York"},"weekdaysOnly":{"description":"WeekdaysOnly limits warm-up sending to Monday through Friday.","type":"boolean","example":true}}},"models.UpdateWebhookRequest":{"type":"object","properties":{"isEnabled":{"type":"boolean"},"name":{"type":"string","maxLength":255,"minLength":1},"type":{"type":"string"},"url":{"type":"string"}}},"models.UpdateWebhookResponse":{"type":"object","properties":{"message":{"type":"string","example":"webhook updated successfully"},"webhook":{"$ref":"#/definitions/models.WebhookResponse"}}},"models.UpdatedLead":{"type":"object","properties":{"customVariables":{"description":"CustomVariables is returned only for a lead in no campaign.","type":"object","additionalProperties":true},"email":{"type":"string","example":"john.doe@example.com"},"firstName":{"type":"string","example":"John"},"id":{"type":"integer","example":123},"lastName":{"type":"string","example":"Doe"}}},"models.WebhookResponse":{"type":"object","properties":{"createdAt":{"type":"string","example":"2024-01-15T10:30:00Z"},"id":{"type":"integer","example":123},"isEnabled":{"type":"boolean","example":true},"name":{"type":"string","example":"LeadCreated webhook"},"status":{"type":"string","example":"active"},"type":{"type":"string","example":"LeadCreated"},"url":{"type":"string","example":"https://hook.make.com/abc123"}}},"models.WorkspaceItem":{"type":"object","properties":{"createdAt":{"type":"string","example":"2026-08-19T10:30:00Z"},"iconUrl":{"description":"IconURL is where the workspace's icon image can be loaded from. Empty\nwhen the workspace has none. Icons are uploaded in the app.","type":"string","example":"https://whitelabel-assets.emailchaser.com/workspace-icons/51539607552/1766000000-a1b2c3d4e5f6.png"},"id":{"type":"integer","example":51539607552},"isCurrent":{"description":"IsCurrent is true for the workspace the calling API key is bound to.","type":"boolean","example":false},"name":{"type":"string","example":"Acme Outbound"}}}},"securityDefinitions":{"ApiKeyAuth":{"description":"Send `Authorization: Bearer <API key>` on every request. Include the word Bearer and a space before the key.","type":"apiKey","name":"Authorization","in":"header"}}}