> ## Documentation Index
> Fetch the complete documentation index at: https://developer.lemlist.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Log a Call

> Logs a call made outside lemlist, from an external dialer, with its status, note, recording and transcript.

A call logged here is a call like any other in lemlist: it shows in the contact's timeline, in the Calls table and in the call stats, it reaches the connected CRM (a Call in HubSpot, a completed Task with its Call Result in Salesforce, a call activity in Pipedrive, as set in **Settings → CRM → Activities**), and your outgoing webhooks fire for it.

The usual flow for a dialer:

1. Map your outcomes to the team's call statuses once, from [`GET /call-statuses`](https://developer.lemlist.com/api-reference/endpoints/calls/get-call-statuses).
2. When a call ends, log it with your own `externalId`, the rep's `userId`, the numbers (`from`, `to`), and the task it was made for (`taskId`, from `GET /tasks`) or the contact it was with.
3. When the recording or the transcript lands, send it with [`PATCH /calls/{id}`](https://developer.lemlist.com/api-reference/endpoints/calls/update-call).
4. Read the call back with [`GET /calls/{id}`](https://developer.lemlist.com/api-reference/endpoints/calls/get-call) when you need its state, `hasRecording` first.

Retry on any network error: the same `externalId` is never logged twice.

Send your dialer's name in `provider` (for example `trellus`) on every call: lemlist keeps it to count calls by dialer, while `phoneProvider` stays `api`.

## What the status does

The automations a call runs come from the status the team configured, not from the status key. lemlist's default statuses, `connected-positive` included, only count the call as answered: a status that marks the lead interested or pauses its campaigns is one the team set up so in **Settings → Calls**. Read the `callPositive`, `callNegative` and `pausesCampaigns` flags of each key from `GET /call-statuses` before mapping your outcomes, and expect no effect a flag does not announce.

## Task or lead

With a `taskId`, the call completes that task and the sequence moves on, as when a rep marks it done from the dialer. To log a call on a lead without touching its tasks, send `leadId` (or `contactId`) instead: the call lands in the timeline and the CRM, the task stays open.

## The numbers

`from` and `to` take what your telephony reported. A value that reads as a phone number is standardized to E.164 as the contact panel does: its own country code first, else the team's billing country for a national number. Anything else is kept as sent and never refuses the call: an anonymous caller, an extension, a test number that is no real number such as `+15555555555`. Only an E.164 finds a contact when `to` (or `from` on an inbound call) is the only reference you send.

## What the answer guarantees

A `201` (or a `200` on a replay) means the call is stored. Everything that follows runs right after it and is alerted to lemlist when it fails, never answered to you: the push to the CRM, the completion of the task, the status automations, the copy of the recording, the storage of the transcript. A retry answers the stored call and runs none of them again.

What you can observe and repair from the API:

* `hasRecording` stays `false` when the recording could not be fetched (an expired or protected URL): send the `PATCH` again with a reachable URL.
* The note and the status can be sent again with a `PATCH`; both reach the CRM call.
* A call missing from the CRM is on lemlist's side: lemlist is alerted and looks at it.


## OpenAPI

````yaml post /calls
openapi: 3.0.0
info:
  title: lemlist API
  version: 1.0.0
  description: >-
    Welcome to the lemlist Developer Documentation.


    lemlist is very customizable and open. You'll find on this page all the API
    and integration you can do with lemlist.


    # Rate Limit


    lemlist's API rate limits requests in order to prevent abuse and overload of
    our services.  

    Rate limits are applied on all routes and per API key performing the
    request.  

    The rate limits are **20** requests per **2** seconds.  

    The response provides any information you may need about it:


    | Header | Description |

    | --- | --- |

    | Retry-After | The number of seconds in which you can retry |

    | X-RateLimit-Limit | The maximum requests in that time |

    | X-RateLimit-Remaining | The number of remaining requests you can make |

    | X-RateLimit-Reset | The date when the rate limit will reset |


    _Example of values for the rate limit headers_


    ``` json

    {
        "Retry-After": 2,
        "X-RateLimit-Limit": 20,
        "X-RateLimit-Remaining": 7,
        "X-RateLimit-Reset" : "Tue Feb 16 2021 09:02:42 GMT+0100 (Central European Standard Time)"
    }

     ```

    # Definitions


    ## Team


    A team is the entity of lemlist that can handle users and billing.


    ## Credits


    Credits are the coins a team uses to enrich emails, LinkedIn URLs, etc. via
    the enrich route. Each enrichment feature needs a certain amount of credits
    to run.


    ## User


    You use a user account to connect to lemlist and send messages via the
    connected emails or LinkedIn account.


    ## Campaign


    A campaign is the entity to automate outreach. A campaign has multiple
    sequences composed of steps.


    ## Lead


    A lead is a person that you try to contact via a campaign.


    ## Activity


    An activity is the history of all the steps.


    ## Unsubscribe


    An unsubscribe occurs when a person decides they don't want to receive
    emails from you anymore.


    # Authentication


    All API routes use the dedicated subdomain `api.lemlist.com`.


    lemlist uses API keys to allow access to the API. You can get your lemlist
    API key at our [integration
    page](https://app.lemlist.com/settings/integrations).


    You need to add the `Authorization` header using the `Basic` authentication
    type. `login:password` **where the login is always empty and the password is
    the API key**.


    ⚠️ **Don't forget to add the semicolon (**`:`**) before your API key in curl
    command.**


    > To authorize, use this code: 
      

    ``` shell

    curl https://api.lemlist.com/api/team \
      --user ":YourApiKey"

     ```

    **Make sure to replace** **`YourApiKey`** **with your API key.**


    # Give feedback


    If you want to report a bug, ask for data, or share with us a use case,
    please fill this [form](https://lemlist.typeform.com/to/mfVlkyGf). It will
    help us centralize your needs!
servers:
  - url: https://api.lemlist.com/api
security:
  - basicAuth: []
tags:
  - name: Enrichment Providers
    description: >-
      The data providers lemlist can call to find an email or a phone.


      Internal providers are paid with lemlist credits and always available.
      External providers run on your own account: connect one by saving its API
      key, and it becomes available to your waterfalls. `GET
      /enrichments/providers` is the catalog of provider ids accepted by the
      waterfall endpoints.
  - name: Enrichment Waterfalls
    description: >-
      Control which data providers lemlist calls when enriching a contact, and
      in which order.


      A waterfall is an ordered list of providers for one enrichment `type`
      (`email` or `phone`). Providers are tried one after the other until one
      returns a result.


      Every team has exactly one default waterfall per type (`isDefault: true`).
      While its `editor` is `lemlist` it follows the lemlist behaviour:
      providers you connected with your own API key are tried first, then
      internal providers in a random order. Once a team member edits its
      provider list, `editor` becomes that user's id and the list is followed as
      is; resetting it hands it back to `lemlist`.


      Custom waterfalls (`isDefault: false`) carry `conditions` on the contact
      being enriched, keyed by contact property; every key present must match.
      When a contact is enriched, lemlist runs a single waterfall: the custom
      waterfall of the requested type whose conditions match the contact, or the
      default waterfall when none does. If several match, the one with the most
      condition keys wins, then the most recently updated. Only that waterfall
      runs; when it finds nothing, the enrichment ends without falling back to
      another one.
paths:
  /calls:
    post:
      tags:
        - Calls
      summary: Log a Call
      description: >-
        Logs a call made outside lemlist, as an external dialer reports it. The
        call lands in the contact's timeline, the Calls table, the call stats
        and the connected CRM exactly like a call made with the lemlist dialer:
        a Call in HubSpot, a completed Task with its Call Result in Salesforce,
        a call activity in Pipedrive, following the **Calls** row of your
        activities mapping. Your outgoing webhooks fire as for any call.


        **Who the call was with.** Send one of `taskId`, `leadId`, `contactId`,
        `email` or `phone`. A `taskId` (a call task of a campaign, from `GET
        /tasks`) completes that task with the call, so the campaign moves on as
        when a rep marks it done; the task's contact and lead are the call's,
        and a `contactId` or `leadId` sent alongside must agree with them. A
        `leadId` attaches the call to that lead and its campaign. The other
        three attach it to the contact alone: the call is logged, but the
        campaign automations of a call status (mark as interested, stop the
        campaign, pauses) need a lead, so pass `taskId` or `leadId` when the
        call belongs to a campaign.


        **The numbers.** `from` is the caller's number and `to` the number
        called, international format: on an outbound call the rep's line and the
        contact's number, the reverse on an inbound one. They are written on the
        CRM call and shown in lemlist; the contact's side also finds the contact
        when none of the references above is sent.


        **The rep.** `userId` is the lemlist user who made or took the call, a
        member of the team (`GET /team`): the call is attributed to them in the
        stats and owned by them in the CRM.


        **The call status.** `callStatus` is a key of the team's statuses (`GET
        /call-statuses`). It runs the automations the team configured on that
        status, as when a rep picks it in the dialer. An unknown key is refused:
        lemlist never creates a status on the fly.


        **Retries.** `externalId` is your own id of the call. Sending the same
        `externalId` again answers `200` with the stored call, unchanged,
        instead of logging it twice: retry freely on a network error. Two
        requests racing on one `externalId` answer `409
        CALLS_API_LOG_IN_PROGRESS` to the second while the first writes the
        call, then `200` with the stored call; retry a `409` a moment later.


        **Recording and transcript.** `recordingUrl` is fetched in the
        background and copied to lemlist's storage, so the call plays in lemlist
        after your link expires: send an `https` URL reachable from the public
        internet; `hasRecording` turns `true` once stored, and the CRM call note
        is refreshed with the recording link then. A plain-text `transcript` is
        stored with the call, feeds the AI call summary and the CRM call note.
        Both usually arrive after the call: send them later through `PATCH
        /calls/{id}`.


        **Empty fields.** An optional field may be omitted or sent as `null`:
        both mean it is absent.


        **Plan.** Email Pro and above, as `GET /activities`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - externalId
                - userId
                - direction
                - startedAt
              properties:
                externalId:
                  type: string
                  maxLength: 128
                  pattern: ^[A-Za-z0-9][A-Za-z0-9._:-]*$
                  description: >-
                    Your own id of the call, unique in your dialer. Letters,
                    digits, `.`, `_`, `:` and `-`, 128 characters at most. The
                    idempotency key: the same id never logs twice, and `PATCH
                    /calls/{id}` accepts it.
                  example: trellus-8f2c1
                userId:
                  type: string
                  description: >-
                    The lemlist user who made or took the call (`usr_…`), a
                    member of the team.
                  example: usr_3Bv7GhT5nJk8Lm2Qx
                direction:
                  type: string
                  enum:
                    - inbound
                    - outbound
                  example: outbound
                from:
                  type: string
                  description: >-
                    The caller's number: the rep's line on an outbound call, the
                    contact's number on an inbound one. Standardized to E.164 as
                    the contact panel does (its own country code, else the
                    team's billing country for a national number) and kept as
                    sent otherwise: an anonymous caller, an extension or a test
                    number that is no real number (`+15555555555`) never refuses
                    the call. At most 32 characters.
                  example: '+33100000000'
                to:
                  type: string
                  description: >-
                    The number called: the contact's number on an outbound call,
                    the rep's line on an inbound one, standardized like `from`.
                    On an outbound call its E.164 also finds the contact when no
                    other reference is sent.
                  example: '+33612345678'
                startedAt:
                  type: string
                  format: date-time
                  description: >-
                    When the call started, ISO 8601. May be in the past; at most
                    5 minutes in the future.
                  example: '2026-10-21T09:30:00Z'
                duration:
                  type: integer
                  minimum: 0
                  maximum: 86400
                  description: Seconds.
                  example: 90
                callStatus:
                  type: string
                  description: >-
                    A key of the team's call statuses (`GET /call-statuses`).
                    Runs the automations that status carries, as the team
                    configured them: lemlist's default statuses,
                    `connected-positive` included, only count the call as
                    answered; marking the lead interested or pausing its
                    campaigns comes with a status the team set up so, read its
                    `callPositive` and `pausesCampaigns` flags.
                  example: connected-positive
                note:
                  type: string
                  maxLength: 10000
                  description: >-
                    The rep's note on the call: shown with the call in lemlist
                    and written in the CRM call note.
                  example: Asked for a demo next week.
                recordingUrl:
                  type: string
                  format: uri
                  maxLength: 2048
                  description: >-
                    An `https` URL of the recording, reachable from the public
                    internet. Fetched in the background. An empty string means
                    no recording.
                  example: https://recordings.dialer.example/8f2c1.mp3
                transcript:
                  type: string
                  maxLength: 200000
                  description: The transcript as plain text.
                  example: Hi Jane, this is Alex from lemlist…
                provider:
                  type: string
                  maxLength: 64
                  description: >-
                    Your dialer's name, the same on every call you log. lemlist
                    stores it lowercase, apart from `phoneProvider` (which stays
                    `api`), to count calls by dialer. An empty string means
                    none.
                  example: trellus
                taskId:
                  type: string
                  description: >-
                    A call task of a campaign (`opp_…`, from `GET /tasks`): the
                    call completes it and the sequence moves on. The task's
                    contact and lead are the call's. To log a call on a lead
                    without completing a task, send `leadId` or `contactId`
                    instead.
                  example: opp_9Xw4RtY7kLm2Pq6Zc
                leadId:
                  type: string
                  description: The lead the call was with (`lea_…`), with its campaign.
                  example: lea_2Hn8VbQ4wRz6Ym3Jk
                contactId:
                  type: string
                  description: The contact the call was with (`ctc_…`).
                  example: ctc_7Ks2PqW9mXb4Lc1Td
                email:
                  type: string
                  description: An email address of the contact the call was with.
                  example: jane@acme.io
                phone:
                  type: string
                  description: >-
                    A phone number the call was with: the contact holding it in
                    its phone field. Read as the contact panel reads a number:
                    its own country code, else the team's billing country for a
                    national number. A value that is no number names nobody
                    (`CALLS_API_CONTACT_NOT_FOUND`).
                  example: '+33612345678'
            example:
              externalId: trellus-8f2c1
              provider: trellus
              userId: usr_3Bv7GhT5nJk8Lm2Qx
              direction: outbound
              from: '+33100000000'
              to: '+33612345678'
              startedAt: '2026-10-21T09:30:00Z'
              duration: 90
              callStatus: demo-booked
              note: Asked for a demo next week.
              taskId: opp_9Xw4RtY7kLm2Pq6Zc
              transcript: Hi Jane, this is Alex from lemlist…
      responses:
        '200':
          description: >-
            A call with this `externalId` was already logged: here it is,
            unchanged.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: '#/components/schemas/Call'
        '201':
          description: The call is logged.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: '#/components/schemas/Call'
              example:
                success: true
                data:
                  _id: act_Z8gP3xYq2bKd9mLn7
                  externalId: trellus-8f2c1
                  contactId: ctc_7Ks2PqW9mXb4Lc1Td
                  leadId: lea_2Hn8VbQ4wRz6Ym3Jk
                  campaignId: cam_5Fd9LpX2cVt7Nq8Wb
                  taskId: opp_9Xw4RtY7kLm2Pq6Zc
                  userId: usr_3Bv7GhT5nJk8Lm2Qx
                  direction: outbound
                  from: '+33100000000'
                  to: '+33612345678'
                  startedAt: '2026-10-21T09:30:00.000Z'
                  endedAt: '2026-10-21T09:31:30.000Z'
                  duration: 90
                  callStatus: demo-booked
                  note: Asked for a demo next week.
                  phoneProvider: api
                  provider: trellus
                  hasRecording: false
                  hasTranscript: true
                  createdAt: '2026-10-21T09:30:00.000Z'
        '400':
          description: >-
            The body is refused. `error.code` names the case, `error.message`
            explains it.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                        description: >-
                          Error code: `VALIDATION_ERROR`,
                          `CALLS_API_USER_NOT_IN_TEAM`,
                          `CALLS_UNKNOWN_CALL_STATUS`,
                          `CALLS_API_NO_CONTACT_REFERENCE`,
                          `CALLS_API_TASK_NOT_A_CALL`,
                          `CALLS_API_PARTIES_MISMATCH`,
                          `CALLS_API_UNSAFE_RECORDING_URL`.
                      message:
                        type: string
              examples:
                validation-error:
                  summary: >-
                    A field is missing or malformed: the message names the field
                    and the shape expected
                  value:
                    success: false
                    error:
                      code: VALIDATION_ERROR
                      message: >-
                        Match error: Expected 1 to 128 characters among letters,
                        digits, ".", "_", ":" and "-", starting with a letter or
                        digit in field externalId
                calls-api-user-not-in-team:
                  summary: '`userId` is not a member of the team'
                  value:
                    success: false
                    error:
                      code: CALLS_API_USER_NOT_IN_TEAM
                      message: userId is not a member of this team.
                calls-unknown-call-status:
                  summary: '`callStatus` is not one of the team''s keys'
                  value:
                    success: false
                    error:
                      code: CALLS_UNKNOWN_CALL_STATUS
                      message: >-
                        Unknown call status for this team. Valid keys:
                        no-answer, left-voicemail, wrong-number, connected,
                        connected-positive, connected-negative,
                        connected-neutral, busy, gatekeeper, not-in-service
                calls-api-no-contact-reference:
                  summary: No `taskId`, `leadId`, `contactId`, `email` or `phone`
                  value:
                    success: false
                    error:
                      code: CALLS_API_NO_CONTACT_REFERENCE
                      message: >-
                        Send one of taskId, leadId, contactId, email or phone to
                        say who the call was with.
                calls-api-task-not-a-call:
                  summary: '`taskId` is not a call task'
                  value:
                    success: false
                    error:
                      code: CALLS_API_TASK_NOT_A_CALL
                      message: >-
                        taskId is not a call task: only a call task can be
                        completed by a call.
                calls-api-parties-mismatch:
                  summary: '`taskId`, `leadId` and `contactId` disagree'
                  value:
                    success: false
                    error:
                      code: CALLS_API_PARTIES_MISMATCH
                      message: >-
                        contactId, leadId and taskId must designate the same
                        contact: the task and the lead decide.
                calls-api-unsafe-recording-url:
                  summary: '`recordingUrl` is not a public https URL'
                  value:
                    success: false
                    error:
                      code: CALLS_API_UNSAFE_RECORDING_URL
                      message: >-
                        recordingUrl must be an https URL that resolves to a
                        public address.
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
              example:
                error: Unauthorized
        '402':
          description: >-
            The team's plan does not include the calls API (Email Pro and
            above).
        '404':
          description: The reference names nothing in the team.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                        description: >-
                          Error code: `CALLS_API_CONTACT_NOT_FOUND`,
                          `CALLS_API_LEAD_NOT_FOUND`,
                          `CALLS_API_TASK_NOT_FOUND`.
                      message:
                        type: string
              examples:
                calls-api-contact-not-found:
                  summary: No contact behind `contactId`, `email` or `phone`
                  value:
                    success: false
                    error:
                      code: CALLS_API_CONTACT_NOT_FOUND
                      message: No contact matches the given reference in this team.
                calls-api-lead-not-found:
                  summary: No lead behind `leadId`
                  value:
                    success: false
                    error:
                      code: CALLS_API_LEAD_NOT_FOUND
                      message: No lead matches leadId in this team.
                calls-api-task-not-found:
                  summary: No task behind `taskId`
                  value:
                    success: false
                    error:
                      code: CALLS_API_TASK_NOT_FOUND
                      message: No task matches taskId in this team.
        '405':
          description: >-
            Method not allowed: this endpoint only accepts POST. The body is the
            plain `{ "error": "Route bad method" }`.
          content:
            application/json:
              example:
                error: Route bad method
        '409':
          description: The call cannot be logged right now.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                        description: >-
                          Error code: `CALLS_API_TASK_ALREADY_CLOSED`,
                          `CALLS_API_LOG_IN_PROGRESS`.
                      message:
                        type: string
              examples:
                calls-api-task-already-closed:
                  summary: '`taskId` is already done or ignored'
                  value:
                    success: false
                    error:
                      code: CALLS_API_TASK_ALREADY_CLOSED
                      message: taskId is already done or ignored.
                calls-api-log-in-progress:
                  summary: The same `externalId` is being logged right now
                  value:
                    success: false
                    error:
                      code: CALLS_API_LOG_IN_PROGRESS
                      message: >-
                        A call with this externalId is being logged, retry in a
                        moment.
        '500':
          description: >-
            Unexpected failure, alerted to lemlist. For a `POST`, read the call
            back with `GET /calls/{externalId}` before retrying: once stored, a
            retry answers it as a replay (200).
          content:
            application/json:
              example:
                success: false
                error:
                  code: INTERNAL_ERROR
                  message: Internal error
components:
  schemas:
    Call:
      type: object
      description: A call logged through `POST /calls`, as the calls API returns it.
      properties:
        _id:
          type: string
          description: >-
            The call's activity id, which `PATCH /calls/{id}` and `DELETE
            /activities/{activityId}/recording-transcript` take.
          example: act_Z8gP3xYq2bKd9mLn7
        externalId:
          type: string
          nullable: true
          description: >-
            Your own id of the call, as sent; `null` on a call another dialer
            made.
          example: trellus-8f2c1
        contactId:
          type: string
          nullable: true
          example: ctc_7Ks2PqW9mXb4Lc1Td
        leadId:
          type: string
          nullable: true
          description: The lead the call attached to, when it belongs to a campaign.
          example: lea_2Hn8VbQ4wRz6Ym3Jk
        campaignId:
          type: string
          nullable: true
          example: cam_5Fd9LpX2cVt7Nq8Wb
        taskId:
          type: string
          nullable: true
          description: The call task the call completed.
          example: opp_9Xw4RtY7kLm2Pq6Zc
        userId:
          type: string
          nullable: true
          description: The rep the call is attributed to.
          example: usr_3Bv7GhT5nJk8Lm2Qx
        direction:
          type: string
          nullable: true
          enum:
            - inbound
            - outbound
          example: outbound
        from:
          type: string
          nullable: true
          description: >-
            The caller's number: the rep's line on an outbound call, the
            contact's on an inbound one.
          example: '+33100000000'
        to:
          type: string
          nullable: true
          description: The number called.
          example: '+33612345678'
        startedAt:
          type: string
          format: date-time
          nullable: true
          example: '2026-10-21T09:30:00.000Z'
        endedAt:
          type: string
          format: date-time
          nullable: true
          description: '`startedAt` plus `duration`; absent without a duration.'
          example: '2026-10-21T09:31:30.000Z'
        duration:
          type: integer
          nullable: true
          description: Seconds.
          example: 90
        callStatus:
          type: string
          nullable: true
          description: The key of the call status set on the call.
          example: connected-positive
        note:
          type: string
          nullable: true
          example: Asked for a demo next week.
        phoneProvider:
          type: string
          nullable: true
          description: >-
            `api` for a call logged through this API; the dialer that made it
            otherwise (`twilio` for the lemlist dialer, `aircall`, `ringover`)
            on a call read back by `_id`.
          example: api
        provider:
          type: string
          nullable: true
          description: >-
            The dialer's name a call was logged with through this API
            (`provider`), lowercase; `null` when none was sent and on any other
            call.
          example: trellus
        hasRecording:
          type: boolean
          description: >-
            True once the call has a recording in lemlist: a `recordingUrl` just
            sent is still on its way.
          example: false
        hasTranscript:
          type: boolean
          example: true
        createdAt:
          type: string
          format: date-time
          nullable: true
          description: 'The call''s time in lemlist: `startedAt`.'
          example: '2026-10-21T09:30:00.000Z'
  securitySchemes:
    basicAuth:
      type: http
      scheme: basic

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.