> For the complete documentation index, see [llms.txt](https://run.emailchaser.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://run.emailchaser.com/api-reference/leads.md).

# Leads

## List leads

> 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.

```json
{"openapi":"3.1.1","info":{"title":"Emailchaser API","version":"1.0"},"servers":[{"url":"https://api.emailchaser.com/r"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","name":"Authorization","in":"header"}},"schemas":{"models.ListLeadsResponse":{"type":"object","properties":{"hasMore":{"type":"boolean"},"leads":{"type":"array","items":{"$ref":"#/components/schemas/models.LeadResponse"}},"limit":{"type":"integer"},"page":{"type":"integer"},"total":{"type":"integer"}}},"models.LeadResponse":{"type":"object","properties":{"company":{"type":"string"},"createdAt":{"type":"string"},"customVariables":{"description":"CustomVariables are the lead's merge tags beyond the named fields.","type":"object","additionalProperties":true},"email":{"type":"string"},"firstName":{"type":"string"},"id":{"type":"integer"},"lastName":{"type":"string"},"linkedin":{"type":"string"},"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"},"middleName":{"type":"string"},"phone":{"type":"string"},"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"},"title":{"type":"string"},"updatedAt":{"type":"string"},"website":{"type":"string"}}},"models.ErrorResponse":{"type":"object","properties":{"error":{"type":"string"}}},"models.ErrorUnauthorized":{"type":"object","properties":{"error":{"type":"string"}}},"models.ErrorFailedToRetrieveLeads":{"type":"object","properties":{"error":{"type":"string"}}}}},"paths":{"/leads":{"get":{"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.","tags":["Leads"],"summary":"List leads","parameters":[{"schema":{"type":"string"},"description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"schema":{"type":"integer"},"description":"Page number (default: 1)","name":"page","in":"query"},{"schema":{"type":"integer"},"description":"Page size (default: 20, maximum: 200)","name":"limit","in":"query"},{"schema":{"type":"string"},"description":"Filter by exact email address","name":"email","in":"query"},{"schema":{"type":"integer"},"description":"Filter by campaign ID","name":"campaignId","in":"query"}],"responses":{"200":{"description":"List of leads","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ListLeadsResponse"}}}},"400":{"description":"Invalid query parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorResponse"}}}},"401":{"description":"Unauthorized - invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorUnauthorized"}}}},"500":{"description":"Failed to retrieve leads","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorFailedToRetrieveLeads"}}}}}}}}}
```

## Create or update leads in bulk

> Creates or updates multiple leads. If a lead with the same email already exists, it will be updated. Optionally associate leads with a campaign by passing campaignId at the top level of the request body, not inside a lead object. 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.

```json
{"openapi":"3.1.1","info":{"title":"Emailchaser API","version":"1.0"},"servers":[{"url":"https://api.emailchaser.com/r"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","name":"Authorization","in":"header"}},"schemas":{"models.CreateLeadsResponse":{"type":"object","properties":{"count":{"type":"integer"},"leads":{"type":"array","items":{"$ref":"#/components/schemas/models.CreatedLead"}},"message":{"type":"string"}}},"models.CreatedLead":{"type":"object","properties":{"email":{"type":"string"},"id":{"type":"integer"}}},"models.ErrorInvalidRequest":{"type":"object","properties":{"error":{"type":"string"}}},"models.ErrorUnauthorized":{"type":"object","properties":{"error":{"type":"string"}}},"models.ErrorCampaignNotFound":{"type":"object","properties":{"error":{"type":"string"}}},"models.ErrorFailedToCreateLeads":{"type":"object","properties":{"error":{"type":"string"}}},"models.CreateOrUpdateLeadsRequest":{"type":"object","required":["leads"],"properties":{"campaignId":{"type":"integer"},"leads":{"type":"array","maxItems":1000,"minItems":1,"items":{"$ref":"#/components/schemas/models.LeadInput"}}}},"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":{"type":"string"},"phone":{"type":"string"},"title":{"type":"string"},"website":{"type":"string"}}}}},"paths":{"/leads":{"post":{"description":"Creates or updates multiple leads. If a lead with the same email already exists, it will be updated. Optionally associate leads with a campaign by passing campaignId at the top level of the request body, not inside a lead object. 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.","tags":["Leads"],"summary":"Create or update leads in bulk","parameters":[{"schema":{"type":"string"},"description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true}],"responses":{"201":{"description":"Leads processed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.CreateLeadsResponse"}}}},"400":{"description":"Invalid request body or no leads provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorInvalidRequest"}}}},"401":{"description":"Unauthorized - invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorUnauthorized"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorCampaignNotFound"}}}},"500":{"description":"Failed to create leads","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorFailedToCreateLeads"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.CreateOrUpdateLeadsRequest"}}},"description":"Leads to create or update","required":true}}}}}
```

## Get a lead by ID

> Retrieves detailed information about a specific lead.

```json
{"openapi":"3.1.1","info":{"title":"Emailchaser API","version":"1.0"},"servers":[{"url":"https://api.emailchaser.com/r"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","name":"Authorization","in":"header"}},"schemas":{"models.GetLeadResponse":{"type":"object","properties":{"lead":{"$ref":"#/components/schemas/models.LeadResponse"}}},"models.LeadResponse":{"type":"object","properties":{"company":{"type":"string"},"createdAt":{"type":"string"},"customVariables":{"description":"CustomVariables are the lead's merge tags beyond the named fields.","type":"object","additionalProperties":true},"email":{"type":"string"},"firstName":{"type":"string"},"id":{"type":"integer"},"lastName":{"type":"string"},"linkedin":{"type":"string"},"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"},"middleName":{"type":"string"},"phone":{"type":"string"},"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"},"title":{"type":"string"},"updatedAt":{"type":"string"},"website":{"type":"string"}}},"models.ErrorInvalidLeadID":{"type":"object","properties":{"error":{"type":"string"}}},"models.ErrorUnauthorized":{"type":"object","properties":{"error":{"type":"string"}}},"models.ErrorForbidden":{"type":"object","properties":{"error":{"type":"string"}}},"models.ErrorNotFound":{"type":"object","properties":{"error":{"type":"string"}}}}},"paths":{"/leads/{id}":{"get":{"description":"Retrieves detailed information about a specific lead.","tags":["Leads"],"summary":"Get a lead by ID","parameters":[{"schema":{"type":"string"},"description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"schema":{"type":"integer"},"description":"Lead ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"Lead details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.GetLeadResponse"}}}},"400":{"description":"Invalid lead ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorInvalidLeadID"}}}},"401":{"description":"Unauthorized - invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorUnauthorized"}}}},"403":{"description":"Forbidden - lead does not belong to your space","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorForbidden"}}}},"404":{"description":"Lead not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorNotFound"}}}}}}}}}
```

## Update a lead by ID

> Updates an existing lead's information. Only provided fields will be updated.

```json
{"openapi":"3.1.1","info":{"title":"Emailchaser API","version":"1.0"},"servers":[{"url":"https://api.emailchaser.com/r"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","name":"Authorization","in":"header"}},"schemas":{"models.UpdateLeadResponse":{"type":"object","properties":{"lead":{"$ref":"#/components/schemas/models.LeadResponse"},"message":{"type":"string"}}},"models.LeadResponse":{"type":"object","properties":{"company":{"type":"string"},"createdAt":{"type":"string"},"customVariables":{"description":"CustomVariables are the lead's merge tags beyond the named fields.","type":"object","additionalProperties":true},"email":{"type":"string"},"firstName":{"type":"string"},"id":{"type":"integer"},"lastName":{"type":"string"},"linkedin":{"type":"string"},"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"},"middleName":{"type":"string"},"phone":{"type":"string"},"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"},"title":{"type":"string"},"updatedAt":{"type":"string"},"website":{"type":"string"}}},"models.ErrorInvalidLeadID":{"type":"object","properties":{"error":{"type":"string"}}},"models.ErrorUnauthorized":{"type":"object","properties":{"error":{"type":"string"}}},"models.ErrorForbidden":{"type":"object","properties":{"error":{"type":"string"}}},"models.ErrorNotFound":{"type":"object","properties":{"error":{"type":"string"}}},"models.ErrorFailedToUpdateLead":{"type":"object","properties":{"error":{"type":"string"}}},"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":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"linkedin":{"type":"string"},"middleName":{"type":"string"},"phone":{"type":"string"},"title":{"type":"string"},"website":{"type":"string"}}}}},"paths":{"/leads/{id}":{"put":{"description":"Updates an existing lead's information. Only provided fields will be updated.","tags":["Leads"],"summary":"Update a lead by ID","parameters":[{"schema":{"type":"string"},"description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"schema":{"type":"integer"},"description":"Lead ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"Lead updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.UpdateLeadResponse"}}}},"400":{"description":"Invalid lead ID or request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorInvalidLeadID"}}}},"401":{"description":"Unauthorized - invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorUnauthorized"}}}},"403":{"description":"Forbidden - lead does not belong to your space","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorForbidden"}}}},"404":{"description":"Lead not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorNotFound"}}}},"500":{"description":"Failed to update lead","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorFailedToUpdateLead"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.UpdateLeadRequest"}}},"description":"Lead fields to update","required":true}}}}}
```

## Delete a lead by ID

> Deletes a lead and all associated unsent emails. If the lead is associated with campaigns, it will be removed from those campaigns first.

```json
{"openapi":"3.1.1","info":{"title":"Emailchaser API","version":"1.0"},"servers":[{"url":"https://api.emailchaser.com/r"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","name":"Authorization","in":"header"}},"schemas":{"models.DeleteLeadResponse":{"type":"object","properties":{"id":{"type":"integer"},"message":{"type":"string"}}},"models.ErrorInvalidLeadID":{"type":"object","properties":{"error":{"type":"string"}}},"models.ErrorUnauthorized":{"type":"object","properties":{"error":{"type":"string"}}},"models.ErrorForbidden":{"type":"object","properties":{"error":{"type":"string"}}},"models.ErrorNotFound":{"type":"object","properties":{"error":{"type":"string"}}},"models.ErrorFailedToDeleteLead":{"type":"object","properties":{"error":{"type":"string"}}}}},"paths":{"/leads/{id}":{"delete":{"description":"Deletes a lead and all associated unsent emails. If the lead is associated with campaigns, it will be removed from those campaigns first.","tags":["Leads"],"summary":"Delete a lead by ID","parameters":[{"schema":{"type":"string"},"description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"schema":{"type":"integer"},"description":"Lead ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"Lead deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.DeleteLeadResponse"}}}},"400":{"description":"Invalid lead ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorInvalidLeadID"}}}},"401":{"description":"Unauthorized - invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorUnauthorized"}}}},"403":{"description":"Forbidden - lead does not belong to your space","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorForbidden"}}}},"404":{"description":"Lead not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorNotFound"}}}},"500":{"description":"Failed to delete lead","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorFailedToDeleteLead"}}}}}}}}}
```

## Set or correct a lead's category

> 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.

```json
{"openapi":"3.1.1","info":{"title":"Emailchaser API","version":"1.0"},"servers":[{"url":"https://api.emailchaser.com/r"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","name":"Authorization","in":"header"}},"schemas":{"models.UpdateLeadCategoryResponse":{"type":"object","properties":{"lead":{"$ref":"#/components/schemas/models.LeadCategoryState"},"message":{"type":"string"}}},"models.LeadCategoryState":{"type":"object","properties":{"id":{"type":"integer"},"meetingBookedAt":{"description":"MeetingBookedAt is when the meeting was marked as booked and null while\nno meeting is marked.","type":"string"},"tag":{"description":"Tag is the lead's engagement category and null while the lead has never\nbeen categorized.","type":"string"}}},"models.ErrorInvalidCategory":{"type":"object","properties":{"error":{"type":"string"}}},"models.ErrorUnauthorized":{"type":"object","properties":{"error":{"type":"string"}}},"models.ErrorNotFound":{"type":"object","properties":{"error":{"type":"string"}}},"models.ErrorResponse":{"type":"object","properties":{"error":{"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"}}}}},"paths":{"/leads/{id}/category":{"put":{"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.","tags":["Leads"],"summary":"Set or correct a lead's category","parameters":[{"schema":{"type":"string"},"description":"Bearer <API_KEY>","name":"Authorization","in":"header","required":true},{"schema":{"type":"integer"},"description":"Lead ID","name":"id","in":"path","required":true}],"responses":{"200":{"description":"Lead category updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.UpdateLeadCategoryResponse"}}}},"400":{"description":"Invalid lead ID, request body, or category","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorInvalidCategory"}}}},"401":{"description":"Unauthorized - invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorUnauthorized"}}}},"404":{"description":"Lead not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorNotFound"}}}},"500":{"description":"Failed to update lead category","content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.ErrorResponse"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/models.UpdateLeadCategoryRequest"}}},"description":"Category to set","required":true}}}}}
```
