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

# Batch Create Contact Entries

> Add entries to existing contact lists in batch (up to 100 items per request).

Unlike single-resource `POST` endpoints, the body wraps the payloads in
`items` and the response reports a per-item status.

Requires the `contact_lists:write` scope on the API key.



## OpenAPI

````yaml /openapi.json post /v1/contact-entries
openapi: 3.1.0
info:
  title: Topo Public API
  description: >-
    Public REST API for Topo. All requests require a bearer API key issued from
    Settings → Developers. See the **Guides** in the left sidebar for
    authentication and rate-limit details.
  version: 1.0.0
servers:
  - url: https://api.topo.io
    description: Production
security:
  - BearerApiKey: []
tags:
  - name: Authentication
    description: >-
      Every request to the Topo Public API is authenticated with a bearer API
      key.


      ```http

      Authorization: Bearer topo_live_4f8a9c2b...

      ```


      Requests without a valid bearer token receive `401 Unauthorized`. After
      repeated failures with the same token, further attempts are
      short-circuited with `429 Too Many Requests` (`data.window_seconds: 60`)
      so a stale or malformed key cannot be retried in a tight loop.


      ### Issuing keys


      API keys are issued from the dashboard under **Settings → Developers → API
      keys**. Each key is bound to a single organization — the workspace it was
      created in — and cannot be used to read or write data from any other
      organization.


      The raw key is shown **once** at creation and never displayed again. If
      the raw key is lost, revoke it and issue a new one.


      ### Scopes


      Keys carry a granular scope list of `resource:action` pairs. A request to
      an endpoint whose required scope is not on the key returns `403
      Forbidden`.


      v1 ships with the following scopes:


      - `account_exclusions:read`, `account_exclusions:write`

      - `account_lists:read`, `account_lists:write`

      - `accounts:read`, `accounts:write`

      - `activities:read`

      - `contact_exclusions:read`, `contact_exclusions:write`

      - `contact_lists:read`, `contact_lists:write`

      - `contacts:read`, `contacts:write`

      - `crm:write`

      - `events:read`, `events:write`

      - `messages:read`

      - `sequences:read`, `sequences:write`

      - `tasks:read`, `tasks:write`

      - `users:read`

      - `webhooks:read`, `webhooks:write`


      Each endpoint's required scope is listed in its description.


      ### Inspecting the current key


      `GET /v1/me` returns the principal behind the bearer token: the key id,
      its name, the organization id and name, the id of the user who issued the
      key (`null` when it was not issued by a person), the scope list, the key
      prefix, the last-used timestamp, and the creation date. Use it to confirm
      a key is wired up correctly before debugging endpoint-level errors.
  - name: Rate limits
    description: >-
      Requests are rate-limited per organization across three concurrent
      windows. The budget is shared by every API key issued for the
      organization.


      | Window | Default limit |

      |---|---|

      | Per second | 10 requests |

      | Per minute | 60 requests |

      | Per day | 10,000 requests |


      A request is accepted only if it fits inside **all three** windows.
      Exceeding any one of them returns `429`.


      ### Response headers


      Every response carries the state of all three windows. Each window is
      suffixed with its name — `second`, `minute`, or `day`:


      ```http

      X-RateLimit-Limit-second: 5

      X-RateLimit-Remaining-second: 2

      X-RateLimit-Reset-second: 1729435920

      X-RateLimit-Limit-minute: 60

      X-RateLimit-Remaining-minute: 13

      X-RateLimit-Reset-minute: 1729435932

      X-RateLimit-Limit-day: 1000

      X-RateLimit-Remaining-day: 842

      X-RateLimit-Reset-day: 1729519200

      ```


      | Header | Meaning |

      |---|---|

      | `X-RateLimit-Limit-{window}` | Capacity of that window |

      | `X-RateLimit-Remaining-{window}` | Requests left in that window before
      the next `429` |

      | `X-RateLimit-Reset-{window}` | Unix timestamp (seconds) at which that
      window is fully refilled |


      Each window is a token bucket that refills continuously, so
      `X-RateLimit-Remaining-{window}` recovers gradually under steady traffic —
      it does not snap back at a fixed boundary.


      ### When the limit is exceeded


      Requests over the budget return `429` with the standard error envelope and
      a `Retry-After` header. `data.window_seconds` identifies the tier that
      tripped (`1`, `60`, or `86400`):


      ```http

      Retry-After: 3

      ```


      ```json

      {
        "status_code": 429,
        "type": "RateLimitIssue",
        "message": "Rate limit exceeded",
        "data": {
          "limit": 60,
          "reset_at": 1729435932,
          "window_seconds": 60
        },
        "request_id": "req_01HX7K..."
      }

      ```


      `Retry-After` is the number of seconds until the next request is allowed
      and is the recommended signal for backoff. `X-RateLimit-Reset` marks when
      the window is fully refilled, which is later than `Retry-After` whenever
      the bucket has more than one slot.
  - name: Task lifecycle
    description: >-
      A task represents one action a rep is expected to take — reply to a
      message, review a new lead, make a call, or complete a standalone to-do.
      This guide covers the states a task moves through, and how the wording
      used in the Topo app maps onto the values returned by this API.


      ### Statuses


      | `status` | Meaning |

      |---|---|

      | `PENDING` | Waiting for the rep. This is where a task starts. |

      | `IN_PROGRESS` | A call task the rep has started but not yet logged. |

      | `COMPLETED` | The rep acted on the task — a reply was sent, a lead
      approved or refused, a call logged. |

      | `SKIPPED` | The rep dismissed the task without acting on it. Shown as
      **Archived** in the app. |


      `PENDING` and `IN_PROGRESS` are the actionable states. `COMPLETED` and
      `SKIPPED` are terminal, but both can be reversed with `POST
      /v1/tasks/{task_id}/reopen`.


      ### App wording vs. API wording


      The Topo app calls the dismiss action **Archive**. On this API the same
      operation is called *skip*. They are the same thing — only the label
      differs:


      | In the Topo app | On this API |

      |---|---|

      | The **Archive** button | `POST /v1/tasks/{task_id}/skip` |

      | The **Archived** badge | `status: "SKIPPED"` |

      | "Archived 2 hours ago" | `skipped_at` |

      | — | webhook event `task.skipped` |


      These names are part of the frozen `/v1` contract and will not change
      within this major version. If you are mapping API data back to what a user
      sees in the app, treat `SKIPPED` and `skipped_at` as "archived".


      ### Ending a task


      | Action | Endpoint | Result |

      |---|---|---|

      | Act on it | `POST /v1/tasks/{task_id}/execute` | `COMPLETED` +
      `completed_at`, and the task's own side effects (message sent, lead
      enrolled or refused, call logged) |

      | Mark done without acting | `POST /v1/tasks/{task_id}/complete` |
      `COMPLETED` + `completed_at` |

      | Dismiss it (archive) | `POST /v1/tasks/{task_id}/skip` | `SKIPPED` +
      `skipped_at` |


      `execute` is the only one of the three that changes anything beyond the
      task itself. `complete` and `skip` record an outcome and nothing more.


      Both terminal states emit a webhook if you are subscribed:
      `task.completed` and `task.skipped` respectively.


      ### Reopening


      `POST /v1/tasks/{task_id}/reopen` returns a `COMPLETED` or `SKIPPED` task
      to `PENDING`. It clears `completed_at` and `skipped_at`, and sets
      `reopened_at`. A task that is already actionable cannot be reopened.


      `reopen_reason` distinguishes an automatic reopen from a manual one.
      `REMINDER_RULE` means Topo brought the task back on its own because a sent
      message went unanswered; `null` means a person reopened it.


      ### Snoozing


      Deferring a task ("Snooze") does **not** change its status. The task stays
      `PENDING` and drops out of the active queue until the chosen time passes,
      then reappears on its own.


      Use `POST /v1/tasks/{task_id}/snooze` with `{ "snoozed_until": "<ISO-8601
      timestamp>" }` to defer a task. The `snoozed_until` timestamp is also
      returned on task objects in `GET /v1/tasks` and `GET /v1/tasks/{task_id}`.
  - name: me
    description: >-
      Identity of the API key in use: the workspace it belongs to and its
      scopes.
  - name: search
    description: >-
      Find records by name across kinds and load any match back. A search result
      carries a typed id (`<type>:<uuid>`) that `GET /v1/fetch` resolves into
      the full record, so a name is enough to reach a lead, an account, a
      sequence template or a task without knowing which endpoint owns it.
  - name: users
    description: List users in the authenticated organization.
  - name: contacts
    description: Manage contact lists, list entries, and contact-level exclusions.
  - name: accounts
    description: Manage account lists, list entries, and account-level exclusions.
  - name: sequences
    description: Manage outbound sequences and inspect their execution state.
  - name: sequence-templates
    description: Manage reusable sequence templates that seed new sequences.
  - name: activities
    description: >-
      Read the immutable event log produced by outbound sequences (sent, opened,
      replied, meeting booked, etc.).
  - name: events
    description: >-
      Ingest first-party events (product usage, website activity, form
      submissions) and read them back to verify ingestion and resolution. Every
      well-formed event is accepted (HTTP 202) and stored. Subject correlation
      to contacts and accounts is asynchronous and best-effort. Replays with the
      same `external_event_id` or identical event content are deduplicated.
  - name: webhooks
    description: Manage webhook subscriptions to receive outbound event notifications.
  - name: tasks
    description: List and inspect tasks in the workspace.
  - name: messages
    description: Read email and LinkedIn messages exchanged in outreach sequences.
paths:
  /v1/contact-entries:
    post:
      tags:
        - contacts
      summary: Batch Create Contact Entries
      description: >-
        Add entries to existing contact lists in batch (up to 100 items per
        request).


        Unlike single-resource `POST` endpoints, the body wraps the payloads in

        `items` and the response reports a per-item status.


        Requires the `contact_lists:write` scope on the API key.
      operationId: create_contact_entries_v1_contact_entries_post
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateContactEntriesInput'
        description: Batch Create Contact Entries payload
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchResponse_ContactEntryOutput_'
components:
  schemas:
    CreateContactEntriesInput:
      properties:
        items:
          items:
            $ref: '#/components/schemas/ContactEntryInput'
          type: array
          maxItems: 100
          minItems: 1
          title: Items
          description: Contact entries to create.
      additionalProperties: false
      type: object
      required:
        - items
      title: CreateContactEntriesInput
    BatchResponse_ContactEntryOutput_:
      properties:
        items:
          items:
            $ref: '#/components/schemas/BatchItemResult_ContactEntryOutput_'
          type: array
          title: Items
        summary:
          $ref: '#/components/schemas/BatchSummary'
      type: object
      required:
        - items
        - summary
      title: BatchResponse[ContactEntryOutput]
    ContactEntryInput:
      properties:
        contact_list_id:
          type: string
          format: uuid
          title: Contact List Id
          description: ID of the contact list to add this entry to.
        first_name:
          anyOf:
            - type: string
            - type: 'null'
          title: First Name
          description: First name.
        last_name:
          anyOf:
            - type: string
            - type: 'null'
          title: Last Name
          description: Last name.
        email:
          anyOf:
            - type: string
            - type: 'null'
          title: Email
          description: Email. At least one of `email` or `linkedin_url` is required.
        linkedin_url:
          anyOf:
            - type: string
            - type: 'null'
          title: Linkedin Url
          description: >-
            LinkedIn profile URL. At least one of `email` or `linkedin_url` is
            required.
        company_name:
          anyOf:
            - type: string
            - type: 'null'
          title: Company Name
          description: Employer name.
        company_domain:
          anyOf:
            - type: string
            - type: 'null'
          title: Company Domain
          description: Employer's primary domain.
        job_title:
          anyOf:
            - type: string
            - type: 'null'
          title: Job Title
          description: Job title.
        phone:
          anyOf:
            - type: string
            - type: 'null'
          title: Phone
          description: Phone number.
        external_contact_id:
          anyOf:
            - type: string
            - type: 'null'
          title: External Contact Id
          description: External CRM contact identifier for CRM-backed list entries.
      additionalProperties: false
      type: object
      required:
        - contact_list_id
      title: ContactEntryInput
    BatchItemResult_ContactEntryOutput_:
      properties:
        index:
          type: integer
          title: Index
        status:
          $ref: '#/components/schemas/BatchItemStatus'
        resource:
          anyOf:
            - $ref: '#/components/schemas/ContactEntryOutput'
            - type: 'null'
        error:
          anyOf:
            - $ref: '#/components/schemas/BatchItemError'
            - type: 'null'
      type: object
      required:
        - index
        - status
      title: BatchItemResult[ContactEntryOutput]
    BatchSummary:
      properties:
        total:
          type: integer
          title: Total
        succeeded:
          type: integer
          title: Succeeded
        failed:
          type: integer
          title: Failed
      type: object
      required:
        - total
        - succeeded
        - failed
      title: BatchSummary
    BatchItemStatus:
      type: string
      enum:
        - CREATED
        - UPDATED
        - FAILED
      title: BatchItemStatus
    ContactEntryOutput:
      properties:
        id:
          type: string
          format: uuid
          title: Id
          description: Unique identifier.
        contact_list_id:
          type: string
          format: uuid
          title: Contact List Id
          description: ID of the list this entry belongs to.
        contact_id:
          anyOf:
            - type: string
              format: uuid
            - type: 'null'
          title: Contact Id
          description: ID of the contact this entry resolved to, once matched.
        external_contact_id:
          anyOf:
            - type: string
            - type: 'null'
          title: External Contact Id
          description: External CRM contact identifier when supplied at import.
        first_name:
          anyOf:
            - type: string
            - type: 'null'
          title: First Name
          description: First name.
        last_name:
          anyOf:
            - type: string
            - type: 'null'
          title: Last Name
          description: Last name.
        email:
          anyOf:
            - type: string
            - type: 'null'
          title: Email
          description: Email.
        linkedin_url:
          anyOf:
            - type: string
            - type: 'null'
          title: Linkedin Url
          description: LinkedIn URL.
        company_name:
          anyOf:
            - type: string
            - type: 'null'
          title: Company Name
          description: Employer name.
        company_domain:
          anyOf:
            - type: string
            - type: 'null'
          title: Company Domain
          description: Employer domain.
        job_title:
          anyOf:
            - type: string
            - type: 'null'
          title: Job Title
          description: Job title.
        phone:
          anyOf:
            - type: string
            - type: 'null'
          title: Phone
          description: Phone number.
        description:
          anyOf:
            - type: string
            - type: 'null'
          title: Description
          description: Free-form notes.
        location:
          anyOf:
            - type: string
            - type: 'null'
          title: Location
          description: Person's location.
        created_at:
          type: string
          format: date-time
          title: Created At
          description: When the entry was created.
        updated_at:
          type: string
          format: date-time
          title: Updated At
          description: When the entry last changed.
      type: object
      required:
        - id
        - contact_list_id
        - contact_id
        - external_contact_id
        - first_name
        - last_name
        - email
        - linkedin_url
        - company_name
        - company_domain
        - job_title
        - phone
        - description
        - location
        - created_at
        - updated_at
      title: ContactEntryOutput
    BatchItemError:
      properties:
        type:
          type: string
          title: Type
        message:
          type: string
          title: Message
      type: object
      required:
        - type
        - message
      title: BatchItemError
  securitySchemes:
    BearerApiKey:
      type: http
      scheme: bearer
      bearerFormat: topo_live_<64hex>
      description: Bearer API key issued from Settings → Developers.

````