{"openapi":"3.0.0","paths":{"/":{"get":{"operationId":"AppExternalController_getHello","parameters":[],"responses":{"200":{"description":""}},"tags":["AppExternal"]}},"/health":{"get":{"operationId":"AppExternalController_healthCheck","parameters":[],"responses":{"200":{"description":""}},"tags":["AppExternal"]}},"/v1/contact":{"post":{"operationId":"ContactExternalController_create","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Contact data to create. firstName and lastName are required, all other fields are optional.","content":{"application/json":{"schema":{"type":"object","required":["firstName","lastName","profileUrl"],"properties":{"firstName":{"type":"string","example":"John","description":"First name of the contact"},"lastName":{"type":"string","example":"Doe","description":"Last name of the contact"},"profileBaseline":{"type":"string","description":"Profile baseline information"},"location":{"type":"string","example":"New York, NY","description":"Location of the contact"},"jobTitle":{"type":"string","example":"Software Engineer","description":"Job title of the contact"},"positionDates":{"type":"string","example":"2020 - Present","description":"Position dates information"},"company":{"type":"string","example":"Tech Corp","description":"Company name"},"companySize":{"type":"string","example":"51-200","description":"Company size"},"companyUrl":{"type":"string","example":"https://www.linkedin.com/company/gojiberryai","description":"LinkedIn Company URL"},"website":{"type":"string","example":"https://johndoe.com","description":"Website URL"},"industry":{"type":"string","example":"Technology","description":"Industry"},"email":{"type":"string","example":"john@example.com","description":"Primary email address"},"phone":{"type":"string","example":"+1234567890","description":"Primary phone number"},"profileId":{"type":"string","description":"LinkedIn profile ID, eg. ACoAABcwGZ0BPKyEbC2o13Qq7MhrAaD"},"linkedinMemberId":{"type":"number","description":"Stable numeric LinkedIn member ID (ie. 1234567890)"},"profileUrl":{"type":"string","example":"https://linkedin.com/in/johndoe","description":"LinkedIn profile URL"},"picture":{"type":"string","description":"Profile picture URL"},"linkedinIdentifier":{"type":"string","description":"LinkedIn identifier for the profile. eg. johndoe"},"intent":{"type":"string","description":"Intent or activity description for the contact"},"note":{"type":"string","description":"Additional notes about the contact"},"user_note":{"type":"string","description":"Internal notes about the contact"},"agentId":{"type":"number","description":"Associated agent ID"},"listId":{"type":"number","description":"Associated list ID. Useful to add a contact directly in a campaign."},"readyForCampaign":{"type":"boolean","description":"Mainly used for review mode campaigns: setting this to true accepts the lead for the scheduled queue, so it is picked up at the next campaign launch without manual review."}}}}}},"responses":{"201":{"description":"Contact successfully created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"400":{"description":"Contact already excluded or already exists"}},"security":[{"bearer":[]}],"summary":"Create a new contact","tags":["Contacts"]},"get":{"description":"Retrieve contacts with optional pagination, search, and filtering options","operationId":"ContactExternalController_findMany","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"page","required":false,"in":"query","description":"Page number (starts from 1)","schema":{"type":"number"}},{"name":"limit","required":false,"in":"query","description":"Number of contacts per page (default: 20)","schema":{"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search term for firstName, lastName, email, company, website, location, or jobTitle","schema":{"type":"string"}},{"name":"agent","required":false,"in":"query","description":"Filter by agent id","schema":{"type":"string"}},{"name":"dateFrom","required":false,"in":"query","description":"Filter contacts created from this date (YYYY-MM-DD or YYYY-MM-DDTHH:MM)","schema":{"type":"string"}},{"name":"dateTo","required":false,"in":"query","description":"Filter contacts created until this date (YYYY-MM-DD or YYYY-MM-DDTHH:MM)","schema":{"type":"string"}},{"name":"scoreFrom","required":false,"in":"query","description":"Filter contacts with total scoring >= this value (0-3 range)","schema":{"type":"number"}},{"name":"scoreTo","required":false,"in":"query","description":"Filter contacts with total scoring <= this value (0-3 range)","schema":{"type":"number"}},{"name":"intentType","required":false,"in":"query","description":"Filter contacts with intent type","schema":{"type":"string"}},{"name":"listId","required":false,"in":"query","description":"Filter contacts by list id","schema":{"type":"number"}}],"responses":{"200":{"description":"List of contacts with pagination information","content":{"application/json":{"schema":{"type":"object","properties":{"contacts":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}},"total":{"type":"number","description":"Total number of contacts"},"page":{"type":"number","description":"Current page number"},"limit":{"type":"number","description":"Number of contacts per page"},"totalPages":{"type":"number","description":"Total number of pages"}}}}}}},"security":[{"bearer":[]}],"summary":"Get all contacts with pagination and filtering","tags":["Contacts"]}},"/v1/contact/{id}":{"patch":{"operationId":"ContactExternalController_update","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateContactExternalDto"}}}},"responses":{"200":{"description":"Contact successfully updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"404":{"description":"Contact not found"}},"security":[{"bearer":[]}],"summary":"Update a contact","tags":["Contacts"]},"delete":{"description":"Permanently deletes the contact. When a contact is deleted, its profileUrl (along with its LinkedIn member ID and email, when known) is stored in your excluded contacts, so the Lead sources agents will not import it again. Creating the same contact again through POST /v1/contact is also refused (CONTACT_ALREADY_EXCLUDED).","operationId":"ContactExternalController_delete","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":"Contact successfully deleted"},"404":{"description":"Contact not found"}},"security":[{"bearer":[]}],"summary":"Delete a contact","tags":["Contacts"]},"get":{"operationId":"ContactExternalController_findOne","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":"Get one contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}}},"security":[{"bearer":[]}],"summary":"Get one contact","tags":["Contacts"]}},"/v1/contact/list/{listId}/contacts":{"post":{"operationId":"ContactExternalController_addManyToList","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"listId","required":true,"in":"path","schema":{"type":"number"}}],"requestBody":{"required":true,"description":"List of contact IDs to add to the list.","content":{"application/json":{"schema":{"type":"object","required":["contactIds"],"properties":{"contactIds":{"type":"array","items":{"type":"number"},"description":"Array of contact IDs to add"}}}}}},"responses":{"200":{"description":"Contacts successfully added to the list"}},"security":[{"bearer":[]}],"summary":"Add contacts to a list","tags":["Contacts"]},"delete":{"operationId":"ContactExternalController_removeManyFromList","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"listId","required":true,"in":"path","schema":{"type":"number"}}],"requestBody":{"required":true,"description":"List of contact IDs to remove from the list.","content":{"application/json":{"schema":{"type":"object","required":["contactIds"],"properties":{"contactIds":{"type":"array","items":{"type":"number"},"description":"Array of contact IDs to remove"}}}}}},"responses":{"200":{"description":"Contacts successfully removed from the list"}},"security":[{"bearer":[]}],"summary":"Remove contacts from a list","tags":["Contacts"]}},"/v1/contact/intent-type-counts":{"get":{"operationId":"ContactExternalController_getIntentTypeCounts","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Contact counts grouped by intent type","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"intentType":{"type":"string","description":"The intent type"},"count":{"type":"number","description":"Number of contacts with this intent type"}}}}}}}},"security":[{"bearer":[]}],"summary":"Get contact counts by intent type","tags":["Contacts"]}},"/v1/contact/{id}/enrich/email":{"post":{"description":"Attempts to find a verified email for the given contact. This call is synchronous: it queries several enrichment providers in sequence and only responds once the search completes, which can take from a few seconds up to several minutes. Configure your HTTP client timeout accordingly. Each email enrichment that returns an email consumes 1 credit; failed enrichments do not consume any credit.","operationId":"ContactExternalController_enrichEmail","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":"Contact updated with the enriched email","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"404":{"description":"Contact not found"},"422":{"description":"No email could be found for this contact (no credit consumed)"}},"security":[{"bearer":[]}],"summary":"Enrich a contact's email","tags":["Contacts"]}},"/v1/contact/{id}/enrich/phone":{"post":{"description":"Attempts to find a mobile phone number for the given contact. This call is synchronous: it queries several enrichment providers in sequence and only responds once the search completes, which can take from a few seconds up to several minutes. Configure your HTTP client timeout accordingly. Each phone enrichment that returns a phone number consumes 10 credits; failed enrichments do not consume any credit.","operationId":"ContactExternalController_enrichPhone","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":"Contact updated with the enriched phone number. If no phone could be found, the contact is returned with phoneEnriched set to true and no phone (no credit consumed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"402":{"description":"Not enough credits (phone enrichment requires 10 credits)"},"404":{"description":"Contact not found"}},"security":[{"bearer":[]}],"summary":"Enrich a contact's mobile phone","tags":["Contacts"]}},"/v1/contact/{id}/enrich/email/async":{"post":{"description":"Starts an email enrichment for the given contact and returns immediately, without waiting for the enrichment providers to respond. Recommended over the synchronous route for AI/MCP integrations and any client that cannot hold a long-running HTTP connection. To verify completion, poll GET /v1/contact/{id}: the enrichment is done once emailEnriched is true — the email field is then filled if an email was found, and stays empty otherwise. Each enrichment that finds an email consumes 1 credit; failed enrichments do not consume any credit.","operationId":"ContactExternalController_enrichEmailAsync","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"202":{"description":"Email enrichment accepted and running in the background","content":{"application/json":{"schema":{"type":"object","properties":{"accepted":{"type":"boolean","example":true},"contactId":{"type":"number","example":123},"message":{"type":"string","description":"How to check for completion"}}}}}},"402":{"description":"Not enough credits (email enrichment requires 1 credit)"},"404":{"description":"Contact not found"}},"security":[{"bearer":[]}],"summary":"Enrich a contact's email (asynchronous)","tags":["Contacts"]}},"/v1/contact/{id}/enrich/phone/async":{"post":{"description":"Starts a mobile phone enrichment for the given contact and returns immediately, without waiting for the enrichment providers to respond. Recommended over the synchronous route for AI/MCP integrations and any client that cannot hold a long-running HTTP connection. To verify completion, poll GET /v1/contact/{id}: the enrichment is done once phoneEnriched is true — the phone field is then filled if a phone was found, and stays empty otherwise. Each enrichment that finds a phone number consumes 10 credits; failed enrichments do not consume any credit.","operationId":"ContactExternalController_enrichPhoneAsync","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"202":{"description":"Phone enrichment accepted and running in the background","content":{"application/json":{"schema":{"type":"object","properties":{"accepted":{"type":"boolean","example":true},"contactId":{"type":"number","example":123},"message":{"type":"string","description":"How to check for completion"}}}}}},"402":{"description":"Not enough credits (phone enrichment requires 10 credits)"},"404":{"description":"Contact not found"}},"security":[{"bearer":[]}],"summary":"Enrich a contact's mobile phone (asynchronous)","tags":["Contacts"]}},"/v1/contact/{id}/reject":{"post":{"description":"Marks the contact as rejected: it disappears from the review queue, is excluded from campaign scheduling (readyForCampaign is reset to false), and will not be picked up by automatic campaign launches. The rejection records who rejected the lead and when (rejectedAt / rejectedByUserId on the returned contact). Providing a reason is optional but recommended: reasons are stored on the contact and help understand why leads are rejected so targeting can be refined. Calling this route on an already-rejected contact only updates the reason (latest reason wins); the original rejection timestamp and reviewer are kept. A rejection is reversible with POST /v1/contact/{id}/unreject.","operationId":"ContactExternalController_reject","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"requestBody":{"required":false,"description":"Optional rejection reason. Use one of the stable reason codes when the lead does not match your targeting; use \"other\" together with reasonText for anything else. When reason is \"other\", reasonText is required for the reason to be stored (it is ignored for the other codes).","content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","enum":["company_competitor","company_wrong_industry_or_type","company_wrong_size","company_wrong_location","contact_wrong_title_or_function","contact_wrong_seniority","contact_known_or_duplicate","data_incorrect_or_outdated","signal_not_relevant","other"],"nullable":true,"description":"Stable reason code. company_* codes flag a mismatch on the company (competitor, wrong industry/type, wrong size, wrong location); contact_* codes flag a mismatch on the person (wrong title/function, wrong seniority, already known or duplicate); data_incorrect_or_outdated flags bad enrichment data; signal_not_relevant flags an irrelevant intent signal; other requires reasonText.","example":"company_wrong_size"},"reasonText":{"type":"string","nullable":true,"maxLength":500,"description":"Free-text explanation, only stored when reason is \"other\". Maximum 500 characters.","example":"Already a customer through their parent company"}}}}}},"responses":{"200":{"description":"The rejected contact, with rejectedAt, rejectedByUserId, rejectionReason and rejectionReasonText set","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"400":{"description":"Invalid reason code or reasonText longer than 500 characters"},"404":{"description":"Contact not found"}},"security":[{"bearer":[]}],"summary":"Reject a lead","tags":["Contacts"]}},"/v1/contact/{id}/unreject":{"post":{"description":"Reverses a rejection: clears rejectedAt, rejectedByUserId and the stored reason, putting the contact back in the review queue. This does not approve the lead — readyForCampaign is reset to false, so the contact still needs to be accepted (e.g. by updating it with readyForCampaign set to true) before it is picked up by a campaign. Note that because readyForCampaign is always reset, calling this route on an approved contact moves it back to the review queue.","operationId":"ContactExternalController_unreject","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":"The contact with all rejection fields cleared","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"404":{"description":"Contact not found"}},"security":[{"bearer":[]}],"summary":"Unreject a lead","tags":["Contacts"]}},"/v1/contact/{id}/signals":{"post":{"description":"Designed for first-party signals: actions a lead takes in your own product or stack, pushed programmatically the moment they happen. Hook it to any event you already track (a lead signs up for a free trial, books a demo, attends your webinar, downloads your pricing guide, upgrades to a paid plan...) and the signal is added to the contact, next to the signals detected by Gojiberry agents, most recent first. Anything that can send an HTTP request can feed it: your backend, a CRM workflow, a form or scheduling tool webhook, Zapier, Make or n8n. The possibilities are endless.\n\nTo send a first-party signal, describe what happened in intent (e.g. \"Booked a demo from the pricing page\"), optionally add a short label in intentKeyword (e.g. \"demo_booked\"), and leave intentType empty. Signals without an intentType are recorded on every call, so repeated events (a second demo, another webinar) are all kept; make sure your integration sends each event only once.\n\nSet intentType only to record one of the signal types detected by Gojiberry agents. A typed signal the contact already has (same intentType and intentKeyword) is not recorded twice and the call returns created: false; HIRING, HIRING_SURGE, NEW_DECISION_MAKER, RECENT_ACTIVITY, RECENTLY_CHANGED_JOB and RECENT_FUNDING_EVENT are recorded at most once per contact, whatever the keyword. At least one of intent, intentType or intentKeyword is required.","operationId":"ContactExternalController_addSignal","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddContactSignalDto"}}}},"responses":{"200":{"description":"Whether the signal was recorded, and the contact with its up-to-date signals","content":{"application/json":{"schema":{"type":"object","properties":{"created":{"type":"boolean","description":"false when the contact already had this signal (only possible when intentType is set)"},"contact":{"$ref":"#/components/schemas/Contact"}}}}}},"400":{"description":"Invalid intentType, or none of intent, intentType and intentKeyword provided"},"404":{"description":"Contact not found"}},"security":[{"bearer":[]}],"summary":"Add a signal to a contact (first-party signals)","tags":["Contacts"]}},"/v1/campaign":{"post":{"description":"Create a campaign with its steps, lead source lists and sending seats. A campaign is the **campaign agent** — the outreach half of a **Full Cycle agent**: create the list first (`POST /v1/list`), then the source agent that fills it with leads (`POST /v1/agent`), then this campaign to reach out to them. **At least one list (lead source) is required.** The campaign reaches out to the contacts of its lists; lists are typically filled by a source agent (the lead finder, see `POST /v1/agent`) or by importing contacts. If the campaign contains any LinkedIn step (invitation, invitationNote, message, voiceMessage, visitProfile, likePosts), a `linkedinSeatId` is required. If it contains email steps, at least one `emailSeatId` is required; multiple email seats (up to 100) are only allowed when the campaign has email steps exclusively. Step rules: at most one Connection Request step, an invitation cannot come after a LinkedIn message or voice message, and `delayAfterLastStep` must be at least 1 day (see the `steps` field for per-type fields and limits). When the campaign has a Connection Request step, 1st-degree connections are excluded from the campaign by default. **Once activated, the campaign starts the outreach automatically on a daily basis.** The launch hours can be configured in the LinkedIn seat settings.","operationId":"CampaignExternalController_create","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCampaignExternalDto"}}}},"responses":{"201":{"description":"Campaign created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Campaign"}}}},"400":{"description":"Invalid steps or seat configuration"},"404":{"description":"List or seat not found"}},"security":[{"bearer":[]}],"summary":"Create a campaign","tags":["Campaigns"]},"get":{"description":"Retrieve all campaigns with optional filtering by active status","operationId":"CampaignExternalController_findAll","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"activeOnly","required":false,"in":"query","description":"Filter by active status (true/false)","schema":{"type":"boolean"}}],"responses":{"200":{"description":"List of campaigns","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Campaign"}}}}}},"security":[{"bearer":[]}],"summary":"Get all campaigns","tags":["Campaigns"]}},"/v1/campaign/{id}":{"get":{"operationId":"CampaignExternalController_findOne","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":"Get one campaign","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Campaign"}}}}},"security":[{"bearer":[]}],"summary":"Get one campaign","tags":["Campaigns"]},"patch":{"description":"Update campaign name, associated list IDs, and existing step messages or same/AI mode. **Steps must keep the same length and type order; only the message content and mode can be edited in the steps.**","operationId":"CampaignExternalController_update","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCampaignExternalDto"}}}},"responses":{"200":{"description":"Campaign updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Campaign"}}}},"400":{"description":"Invalid step update or list assignment"},"404":{"description":"Campaign or list not found"}},"security":[{"bearer":[]}],"summary":"Update one campaign","tags":["Campaigns"]},"delete":{"description":"Permanently delete a campaign. The campaign is removed from the daily outreach scheduling, so no further steps are sent (messages and invitations already sent are not retracted). The campaign's lists and contacts are kept, and the contacts' campaign progress is reset so they can be enrolled into another campaign. The outreach logs (`GET /v1/campaign/{id}/logs`) are detached from the campaign and are no longer retrievable through the API. **Deletion cannot be undone.** An active campaign can be deleted directly; it does not need to be deactivated first.","operationId":"CampaignExternalController_remove","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":"Campaign deleted"},"404":{"description":"Campaign not found"}},"security":[{"bearer":[]}],"summary":"Delete a campaign","tags":["Campaigns"]}},"/v1/campaign/{id}/logs":{"get":{"description":"Retrieve the outreach event log of a campaign (paginated, most recent first). Each entry records an action performed on a contact, e.g. invitation, invitationNote, invitationAccepted, message, voiceMessage, visitProfile, likePosts, email, replied, email-reply, interested, not-interested.","operationId":"CampaignExternalController_findLogs","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"number"}},{"name":"page","required":false,"in":"query","description":"Page number, starting at 1 (default 1)","schema":{"type":"number"}},{"name":"limit","required":false,"in":"query","description":"Entries per page, between 1 and 100 (default 50)","schema":{"type":"number"}}],"responses":{"200":{"description":"Paginated campaign contact logs","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number"},"action":{"type":"string","example":"invitation"},"contactId":{"type":"number","nullable":true},"contact":{"type":"object","nullable":true,"description":"The contact the action was performed on"},"campaignId":{"type":"number"},"createdAt":{"type":"string","format":"date-time"}}}},"total":{"type":"number"},"page":{"type":"number"},"limit":{"type":"number"}}}}}},"404":{"description":"Campaign not found"}},"security":[{"bearer":[]}],"summary":"Get campaign outreach logs","tags":["Campaigns"]}},"/v1/campaign/{id}/status":{"patch":{"description":"Set the campaign `active` flag. An active campaign is included in the daily outreach scheduling; a deactivated campaign stops sending new steps (messages and invitations already sent are not retracted). Activating requires free campaign capacity: each account can run 2 campaigns, plus one per assigned Full-cycle Agent add-on. If the account is at capacity, an Full-cycle Agent add-on from the organization is assigned automatically; otherwise the request fails with 400. This endpoint is rate limited to 10 requests per minute.","operationId":"CampaignExternalController_toggleStatus","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToggleCampaignStatusExternalDto"}}}},"responses":{"200":{"description":"Campaign status updated"},"400":{"description":"No campaign capacity left (no Outreach Agent add-on available)"},"404":{"description":"Campaign or LinkedIn seat not found"},"429":{"description":"Rate limit exceeded (10 requests per minute)."}},"security":[{"bearer":[]}],"summary":"Activate or deactivate a campaign","tags":["Campaigns"]}},"/v1/list":{"post":{"description":"Create a new list for the authenticated user. The list is the link between a source agent (which imports the leads it finds into the list) and a campaign agent (which reaches out to the contacts of the list). Creating the list is the first step of setting up a **Full Cycle agent**: create the list, then the source agent (`POST /v1/agent`), then the campaign (`POST /v1/campaign`).","operationId":"ListExternalController_create","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateListDto"}}}},"responses":{"201":{"description":"List created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/List"}}}}},"security":[{"bearer":[]}],"summary":"Create a list","tags":["Lists"]},"get":{"description":"Retrieve all lists with contact counts","operationId":"ListExternalController_findAll","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"List of lists with contact counts","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/List"}}}}}},"security":[{"bearer":[]}],"summary":"Get all lists","tags":["Lists"]}},"/v1/list/{id}":{"get":{"description":"Retrieve a single list by ID with its contacts","operationId":"ListExternalController_findOne","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":"Get one list with contacts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/List"}}}}},"security":[{"bearer":[]}],"summary":"Get one list","tags":["Lists"]}},"/v1/agent":{"post":{"description":"Create a new source agent (the lead finder) for the authenticated user. **A list is mandatory: the agent imports the leads it sources into a list, so you must create a list first (`POST /v1/list`) and link it to the agent through the required `listId` field.** If the active agent limit is reached, the agent is still created but paused. A source agent only finds leads — it does no outreach. Without a campaign agent attached to the same list, it shows up as a **legacy agent** in the platform. Best practice is to follow up with `POST /v1/campaign` using the same list as lead source, forming a **Full Cycle agent** (list + source agent + campaign agent) that sources and reaches out to leads automatically.","operationId":"AgentExternalController_create","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAgentExternalDto"}}}},"responses":{"201":{"description":"Agent created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Agent"}}}},"400":{"description":"Invalid payload (e.g. fewer than 4 variables)"},"404":{"description":"List not found"}},"security":[{"bearer":[]}],"summary":"Create a source agent","tags":["Lead source agents"]},"get":{"description":"Retrieve all source agents for the authenticated user","operationId":"AgentExternalController_findAll","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"List of agents","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Agent"}}}}}},"security":[{"bearer":[]}],"summary":"Get all source agents","tags":["Lead source agents"]}},"/v1/agent/{id}":{"get":{"description":"Retrieve a single source agent by ID","operationId":"AgentExternalController_findOne","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":"Agent found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Agent"}}}},"404":{"description":"Agent not found"}},"security":[{"bearer":[]}],"summary":"Get one source agent","tags":["Lead source agents"]},"patch":{"description":"Update a source agent by ID","operationId":"AgentExternalController_update","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAgentDto"}}}},"responses":{"200":{"description":"Agent updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Agent"}}}},"400":{"description":"Invalid payload (e.g. fewer than 4 variables)"},"404":{"description":"Agent not found"}},"security":[{"bearer":[]}],"summary":"Update a source agent","tags":["Lead source agents"]},"delete":{"description":"Permanently delete a source agent. The agent stops sourcing immediately and its run logs (`GET /v1/agent/{id}/logs`) are deleted with it. The list the agent imports into and the leads already imported are **not** deleted. For website-visitor agents, the website tracking script is deactivated. **Deletion cannot be undone** — to stop an agent temporarily, use `PATCH /v1/agent/{id}` with `{ \"paused\": true }` instead.","operationId":"AgentExternalController_delete","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":"Agent deleted"},"404":{"description":"Agent not found"}},"security":[{"bearer":[]}],"summary":"Delete a source agent","tags":["Lead source agents"]}},"/v1/agent/{id}/logs":{"get":{"description":"Retrieve the run logs for one AI Agent. Each log entry corresponds to one signal run and reports how many leads were sourced vs. how many ended up imported into the platform after AI scoring & filtering. Use this to answer questions like \"how many leads did this agent create last week?\", \"which signals are producing the most leads?\", or \"did any of this agent's signals fail recently?\". Optionally narrow the window with dateFrom / dateTo (ISO 8601). Capped at the most recent 100 logs (newest first).","operationId":"AgentExternalController_findLogs","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"number"}},{"name":"dateFrom","required":false,"in":"query","description":"Start of the time window (ISO 8601, e.g. 2025-04-01T00:00:00Z). Used together with dateTo.","schema":{"type":"string"}},{"name":"dateTo","required":false,"in":"query","description":"End of the time window (ISO 8601). Used together with dateFrom.","schema":{"type":"string"}}],"responses":{"200":{"description":"Up to 100 most recent log entries for the agent (newest first).","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AgentLog"}}}}},"404":{"description":"Agent not found"}},"security":[{"bearer":[]}],"summary":"Get logs for a source agent","tags":["Lead source agents"]}},"/v1/directory/industries":{"get":{"description":"Return the suggested values for the `industry` filter of `POST /v1/directory/leads/search` and `POST /v1/directory/companies/search`, both flat and grouped by category. Free of charge. Other industry values are accepted by the search endpoints, but these labels line up with the pool taxonomy and give the best results.","operationId":"DirectoryExternalController_getIndustries","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Suggested industries, flat and grouped","content":{"application/json":{"schema":{"type":"object","properties":{"count":{"type":"number","description":"Number of distinct industries"},"industries":{"type":"array","items":{"type":"string"},"description":"Flat list of suggested industry values"},"groups":{"type":"array","description":"Same industries grouped by category (as shown in the app)","items":{"type":"object","properties":{"name":{"type":"string","example":"Technology"},"industries":{"type":"array","items":{"type":"string"},"example":["Software Development & SaaS","Cybersecurity"]}}}}}}}}}},"security":[{"bearer":[]}],"summary":"List suggested industries (free)","tags":["Directory"]}},"/v1/directory/locations":{"get":{"description":"Return the suggested values for the `location` filter of `POST /v1/directory/leads/search`, grouped by country: the country name itself plus one string per region or state in the form `\"<region>, <country>\"` (for example `\"California, United States\"`). Free of charge. Other location strings are accepted by the search endpoint too, but these values line up with how the pool spells locations and give the best results.","operationId":"DirectoryExternalController_getLocations","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Suggested locations grouped by country","content":{"application/json":{"schema":{"type":"object","properties":{"count":{"type":"number","description":"Number of countries"},"countries":{"type":"array","items":{"type":"object","properties":{"country":{"type":"string","example":"United States","description":"Country name, usable as a `location` value on its own"},"locations":{"type":"array","items":{"type":"string"},"example":["Alabama, United States","Alaska, United States","California, United States"],"description":"Region/state-level locations of that country, formatted as \"<region>, <country>\" (empty when the pool has no region breakdown)"}}}}}}}}}},"security":[{"bearer":[]}],"summary":"List suggested locations (free)","tags":["Directory"]}},"/v1/directory/leads/search":{"post":{"description":"Search the lead pool with filters. Free of charge: only id, firstName, jobTitle, location and industry are filled — all other fields are returned empty. Use `POST /v1/directory/leads/reveal` with the returned ids to get the full lead data (0.5 credit per lead). Results are deterministic (no randomness), so the same filters + page always return the same leads.","operationId":"DirectoryExternalController_search","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Search filters. At least one filter is required. String-array fields also accept a single string.","content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"type":"number","example":25,"description":"Number of leads per page (1-100, default 10)"},"page":{"type":"number","example":1,"description":"Page number, starts at 1"},"jobTitle":{"type":"array","items":{"type":"string"},"example":["Head of Marketing"],"description":"Job titles to match"},"location":{"type":"array","items":{"type":"string"},"example":["France"],"description":"Locations or countries to match"},"industry":{"type":"array","items":{"type":"string"},"example":["Software Development"],"description":"Industries to prefer"},"companySize":{"type":"array","items":{"type":"string"},"example":["51-200"],"description":"Company size brackets (e.g. \"2-10\", \"51-200\", \"10001+\")"},"name":{"type":"string","description":"Full name of the person"},"company":{"type":"string","description":"Company name"},"companyUrl":{"type":"array","items":{"type":"string"},"description":"LinkedIn company URLs to restrict the search to"},"companyIdentifier":{"type":"array","items":{"type":"string"},"example":["gojiberryai"],"description":"Exact LinkedIn company identifiers (the vanity slug in linkedin.com/company/<identifier>) to restrict the search to"},"companyLastFundingRoundSince":{"type":"string","example":"2025-01-01","description":"Only companies with a funding round since this date (YYYY-MM-DD)"},"isCompanyHiring":{"type":"boolean","description":"Only companies with at least one active job posting"},"hasRecentFundingRound":{"type":"boolean","description":"Only companies with a funding round in the last 12 months"},"hasRecentlyChangedJob":{"type":"boolean","description":"Only people who started their current position in the last 3 months"},"hasRecentActivity":{"type":"boolean","description":"Only people who were active on LinkedIn (posted, commented or reacted) in the last 90 days. People whose last activity date is unknown are excluded."}}}}}},"responses":{"200":{"description":"Page of masked leads. Empty string fields become available after revealing the lead.","content":{"application/json":{"schema":{"type":"object","properties":{"leads":{"type":"array","items":{"type":"object"}},"total":{"type":"number","description":"Total number of leads matching the filters, capped at 10 000 (the deepest page reachable anyway)"},"page":{"type":"number"},"limit":{"type":"number"}}}}}},"400":{"description":"No filter provided"}},"security":[{"bearer":[]}],"summary":"Search leads (free preview)","tags":["Directory"]}},"/v1/directory/leads/reveal":{"post":{"description":"Return the full data of the leads with the given ids (from `POST /v1/directory/leads/search`). Each revealed lead costs 0.5 credit. If the credit balance cannot cover all requested ids, only the first affordable leads are revealed and charged. When `listId` is provided, the revealed leads are also saved as contacts into that list (no extra credit cost): leads already in the workspace, excluded, or missing a first/last name are skipped. The save is synchronous, so a large reveal with `listId` can take noticeably longer — configure your HTTP client timeout accordingly.","operationId":"DirectoryExternalController_reveal","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Ids of the leads to reveal (max 100 per request).","content":{"application/json":{"schema":{"type":"object","required":["ids"],"properties":{"ids":{"type":"array","items":{"type":"number"},"example":[1234567890,987654321],"description":"Lead ids returned by the search endpoint"},"listId":{"type":"number","example":123,"description":"Optional id of one of your lists. When provided, the revealed leads are also saved as contacts into this list (free of extra charge)."}}}}}},"responses":{"200":{"description":"Full leads and credit usage summary","content":{"application/json":{"schema":{"type":"object","properties":{"leads":{"type":"array","items":{"type":"object"}},"requested":{"type":"number","description":"Number of distinct ids requested"},"revealed":{"type":"number","description":"Number of leads actually returned"},"creditsUsed":{"type":"number"},"savedToList":{"type":"number","description":"Only present when listId was provided: number of leads saved as contacts into the list (already-existing/excluded leads are skipped)"}}}}}},"400":{"description":"ids is missing or empty, or listId is not a positive integer"},"404":{"description":"listId does not match one of your lists"}},"security":[{"bearer":[]}],"summary":"Reveal full leads (0.5 credit per lead)","tags":["Directory"]}},"/v1/directory/companies/search":{"post":{"description":"Search the company pool with filters. Free of charge: only id, size, industry and headquarters are filled — all other fields are returned empty. Use `POST /v1/directory/companies/reveal` with the returned ids to get the full company data (0.5 credit per company). Results are deterministic (no randomness), so the same filters + page always return the same companies. Disclaimer: a company \"uses\" a technology when we saw that technology mentioned in at least one of the company's job offers in the past months, or in the website sources. It signals the company hires for or runs that technology, not a verified install base.","operationId":"DirectoryExternalController_searchCompanies","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Search filters. At least one filter is required. String-array fields also accept a single string.","content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"type":"number","example":25,"description":"Number of companies per page (1-100, default 20)"},"page":{"type":"number","example":1,"description":"Page number, starts at 1"},"companySize":{"type":"array","items":{"type":"string"},"example":["51-200"],"description":"Company size brackets (e.g. \"2-10\", \"51-200\", \"10001+\")"},"industry":{"type":"array","items":{"type":"string"},"example":["Software Development"],"description":"Industries to prefer"},"headquarters":{"type":"array","items":{"type":"string"},"example":["France"],"description":"Headquarters locations or countries to match"},"isHiring":{"type":"boolean","description":"Only companies with at least one active job posting"},"isB2b":{"type":"boolean","description":"true: only B2B companies, false: only B2C companies. Omit to get both."},"technologiesUsed":{"type":"array","items":{"type":"string"},"example":["hubspot"],"description":"Technologies the company uses. Disclaimer: a company \"uses\" a technology when we saw that technology mentioned in at least one of the company's job offers in the past months, or in the website sources. It signals the company hires for or runs that technology, not a verified install base."}}}}}},"responses":{"200":{"description":"Page of masked companies. Empty fields become available after revealing the company.","content":{"application/json":{"schema":{"type":"object","properties":{"companies":{"type":"array","items":{"type":"object"}},"total":{"type":"number","description":"Total number of companies matching the filters, capped at 10 000 (the deepest page reachable anyway)"},"page":{"type":"number"},"limit":{"type":"number"}}}}}},"400":{"description":"No filter provided"}},"security":[{"bearer":[]}],"summary":"Search companies (free preview)","tags":["Directory"]}},"/v1/directory/companies/reveal":{"post":{"description":"Return the full data of the companies with the given ids (from `POST /v1/directory/companies/search`). Each revealed company costs 0.5 credit. If the credit balance cannot cover all requested ids, only the first affordable companies are revealed and charged. Disclaimer: a company \"uses\" a technology when we saw that technology mentioned in at least one of the company's job offers in the past months, or in the website sources. It signals the company hires for or runs that technology, not a verified install base.","operationId":"DirectoryExternalController_revealCompanies","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Ids of the companies to reveal (max 100 per request).","content":{"application/json":{"schema":{"type":"object","required":["ids"],"properties":{"ids":{"type":"array","items":{"type":"number"},"example":[1441,10667],"description":"Company ids returned by the search endpoint"}}}}}},"responses":{"200":{"description":"Full companies and credit usage summary","content":{"application/json":{"schema":{"type":"object","properties":{"companies":{"type":"array","items":{"type":"object"}},"requested":{"type":"number","description":"Number of distinct ids requested"},"revealed":{"type":"number","description":"Number of companies actually returned"},"creditsUsed":{"type":"number"}}}}}},"400":{"description":"ids is missing or empty"}},"security":[{"bearer":[]}],"summary":"Reveal full companies (0.5 credit per company)","tags":["Directory"]}},"/v1/unibox/contact/{contactId}":{"get":{"description":"Legacy endpoint. Returns all unibox chats (with their messages) associated with the given contact for the authenticated user.","operationId":"UniboxExternalController_getMessagesByContactId","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"contactId","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":"List of unibox chats for the contact, ordered by most recent message first","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Unibox"}}}}},"404":{"description":"Contact not found"}},"security":[{"bearer":[]}],"summary":"Legacy - Get unibox chats and messages for a contact","tags":["Unibox"]}},"/v1/unibox/threads":{"get":{"description":"Returns paginated unibox threads for a specific LinkedIn seat when seatId is provided. If no seatId is provided, it returns threads for all LinkedIn seats in the account.","operationId":"UniboxExternalController_getThreads","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"dateTo","required":false,"in":"query","description":"Filter threads with lastMessageDate on or before this ISO date string.","schema":{"type":"string"}},{"name":"dateFrom","required":false,"in":"query","description":"Filter threads with lastMessageDate on or after this ISO date string.","schema":{"type":"string"}},{"name":"seen","required":false,"in":"query","description":"Filter threads by seen/read status. Use true or false.","schema":{"type":"boolean"}},{"name":"interested","required":false,"in":"query","description":"Filter threads by interested status. Use true or false.","schema":{"type":"boolean"}},{"name":"attendeeFullName","required":false,"in":"query","description":"Filter threads by attendee full name. Partial matches are supported.","schema":{"type":"string"}},{"name":"order","required":false,"in":"query","description":"Sort direction by last message date. Default: DESC.","schema":{"enum":["ASC","DESC"],"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Number of threads per page. Default: 20. Minimum: 1. Maximum: 100.","schema":{"type":"number"}},{"name":"page","required":false,"in":"query","description":"Page number. Default: 1. Minimum: 1.","schema":{"type":"number"}},{"name":"seatId","required":false,"in":"query","description":"Optional LinkedIn seat ID. If omitted, returns threads for all seats in the account.","schema":{"type":"number"}}],"responses":{"200":{"description":"Paginated unibox threads retrieved successfully."}},"security":[{"bearer":[]}],"summary":"Get unibox threads","tags":["Unibox"]}},"/v1/unibox/threads/{threadId}/messages":{"get":{"description":"Returns paginated unibox messages for a thread owned by the authenticated user.","operationId":"UniboxExternalController_getThreadMessages","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"threadId","required":true,"in":"path","schema":{"type":"number"}},{"name":"order","required":false,"in":"query","description":"Sort direction by message creation date. Default: ASC.","schema":{"enum":["ASC","DESC"],"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Number of messages per page. Default: 50. Minimum: 1. Maximum: 200.","schema":{"type":"number"}},{"name":"page","required":false,"in":"query","description":"Page number. Default: 1. Minimum: 1.","schema":{"type":"number"}}],"responses":{"200":{"description":"Paginated unibox messages retrieved successfully."}},"security":[{"bearer":[]}],"summary":"Get unibox messages from a thread","tags":["Unibox"]}},"/v1/unibox/messages/send-message":{"post":{"description":"Sends a LinkedIn message in a unibox thread. Provide chatId and message in the body. chatId is the internal unibox thread id returned as id by GET /v1/unibox/threads; it is not the LinkedIn conversationUrn. Optional multipart fields: file, voiceMessage.","operationId":"UniboxExternalController_sendMessage","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["chatId"],"properties":{"chatId":{"type":"string","description":"Internal unibox thread id returned as id by GET /v1/unibox/threads. This is not the LinkedIn conversationUrn."},"message":{"type":"string","description":"Text message to send. Optional only when sending a file or voiceMessage.","example":"Hi, thanks for your reply."},"file":{"type":"string","format":"binary","description":"Optional file attachment. Do not send together with voiceMessage."},"voiceMessage":{"type":"string","format":"binary","description":"Optional voice message attachment. Do not send together with file."}}}}}},"responses":{"201":{"description":"Message sent successfully."}},"security":[{"bearer":[]}],"summary":"Send a unibox LinkedIn message","tags":["Unibox"]}},"/v1/unibox/messages/download-attachment/{messageId}/{attachmentId}":{"get":{"description":"Downloads an attachment from a unibox message, returned as base64 with its mime type. Please note: each call fetches the attachment live from the connected LinkedIn account. To keep that account healthy, we recommend downloading each attachment once and caching it on your side rather than calling this endpoint repeatedly. This endpoint is rate limited to 10 requests per minute.","operationId":"UniboxExternalController_downloadAttachment","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}},{"name":"messageId","required":true,"in":"path","schema":{"type":"string"}},{"name":"attachmentId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Attachment downloaded successfully, returned as base64 with its mime type."},"404":{"description":"Message or attachment not found."},"429":{"description":"Rate limit exceeded (10 requests per minute)."}},"security":[{"bearer":[]}],"summary":"Download a unibox message attachment","tags":["Unibox"]}},"/v1/organization":{"get":{"description":"Retrieve the organization of the authenticated user.","operationId":"OrganizationExternalController_getOrganization","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Organization information","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"number","description":"Organization ID"},"companyName":{"type":"string","nullable":true,"description":"Company name"},"website":{"type":"string","nullable":true,"description":"Company website URL"},"industry":{"type":"string","nullable":true,"description":"Industry"},"companySize":{"type":"string","nullable":true,"description":"Company size"},"description":{"type":"string","nullable":true,"description":"Company description"},"linkedinPage":{"type":"string","nullable":true,"description":"LinkedIn company page URL"},"ownerId":{"type":"number","nullable":true,"description":"User ID of the organization owner"},"createdAt":{"type":"string","format":"date-time","description":"Creation date"}}}}}},"404":{"description":"Organization not found"}},"security":[{"bearer":[]}],"summary":"Get the organization","tags":["Organization"]}},"/v1/organization/members":{"get":{"description":"Retrieve all members of the organization of the authenticated user. The member IDs can be used in the x-impersonate-user-id header to act on behalf of a member.","operationId":"OrganizationExternalController_getOrganizationMembers","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"List of organization members","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number","description":"User ID of the member (usable in the x-impersonate-user-id header)"},"email":{"type":"string","description":"Email address of the member"},"firstName":{"type":"string","nullable":true,"description":"First name of the member"},"lastName":{"type":"string","nullable":true,"description":"Last name of the member"},"organizationRole":{"type":"string","enum":["member","admin"],"description":"Role of the member in the organization"},"isActive":{"type":"boolean","description":"Whether the member account is active"},"createdAt":{"type":"string","format":"date-time","description":"Account creation date"}}}}}}},"404":{"description":"Organization not found"}},"security":[{"bearer":[]}],"summary":"Get all organization members","tags":["Organization"]}},"/v1/user/me":{"get":{"description":"Retrieve the profile of the authenticated user. When impersonating with the x-impersonate-user-id header, the impersonated user is returned.","operationId":"UserExternalController_getMe","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"User information","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"number","description":"User ID"},"email":{"type":"string","description":"Email address"},"firstName":{"type":"string","nullable":true,"description":"First name"},"lastName":{"type":"string","nullable":true,"description":"Last name"},"organizationId":{"type":"number","nullable":true,"description":"ID of the organization the user belongs to"},"organizationRole":{"type":"string","enum":["member","admin"],"description":"Role of the user in the organization"},"isActive":{"type":"boolean","description":"Whether the account is active"},"language":{"type":"string","nullable":true,"description":"Preferred language (e.g. en-US)"},"country":{"type":"string","nullable":true,"description":"Country code (ISO 3166-1 alpha-2)"},"timezone":{"type":"string","nullable":true,"description":"Timezone (e.g. Europe/Paris)"},"createdAt":{"type":"string","format":"date-time","description":"Account creation date"}}}}}}},"security":[{"bearer":[]}],"summary":"Get the authenticated user","tags":["User"]}},"/v1/user/me/permissions":{"get":{"description":"Retrieve the organization permissions of the authenticated user. When impersonating with the x-impersonate-user-id header, the impersonated user permissions are returned.","operationId":"UserExternalController_getMyPermissions","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"List of permission keys granted to the user in their organization","content":{"application/json":{"schema":{"type":"array","items":{"type":"string","enum":["organization.view","organization.delete","organization.settings.update","organization.ownership.transfer","organization.members.remove","organization.members.change_role","organization.members.impersonate","organization.owner.impersonate","organization.invitations.create","organization.invitations.revoke","organization.billing.manage","organization.team_analytics.view","organization.competitor_filtering.manage"]},"example":["organization.settings.update","organization.members.remove"]}}}}},"security":[{"bearer":[]}],"summary":"Get the permissions of the authenticated user","tags":["User"]}},"/v1/email-seat":{"get":{"description":"Retrieve the email seats (connected Gmail / Outlook mailboxes) of the authenticated user. The seat IDs can be used in the emailSeatIds field when creating a campaign with POST /v1/campaign. When impersonating with the x-impersonate-user-id header, the email seats of the impersonated user are returned.","operationId":"EmailSeatExternalController_getEmailSeats","parameters":[{"name":"x-impersonate-user-id","in":"header","description":"ID of an organization member to act on behalf of. The API key user must be the owner of the organization and the target user must belong to the same organization.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"List of email seats","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number","description":"Email seat ID (usable in the emailSeatIds field of POST /v1/campaign)"},"email":{"type":"string","description":"Email address of the mailbox"},"name":{"type":"string","nullable":true,"description":"Display name used as sender name"},"firstName":{"type":"string","nullable":true,"description":"First name of the sender"},"lastName":{"type":"string","nullable":true,"description":"Last name of the sender"},"type":{"type":"string","enum":["google","outlook"],"description":"Email provider of the mailbox"},"purchased":{"type":"boolean","description":"Whether the mailbox was purchased through Gojiberry"},"dailyEmailMax":{"type":"number","description":"Maximum number of emails sent per day"},"dailyEmailCount":{"type":"number","description":"Number of emails sent today"},"launchHour":{"type":"number","description":"Hour of the day (0-23) at which the daily sending starts"},"activeDays":{"type":"object","nullable":true,"description":"Days of the week on which the seat sends emails","properties":{"monday":{"type":"boolean"},"tuesday":{"type":"boolean"},"wednesday":{"type":"boolean"},"thursday":{"type":"boolean"},"friday":{"type":"boolean"},"saturday":{"type":"boolean"},"sunday":{"type":"boolean"}}},"trackOpening":{"type":"boolean","description":"Whether email opens are tracked"},"removeUnsubscribeLink":{"type":"boolean","description":"Whether the unsubscribe link is removed from the emails"},"warmupEnabled":{"type":"boolean","description":"Whether the mailbox warm-up is enabled"},"warmupCompleted":{"type":"boolean","description":"Whether the mailbox warm-up is completed"},"warmupStartedAt":{"type":"string","format":"date-time","nullable":true,"description":"Date at which the warm-up started"},"createdAt":{"type":"string","format":"date-time","description":"Date at which the mailbox was connected"},"updatedAt":{"type":"string","format":"date-time","description":"Last update date"}}}}}}}},"security":[{"bearer":[]}],"summary":"Get all email seats","tags":["Email Seat"]}}},"info":{"title":"Gojiberry AI - External API","description":"\n# Gojiberry AI External API\n\nThis API allows you to programmatically access your Gojiberry AI data and integrate it with your applications.\n\n## How it works\n\nGojiberry runs your outreach as a pipeline of three building blocks:\n\n1. **Source agent — the lead finder.** A source agent continuously sources leads matching your targeting criteria (job titles, industries, locations, signals, ...) and scores them with AI.\n2. **List — where the leads land.** Every source agent is linked to a list (`listId` is required when creating an agent): the leads it finds are imported as contacts into that list. Lists can also be filled by importing contacts yourself.\n3. **Campaign — the outreach.** A campaign uses one or more lists as its lead source (`listIds` is required when creating a campaign) and runs its steps (LinkedIn invitations, messages, emails, ...) on the contacts of those lists, sending through your LinkedIn and/or email seats. Once activated, the campaign runs the outreach automatically on a daily basis.\n\n**Terminology.** In the Gojiberry platform, an **Agent** is a *campaign agent* — the campaign that performs the outreach. A **source agent** is the lead finder that sources the leads. The two are linked through a **list**: the source agent imports the leads it finds into the list, and the campaign agent reaches out to the contacts of that list.\n\n**Best practice: create a Full Cycle agent.** Always create the three resources in this order — first the list, then the source agent that fills it, then the campaign that reaches out to it. Together, the list + source agent + campaign agent form a **Full Cycle agent**: leads are sourced and outreached automatically, end to end. If you create a source agent without a campaign agent, it shows up as a **legacy agent** in the platform: it only sources leads and does **no outreach**. So it's best practice to always create all three.\n\nA typical setup through the API:\n\n```text\nPOST /v1/list                  # 1. create the list\nPOST /v1/agent                 # 2. create the source agent (lead finder) linked to that list (listId)\nPOST /v1/campaign              # 3. create the campaign agent using that list as lead source (listIds)\nGET  /v1/campaign/{id}/logs    # 4. follow the outreach activity\n```\n\n## Authentication\n\nAll API requests require authentication using a Bearer token. To get your API key:\n\n1. **Log in to your Gojiberry AI account** at [https://app.gojiberry.ai](https://app.gojiberry.ai)\n2. **Navigate to Settings** → **API** section\n3. **Click \"Create API Key\"** to generate a new token\n4. **Copy the API key** when it's displayed (you won't be able to see it again)\n5. **Use the token** in your requests by adding the header: `Authorization: Bearer YOUR_API_KEY`\n\n## Impersonation (organization owners)\n\nIf your API key belongs to the **owner of an organization**, you can perform any API call on behalf of another member of your organization by adding the `x-impersonate-user-id` header with the member's user ID:\n\n```bash\n# Get the contacts of the organization member with user ID 123\ncurl -H \"Authorization: Bearer YOUR_API_KEY\" \\\n     -H \"x-impersonate-user-id: 123\" \\\n     https://ext.gojiberry.ai/v1/contact\n```\n\nWhen impersonating, the request behaves exactly as if it was made by that member: you read and write their data (contacts, campaigns, lists, unibox, etc.).\n\nRules:\n- Your API key must belong to the **organization owner**; other members cannot impersonate.\n- The impersonated user must belong to the **same organization** as the API key user.\n- Requests that don't meet these rules are rejected with a `401 Unauthorized`.\n\nTip: use `GET /v1/organization/members` to list the members of your organization and their user IDs, and `GET /v1/user/me` to verify which user the current request acts as.\n\n## Rate Limits\n\n- **100 requests per minute** per API key\n\n## Base URL\n- **Production**: `https://ext.gojiberry.ai`\n\n## Endpoints\n\n### Contacts\n- `GET /v1/contact` - Get all contacts with filtering and pagination\n- `GET /v1/contact/{id}` - Get a specific contact\n- `GET /v1/contact/health` - Health check endpoint\n\n## Example Usage\n\n```bash\n# Get all contacts\ncurl -H \"Authorization: Bearer YOUR_API_KEY\" \\\n     https://ext.gojiberry.ai/v1/contact\n\n# Get contacts with filtering\ncurl -H \"Authorization: Bearer YOUR_API_KEY\" \\\n     \"https://ext.gojiberry.ai/v1/contact?search=john&agent=1&dateFrom=2024-01-01\"\n\n# Get a specific contact\ncurl -H \"Authorization: Bearer YOUR_API_KEY\" \\\n     https://ext.gojiberry.ai/v1/contact/123\n```\n\n## Webhooks\n\nGojiberry allows you to receive real-time notifications when new contacts are created or updated by configuring a webhook URL.\n\n### How to Configure Webhooks\n\n1. **Log in to your Gojiberry AI account** at [https://app.gojiberry.ai](https://app.gojiberry.ai)\n2. **Navigate to the Integrations page** at [https://app.gojiberry.ai/integrations](https://app.gojiberry.ai/integrations)\n3. **Add a Webhook integration** by clicking the Webhook option\n4. **Enter your webhook URL** where you want to receive notifications\n5. **Save the configuration**\n\nOnce configured, Gojiberry will send POST requests to your webhook URL whenever:\n- A new contact is created\n- A contact is updated\n- Other relevant events occur in your account\n\nThe webhook payload will include the full contact data in JSON format. Each webhook request includes a custom header `x-gojiberry-user-id` containing your user ID for verification purposes.\n\n### Verifying Webhook Signatures (optional)\n\nYou can optionally configure a signing secret on your Webhook integration. When a secret is set, every webhook request also includes:\n\n- `x-gojiberry-timestamp`: the Unix timestamp (in seconds) at which the request was sent\n- `x-gojiberry-signature`: `sha256=` followed by the hex-encoded HMAC-SHA256 of `{timestamp}.{rawBody}` computed with your signing secret\n\nTo verify a request:\n\n1. Read the raw request body as a string (do not re-serialize parsed JSON).\n2. Concatenate the value of `x-gojiberry-timestamp`, a `.` character, and the raw body.\n3. Compute the HMAC-SHA256 of that string using your signing secret, hex-encoded, and prefix it with `sha256=`.\n4. Compare it to `x-gojiberry-signature` using a constant-time comparison.\n5. Reject requests whose timestamp is too old (we recommend a 5 minute tolerance) to prevent replay attacks.\n\nExample in Node.js:\n\n```js\nconst crypto = require('crypto');\n\nfunction verifyGojiberrySignature(rawBody, headers, secret) {\n  const timestamp = headers['x-gojiberry-timestamp'];\n  const signature = headers['x-gojiberry-signature'];\n  if (!timestamp || !signature) return false;\n  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;\n  const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(timestamp + '.' + rawBody).digest('hex');\n  return expected.length === signature.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));\n}\n```\n\nIf no signing secret is configured, these headers are not sent.\n\n## Support\n\nFor API support, please contact us at [hello@gojiberry.ai](mailto:hello@gojiberry.ai)\n      ","version":"1.0","contact":{}},"tags":[],"servers":[],"components":{"securitySchemes":{"API-Key":{"scheme":"bearer","bearerFormat":"JWT","type":"http","name":"JWT","description":"Enter API Key","in":"header"}},"schemas":{"Account":{"type":"object","properties":{}},"Agent":{"type":"object","properties":{"id":{"type":"number","description":"Unique identifier"},"name":{"type":"string","description":"Display name of the agent"},"targetJobTitles":{"description":"List of target job titles to prospect. Part of the ICP.","type":"array","items":{"type":"string"}},"targetIndustries":{"description":"List of target industries to prospect. Part of the ICP.","type":"array","items":{"type":"string"}},"targetCompanySizes":{"description":"List of target company sizes (e.g. \"1-10\", \"11-50\"). Part of the ICP.","type":"array","items":{"type":"string"}},"targetLocations":{"description":"List of target geographic locations. Part of the ICP.","type":"array","items":{"type":"string"}},"targetCompanyTypes":{"description":"List of target company types (e.g. \"Public\", \"Private\")","type":"array","items":{"type":"string"}},"additionalCriteria":{"type":"string","description":"Additional free-text targeting criteria","nullable":true},"variables":{"description":"Variables used by the agent to find high intent leads. (Keywords, Competitor Page URL, etc.)","nullable":true,"type":"array","items":{"type":"string"}},"ignoredCompanies":{"description":"Company names to exclude from prospecting. Part of the ICP.","nullable":true,"type":"array","items":{"type":"string"}},"mandatoryKeywords":{"description":"Keywords that must be present in a lead profile to be included","nullable":true,"type":"array","items":{"type":"string"}},"agentType":{"type":"string","description":"Type of agent: \"autopilot\", \"lookalike\" or \"website-visitor\"","example":"autopilot"},"scriptInstalled":{"type":"boolean","description":"Whether the visitor tracking script is installed on the website (website-visitor agents)","default":false},"trackingScriptId":{"type":"string","description":"Snitcher tracking script ID, used as the profileId in the tracking snippet (website-visitor agents)","nullable":true},"maxCreditUsage":{"type":"number","description":"Max number of credits per month","nullable":true,"example":200},"currentCreditUsage":{"type":"number","description":"Credits consumed so far in the current period (e.g. website-visitor company identifications)","default":0},"lastRun":{"format":"date-time","type":"string","description":"Timestamp of the last agent execution","nullable":true},"paused":{"type":"boolean","description":"Whether the agent is paused and will not run on its next scheduled time","default":false},"excludeServiceProviders":{"type":"boolean","description":"Whether to exclude service provider companies from prospecting","default":false},"skipIcpFilter":{"type":"boolean","description":"Whether to skip the ICP (Ideal Customer Profile) filter when sourcing leads","default":false},"includeOpenToWorkProfiles":{"type":"boolean","description":"Whether to include LinkedIn profiles marked as open to work","default":false},"leadWaterfall":{"type":"boolean","description":"Whether to use the lead waterfall strategy (cascade through multiple sources)","default":true},"minLeadScore":{"type":"number","description":"Minimum lead score threshold (0.00–1.00) required to include a lead","nullable":true,"example":0.75},"userId":{"type":"number","description":"ID of the account (user) who owns this agent"},"listId":{"type":"number","description":"ID of the list this agent sources contacts into","nullable":true},"createdAt":{"format":"date-time","type":"string","description":"Creation timestamp"},"updatedAt":{"format":"date-time","type":"string","description":"Last update timestamp"}},"required":["id","name","targetJobTitles","targetIndustries","targetCompanySizes","targetLocations","targetCompanyTypes","agentType","scriptInstalled","currentCreditUsage","paused","excludeServiceProviders","skipIcpFilter","includeOpenToWorkProfiles","leadWaterfall","userId","createdAt","updatedAt"]},"Campaign":{"type":"object","properties":{"id":{"type":"number","description":"Unique identifier for the campaign"},"name":{"type":"string","description":"Name of the campaign"},"steps":{"type":"array","description":"Campaign steps"},"userId":{"type":"number","description":"User ID who owns this campaign"},"active":{"type":"boolean","description":"Whether the campaign is active","default":true},"excludeFirstDegreeFromCampaigns":{"type":"boolean","description":"Exclude first degree connections from campaigns","default":true},"splitLinkedinMessagesIntoConversation":{"type":"boolean","description":"When enabled, LinkedIn message steps may be sent as multiple shorter messages when it feels natural","default":false},"isManual":{"type":"boolean","description":"Whether the campaign is manual","default":false},"skipInvitationAfterDays":{"type":"number","description":"Number of days after which a pending invitation is skipped and only email steps are sent"},"language":{"type":"string","description":"Campaign language","nullable":true},"tone":{"type":"string","description":"Campaign tone","enum":["professional","direct","conversational"]},"goal":{"type":"string","description":"Campaign goal","enum":["conversations","demos"]},"linkedinSeatId":{"type":"number","description":"LinkedIn seat ID for this campaign"},"createdAt":{"format":"date-time","type":"string","description":"Creation timestamp"},"updatedAt":{"format":"date-time","type":"string","description":"Last update timestamp"}},"required":["id","name","userId","active","excludeFirstDegreeFromCampaigns","splitLinkedinMessagesIntoConversation","isManual","createdAt","updatedAt"]},"List":{"type":"object","properties":{"id":{"type":"number","description":"Unique identifier for the list"},"name":{"type":"string","description":"Name of the list"},"userId":{"type":"number","description":"User ID who owns this list"},"user":{"description":"Associated user account","allOf":[{"$ref":"#/components/schemas/Account"}]},"campaignId":{"type":"number","description":"Campaign ID associated with this list"},"campaign":{"description":"Associated campaign","allOf":[{"$ref":"#/components/schemas/Campaign"}]},"customFields":{"description":"Custom field definitions imported into this list (append-only union across imports)","type":"array","items":{"type":"string"}},"createdAt":{"format":"date-time","type":"string","description":"Creation timestamp"},"updatedAt":{"format":"date-time","type":"string","description":"Last update timestamp"}},"required":["id","name","userId","user","createdAt","updatedAt"]},"Contact":{"type":"object","properties":{"id":{"type":"number","description":"Unique identifier for the contact"},"firstName":{"type":"string","description":"First name of the contact"},"lastName":{"type":"string","description":"Last name of the contact"},"fullName":{"type":"string","description":"Full name (auto-generated from firstName and lastName)","readOnly":true},"profileBaseline":{"type":"string","description":"Profile baseline information"},"location":{"type":"string","description":"Location of the contact"},"companyLocation":{"type":"string","description":"Location of the contact company"},"jobTitle":{"type":"string","description":"Job title of the contact"},"positionDates":{"type":"string","description":"Position dates information"},"company":{"type":"string","description":"Company name"},"companySize":{"type":"string","description":"Company size"},"companyUrl":{"type":"string","description":"Company URL"},"website":{"type":"string","description":"Website URL"},"industry":{"type":"string","description":"Industry"},"email":{"type":"string","description":"Primary email address"},"email_2":{"type":"string","description":"Secondary email address"},"email_3":{"type":"string","description":"Tertiary email address"},"secondEmail":{"type":"string","description":"Second email alias"},"thirdEmail":{"type":"string","description":"Third email alias"},"phone":{"type":"string","description":"Primary phone number"},"phone_2":{"type":"string","description":"Secondary phone number"},"phone_3":{"type":"string","description":"Tertiary phone number"},"secondPhone":{"type":"string","description":"Second phone alias"},"thirdPhone":{"type":"string","description":"Third phone alias"},"profileId":{"type":"string","description":"LinkedIn profile ID"},"linkedinMemberId":{"type":"number","description":"Numeric LinkedIn member ID decoded from the profile URN"},"profileUrl":{"type":"string","description":"LinkedIn profile URL"},"picture":{"type":"string","description":"Profile picture URL"},"openToWork":{"type":"boolean","description":"Open to work status"},"skills":{"description":"Array of skills","type":"array","items":{"type":"string"}},"linkedinIdentifier":{"type":"string","description":"LinkedIn identifier for the profile"},"note":{"type":"string","description":"Additional notes about the contact"},"user_note":{"type":"string","description":"User personal notes about the contact"},"intent":{"type":"string","description":"Intent or activity description"},"intent_scoring":{"type":"number","description":"AI scoring for intent relevance between 0 and 3"},"total_scoring":{"type":"number","description":"Total scoring (intent_scoring + scoring)"},"intent_type":{"type":"object","description":"Type or category of the intent"},"intent_keyword":{"type":"string","description":"Keyword extracted from the intent"},"scoring":{"type":"number","description":"AI scoring result between 0 and 1 (0 = no match, 1 = perfect match)"},"score_reasoning":{"type":"string","description":"Reasoning for the AI scoring result"},"bounced":{"type":"boolean","description":"Whether the contact email has bounced","default":false},"unsubscribed":{"type":"boolean","description":"Whether the contact has unsubscribed from emails","default":false},"redListed":{"type":"boolean","description":"Whether the contact is on the global GDPR red list (do-not-contact)","default":false},"blocked":{"type":"boolean","description":"Whether the contact matches the organization people blocklist","default":false},"companyBlocked":{"type":"boolean","description":"Set once a campaign pass skipped the contact because its company is on the organization blocklist","default":false},"emailEnriched":{"type":"boolean","description":"Whether email has been enriched","default":false},"phoneEnriched":{"type":"boolean","description":"Whether phone has been enriched","default":false},"enrichingEmail":{"type":"boolean","description":"Whether an email enrichment job currently holds the billing lease","default":false},"enrichingPhone":{"type":"boolean","description":"Whether a phone enrichment job currently holds the billing lease","default":false},"fit":{"type":"string","description":"Contact fit classification","enum":["qualified","unknown","out-of-scope"]},"rejectedAt":{"format":"date-time","type":"string","description":"When this contact was rejected","nullable":true},"rejectedByUserId":{"type":"number","description":"User who rejected this contact","nullable":true},"rejectionReason":{"type":"string","description":"Stable rejection reason","nullable":true},"rejectionReasonText":{"type":"string","description":"Free-text reason when Other was submitted with text","nullable":true},"deepResearchStartedAt":{"format":"date-time","type":"string","description":"When the latest deep research started for this contact"},"deepResearchEndedAt":{"format":"date-time","type":"string","description":"When the latest deep research ended for this contact"},"email_template":{"type":"object","description":"Email template in JSON format with subject and body"},"linkedin_template":{"type":"string","description":"LinkedIn template text"},"personalizedMessages":{"description":"Personalized messages for the contact","type":"array","items":{"type":"string"}},"destinationStatus":{"description":"Destination sync status information","type":"array","items":{"type":"string"}},"customFields":{"type":"object","description":"Custom fields imported from CSV, keyed by normalized snake_case name (rendered with the custom_ prefix)"},"campaignStatus":{"description":"Campaign status information. Useful to know what is the achieved step of the contact in the campaign — i.e. how far the contact has progressed through the campaign's sequence. The stepNumber matches the campaign's step numbers (the steps defined on the campaign), so a stepNumber of 2 means the contact has reached step 2 of that campaign.","type":"array","items":{"type":"string"}},"signals":{"description":"Every signal recorded for the contact, most recent first (agents and LinkedIn pools)","type":"array","items":{"type":"string"}},"userId":{"type":"number","description":"User ID who owns this contact"},"user":{"description":"Associated user account","allOf":[{"$ref":"#/components/schemas/Account"}]},"agentId":{"type":"number","description":"Associated agent ID"},"agent":{"description":"Associated agent","allOf":[{"$ref":"#/components/schemas/Agent"}]},"listId":{"type":"number","description":"Associated list ID"},"list":{"description":"Associated list","allOf":[{"$ref":"#/components/schemas/List"}]},"readyForCampaign":{"type":"boolean","description":"Ready for campaign flag","default":false},"hasStartedCampaign":{"type":"boolean","description":"Has started campaign flag","default":false},"state":{"type":"string","description":"Contact state","enum":["finished","paused","1stnetwork","excluded","onhold"]},"createdAt":{"format":"date-time","type":"string","description":"Creation timestamp"},"updatedAt":{"format":"date-time","type":"string","description":"Last update timestamp"}},"required":["id","firstName","lastName","fullName","bounced","unsubscribed","redListed","blocked","companyBlocked","emailEnriched","phoneEnriched","enrichingEmail","enrichingPhone","userId","user","readyForCampaign","hasStartedCampaign","createdAt","updatedAt"]},"PersonalizedMessageDto":{"type":"object","properties":{"content":{"type":"string","description":"Message content for this campaign step"},"parts":{"description":"Derived conversation parts when split is enabled","type":"array","items":{"type":"string"}},"subject":{"type":"string","description":"Email subject for this campaign step. Only used when the step is an email step. When editing an email step, always resend the subject together with the content — even if only the content changed — otherwise the stored subject is erased."},"stepNumber":{"type":"number","description":"Campaign step number this message applies to. Please refer to the campaign.steps array !"},"stepId":{"type":"string","description":"Server-managed stable id of the campaign step. Read-only: the server derives it from stepNumber on write, any sent value is ignored. Declared so stored messages can be resent as-is."},"audioUrl":{"type":"string","description":"Server-managed audio of an AI voice message step, spoken in the account’s cloned voice. Read-only: generated voice messages are kept as stored and any sent value is ignored. Declared so stored messages can be resent as-is."},"aiVoiceId":{"type":"number","description":"Server-managed id of the cloned voice that spoke `audioUrl`. Read-only: any sent value is ignored. Declared so stored messages can be resent as-is."}},"required":["content","stepNumber"]},"UpdateContactExternalDto":{"type":"object","properties":{"firstName":{"type":"string","description":"First name of the contact"},"lastName":{"type":"string","description":"Last name of the contact"},"profileUrl":{"type":"string","description":"LinkedIn profile URL"},"profileId":{"type":"string","description":"LinkedIn profile ID"},"profileBaseline":{"type":"string","description":"Profile baseline information"},"location":{"type":"string","description":"Location of the contact"},"companyLocation":{"type":"string","description":"Location of the contact company"},"jobTitle":{"type":"string","description":"Job title of the contact"},"positionDates":{"type":"string","description":"Position dates information"},"company":{"type":"string","description":"Company name"},"companySize":{"type":"string","description":"Company size"},"companyUrl":{"type":"string","description":"LinkedIn company URL"},"website":{"type":"string","description":"Website URL"},"industry":{"type":"string","description":"Industry"},"email":{"type":"string","description":"Primary email address"},"picture":{"type":"string","description":"Profile picture URL"},"openToWork":{"type":"boolean","description":"Open to work status"},"linkedinIdentifier":{"type":"string","description":"LinkedIn identifier for the profile"},"intent":{"type":"string","description":"Intent or activity description for the contact"},"fit":{"type":"string","description":"Contact fit classification","enum":["qualified","unknown","out-of-scope"]},"readyForCampaign":{"type":"boolean","description":"Mainly used for review mode campaigns: setting this to true accepts the lead for the scheduled queue, so it is picked up at the next campaign launch without manual review. Also set by the AI agent when a contact should be scheduled."},"state":{"type":"string","description":"Contact state. Set to \"paused\" to stop a contact from progressing in the campaign. Send null to clear the state. Possible values: \"paused\" (stops campaign progression), \"finished\" (completed all steps), \"1stnetwork\" (1st degree LinkedIn connection), \"excluded\" (excluded from all campaigns), \"answered\" (replied to a message).","enum":["finished","paused","1stnetwork","excluded","answered"],"nullable":true},"personalizedMessages":{"description":"Personalized messages for each campaign step. This array REPLACES the stored messages: always resend every step you want to keep, with all of its fields (content, subject, parts). Any step or field left out of the payload is erased.","type":"array","items":{"$ref":"#/components/schemas/PersonalizedMessageDto"}}}},"AddContactSignalDto":{"type":"object","properties":{"intent":{"type":"string","description":"Human-readable description of what the lead did, as it should appear on the contact","example":"Booked a demo from the pricing page"},"intentType":{"type":"string","description":"Leave empty for first-party signals. Only set it to record one of the signal types detected by Gojiberry agents.","enum":["SEARCH_KEYWORD","SEARCH_KEYWORD_COMMENT","SEARCH_KEYWORD_LIKE","SEARCH_KEYWORD_POST","EVENT_KEYWORD","GROUP_KEYWORD","COMPETITOR_PAGE_URL","INFLUENCER_PAGE_URL","YOUR_COMPANY","RECENT_ACTIVITY","RECENTLY_CHANGED_JOB","RECENT_FUNDING_EVENT","VISITED_PROFILE","YOUR_PROFILE","YOUR_COMPANY_FOLLOWERS","JOB_SEARCH","HIRING","HIRING_SURGE","TECHNOLOGY","NEW_DECISION_MAKER","LOOKALIKE","WEBSITE_VISITOR"]},"intentKeyword":{"type":"string","description":"Short label for the signal, e.g. trial_signup, demo_booked or webinar_attended","example":"demo_booked","maxLength":255}}},"CreateCampaignExternalDto":{"type":"object","properties":{"name":{"type":"string","description":"Campaign name","example":"Q1 Sales Campaign"},"steps":{"type":"array","description":"Campaign steps, executed in order. Valid types: invitation, invitationNote, message, voiceMessage, visitProfile, likePosts, email. Ordering rules: at most one Connection Request step (invitation or invitationNote); an invitation cannot come after a message or voiceMessage step. Common fields: delayAfterLastStep in days (integer >= 1, default 2). stepNumber is assigned automatically from the array order and unknown fields are ignored. message and email steps take a messageMode: 'ai' (content is generated by AI for each contact, the default when no content is provided) or 'same' (the provided content is sent as-is, supports the [FirstName], [LastName], [Company], [JobTitle], [SenderFirstName], [SenderName], [SenderFullName] and [SenderCompany] variables; an empty value removes the tag). In 'same' mode, message is required (max 1900 chars for LinkedIn messages) and email steps also require subject. invitationNote requires note (max 180 chars, 280 with a Sales Navigator seat); voiceMessage requires url to the audio file; likePosts accepts numberOfPosts (1-3, default 1); visitProfile and invitation need no extra fields.","example":[{"type":"invitationNote","note":"Hi [FirstName], let’s connect!","delayAfterLastStep":1},{"type":"message","messageMode":"ai","delayAfterLastStep":2},{"type":"email","subject":"Quick question","message":"Hi [FirstName], ...","delayAfterLastStep":3}]},"listIds":{"description":"IDs of the lists used as lead source. At least one list is required.","example":[1],"type":"array","items":{"type":"string"}},"linkedinSeatId":{"type":"number","description":"LinkedIn seat sending the LinkedIn steps. Required when the campaign contains any LinkedIn step; not allowed on email-only campaigns.","example":1},"emailSeatIds":{"description":"Email seats sending the email steps. Required when the campaign contains email steps. Multiple seats (up to 100) are only allowed on email-only campaigns. Use GET /v1/email-seat to list your email seats and their IDs.","example":[1],"type":"array","items":{"type":"string"}},"active":{"type":"boolean","description":"Whether the campaign starts active. Once active, the campaign starts the outreach automatically on a daily basis; the launch hours can be configured in the LinkedIn seat settings.","example":true,"default":true},"language":{"type":"string","description":"Campaign language","example":"en-US"}},"required":["name","steps","listIds"]},"UpdateCampaignExternalDto":{"type":"object","properties":{"name":{"type":"string","description":"Campaign name","example":"Q1 Sales Campaign"},"listIds":{"description":"List IDs to associate with the campaign","example":[1,2,3],"type":"array","items":{"type":"string"}},"steps":{"type":"array","description":"Campaign steps. Must have the same length and step type order as the existing campaign; only message and messageMode are updated."}}},"ToggleCampaignStatusExternalDto":{"type":"object","properties":{"active":{"type":"boolean","description":"`true` to activate the campaign, `false` to deactivate it","example":true}},"required":["active"]},"CreateListDto":{"type":"object","properties":{"name":{"type":"string","description":"Name of the list","maxLength":255},"campaignId":{"type":"number","description":"Campaign ID associated with this list"}},"required":["name"]},"AgentVariableV2Dto":{"type":"object","properties":{"type":{"type":"string","description":"Type of signal this variable configures","enum":["SEARCH_KEYWORD","SEARCH_KEYWORD_COMMENT","SEARCH_KEYWORD_LIKE","SEARCH_KEYWORD_POST","EVENT_KEYWORD","GROUP_KEYWORD","COMPETITOR_PAGE_URL","INFLUENCER_PAGE_URL","YOUR_COMPANY","RECENT_ACTIVITY","RECENTLY_CHANGED_JOB","RECENT_FUNDING_EVENT","VISITED_PROFILE","YOUR_PROFILE","YOUR_COMPANY_FOLLOWERS","JOB_SEARCH","HIRING","HIRING_SURGE","TECHNOLOGY","NEW_DECISION_MAKER","LOOKALIKE","WEBSITE_VISITOR"]},"value":{"type":"string","description":"Value of the variable; its meaning depends on the type (e.g. a search keyword for SEARCH_KEYWORD, a page URL for COMPETITOR_PAGE_URL)"},"options":{"type":"object","description":"Type-specific configuration object (e.g. JOB_SEARCH stores { job_type, geocode })"},"enabled":{"type":"boolean","description":"Whether the variable is enabled and will be used in runs"},"needsUserToken":{"type":"boolean","description":"Whether running this variable requires a connected LinkedIn account token"},"linkedinSeatId":{"type":"number","description":"ID of the LinkedIn seat used to run this variable (Only for variables of type VISITED_PROFILE, YOUR_COMPANY_FOLLOWERS)"}},"required":["type","value"]},"CreateAgentExternalDto":{"type":"object","properties":{"name":{"type":"string","description":"Name of the source agent","example":"SaaS CTOs - Europe"},"listId":{"type":"number","description":"ID of the list the agent imports its sourced leads into. A list is mandatory: create one first (POST /v1/list) and pass its ID here. The list must belong to the authenticated user.","example":123},"targetJobTitles":{"description":"Target job titles to search for","example":["CTO","VP of Engineering"],"type":"array","items":{"type":"string"}},"targetIndustries":{"description":"Target industries to search in","example":["Computer Software"],"type":"array","items":{"type":"string"}},"targetCompanySizes":{"type":"array","description":"Target company sizes","example":["11-50","51-200"],"items":{"type":"string","enum":["2-10","11-50","51-200","201-500","501-1000","1001-5000","5001-10000","10001+"]}},"targetLocations":{"description":"Target locations to search in","example":["France","Germany"],"type":"array","items":{"type":"string"}},"targetCompanyTypes":{"type":"array","description":"Target company types","example":["Private Company","Startup"],"items":{"type":"string","enum":["All company types","Private Company","Public Company","Startup","Non-profit","Government","Educational Institution","Other"]}},"agentType":{"type":"string","description":"Type of agent: \"autopilot\" (default), \"lookalike\" or \"website-visitor\".","enum":["autopilot","lookalike","website-visitor"],"default":"autopilot"},"variables":{"description":"Signal variables configuration (max 15). For agent type \"autopilot\" only. When provided, at least 4 variables are required. LOOKALIKE variables are not allowed, and JOB_SEARCH variables are not authorized for now.","type":"array","items":{"$ref":"#/components/schemas/AgentVariableV2Dto"}},"ignoredCompanies":{"description":"Companies to ignore in search results","type":"array","items":{"type":"string"}},"mandatoryKeywords":{"description":"Keywords that must be present in the profile","type":"array","items":{"type":"string"}},"excludeServiceProviders":{"type":"boolean","description":"Exclude service providers from results"},"skipIcpFilter":{"type":"boolean","description":"Skip ICP (Ideal Customer Profile) filter"},"includeOpenToWorkProfiles":{"type":"boolean","description":"Include open-to-work profiles in results"},"leadWaterfall":{"type":"boolean","description":"Enable smart lead finder mode, usually true"},"paused":{"type":"boolean","description":"Create the agent paused. Note: the agent is also created paused when the active agent limit is reached."},"minLeadScore":{"type":"number","description":"Minimum lead score threshold (0–1) - Usually 0.6 or 0.8"},"maxCreditUsage":{"type":"number","description":"Max number of credits per month (website visitor agent only)"}},"required":["name","listId","targetJobTitles","targetIndustries","targetCompanySizes","targetLocations","targetCompanyTypes"]},"AgentLog":{"type":"object","properties":{"id":{"type":"number","description":"Unique identifier of the agent log entry."},"leadsCreated":{"type":"number","description":"Number of leads imported into the user platform after the AI scoring & filtering pipeline. These are the qualified leads that ended up on the agent's list and become contacts of the linked outreach campaign."},"totalLeads":{"type":"number","description":"Total number of raw leads the agent found from this signal in this run, BEFORE any AI scoring or filtering. Always greater than or equal to leadsCreated."},"errorMessage":{"type":"string","description":"Error message if the run failed for this signal. Null when the run succeeded.","nullable":true},"agentType":{"type":"object","description":"Type of signal the agent ran (e.g. SEARCH_KEYWORD, COMPETITOR_PAGE_URL, RECENT_FUNDING_EVENT). Matches the agent's configured signal types.","nullable":true},"agentKeyword":{"type":"string","description":"The specific signal value used in this run — e.g. the keyword searched, the competitor page URL watched, or the lookalike seed. Lets you see which signal produced which leads.","nullable":true},"userId":{"type":"number","description":"Owner user ID."},"agentId":{"type":"number","description":"ID of the agent this log entry belongs to.","nullable":true},"createdAt":{"format":"date-time","type":"string","description":"When this log entry was recorded (ISO 8601)."}},"required":["id","leadsCreated","totalLeads","userId","createdAt"]},"UpdateAgentDto":{"type":"object","properties":{"name":{"type":"string","description":"Name of the agent"},"targetJobTitles":{"description":"Target job titles to search for","type":"array","items":{"type":"string"}},"targetIndustries":{"description":"Target industries to search in","type":"array","items":{"type":"string"}},"targetCompanySizes":{"description":"Target company sizes","type":"array","items":{"type":"string"}},"targetLocations":{"description":"Target locations to search in","type":"array","items":{"type":"string"}},"targetCompanyTypes":{"description":"Target company types","type":"array","items":{"type":"string"}},"additionalCriteria":{"type":"string","description":"Additional search criteria or instructions","deprecated":true,"maxLength":200},"agentType":{"type":"string","description":"Type of agent, only \"autopilot\" is accepted","enum":["autopilot"]},"variables":{"description":"Agent variables configuration (minimum 4 required)","type":"array","items":{"$ref":"#/components/schemas/AgentVariableV2Dto"}},"ignoredCompanies":{"description":"Companies to ignore in search results","type":"array","items":{"type":"string"}},"mandatoryKeywords":{"description":"Keywords that must be present in the profile","type":"array","items":{"type":"string"}},"listId":{"type":"number","description":"ID of the list to associate with this agent"},"excludeServiceProviders":{"type":"boolean","description":"Exclude service providers from results"},"skipIcpFilter":{"type":"boolean","description":"Skip ICP (Ideal Customer Profile) filter"},"includeOpenToWorkProfiles":{"type":"boolean","description":"Include open-to-work profiles in results"},"leadWaterfall":{"type":"boolean","description":"Enable lead waterfall mode"},"paused":{"type":"boolean","description":"Whether the agent is paused"},"minLeadScore":{"type":"number","description":"Minimum lead score threshold (0–1)"},"scriptInstalled":{"type":"boolean","description":"Whether the visitor tracking script is installed on the website (website-visitor agents)"},"maxCreditUsage":{"type":"number","description":"Max number of credits per month"}}},"LinkedinSeat":{"type":"object","properties":{}},"Unibox":{"type":"object","properties":{"id":{"type":"string","description":"Unique chat identifier (internal chat ID)"},"subject":{"type":"string","description":"Subject of the chat (sometimes empty)"},"lastMessage":{"type":"string","description":"Content of the latest message in the chat"},"lastMessageDate":{"format":"date-time","type":"string","description":"Date of the latest message in the chat"},"messages":{"description":"List of messages in the chat","type":"array","items":{"type":"object"}},"sender":{"type":"object","description":"Sender attendee information for the chat"},"seen":{"type":"boolean","description":"Whether the chat has been seen","default":false},"attendeeId":{"type":"string","description":"attendee ID of the conversation partner (internal)"},"accountId":{"type":"string","description":"Linkedin account ID associated with this chat (internal)"},"interested":{"type":"boolean","description":"Whether the contact is interested (sentiment analysis result)"},"tag":{"type":"string","description":"Sentiment / category tag for the chat"},"contactId":{"type":"number","description":"Linked Gojiberry contact ID, if matched"},"contact":{"description":"Associated Gojiberry contact","allOf":[{"$ref":"#/components/schemas/Contact"}]},"seatId":{"type":"number","description":"Gojiberry LinkedIn seat ID owning this chat"},"seat":{"description":"Associated LinkedIn seat","allOf":[{"$ref":"#/components/schemas/LinkedinSeat"}]},"userId":{"type":"number","description":"User ID who owns this chat"},"user":{"description":"Associated user account","allOf":[{"$ref":"#/components/schemas/Account"}]},"createdAt":{"format":"date-time","type":"string","description":"Creation timestamp"},"updatedAt":{"format":"date-time","type":"string","description":"Last update timestamp"}},"required":["id","seen","createdAt","updatedAt"]}}}}