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

# Start a lead-search run

> Finds people by criteria — job title, location, company, industry, headcount, seniority, keywords — with no target person known in advance. Creates a **background run** that delivers up to `count` directory-style results (name, title, company, location, profile URL — never contact data) and returns immediately with the run handle and your post-reservation balance; poll `GET /api/v1/lead-search/runs/{runId}` for status and results. Set `continueFromRunId` to continue a finished run ("load more" or retry) — already-delivered leads are never re-delivered or re-billed. One active run per workspace at a time. Feed a result’s `linkedinUrl` into `POST /api/v1/find` (linkedin mode) to get a verified email. Rate limited per workspace. **Breaking change from the earlier synchronous contract**: the old one-batch `page` request/response is gone.

#### Billing

- per delivered lead, debited once when the run finalizes — an empty run is free, and a continuation run never re-bills previously delivered leads; `unitCost × count` is reserved up front and the unused remainder is released at finalize.

The current cost is shown in the app under Settings → Billing and in the live `GET /api/v1/openapi.json`. Calls that end in a system or provider error (HTTP 500) or are rate limited (HTTP 429) are not billed. The `charged` and `pointsCharged` fields of the response state what this call actually consumed.



## OpenAPI

````yaml /openapi.json post /api/v1/lead-search
openapi: 3.1.0
info:
  title: Loice API
  version: v1
  description: >-
    The Loice API finds and verifies professional contact data. Every capability
    is exposed here, key-authenticated and rate-limited; the web app is a client
    of the same platform.


    **Authentication** — pass your workspace API key on every request (except
    the API description) as `Authorization: Bearer loice_sk_…`. Keys are created
    under Settings → API Keys.


    **Billing** — operations are billed in points against your workspace
    balance. Each operation below states when the charge applies. The current
    cost of every operation is shown in the app under [Settings →
    Billing](https://app.loice.ai/settings/account?tab=billing) and in the live
    API description, `GET /api/v1/openapi.json`. Calls that fail because of a
    system or provider error (timeouts, upstream failures, our defects) are not
    billed, and rate-limited calls are rejected before any billable work. Check
    headroom with `GET /api/v1/points`. Where an operation documents an
    `Idempotency-Key` header, send one to retry safely: a repeated request
    returns the original outcome and is not charged again.


    **Errors** — every non-200 response has the shape `{ "error": { "message":
    "…" } }`. Operations whose error responses document an `error.code` also
    return that machine-readable code beside the message; branch on the code,
    not the text.
servers:
  - url: https://app.loice.ai
security: []
paths:
  /api/v1/lead-search:
    post:
      summary: Start a lead-search run
      description: >-
        Finds people by criteria — job title, location, company, industry,
        headcount, seniority, keywords — with no target person known in advance.
        Creates a **background run** that delivers up to `count` directory-style
        results (name, title, company, location, profile URL — never contact
        data) and returns immediately with the run handle and your
        post-reservation balance; poll `GET /api/v1/lead-search/runs/{runId}`
        for status and results. Set `continueFromRunId` to continue a finished
        run ("load more" or retry) — already-delivered leads are never
        re-delivered or re-billed. One active run per workspace at a time. Feed
        a result’s `linkedinUrl` into `POST /api/v1/find` (linkedin mode) to get
        a verified email. Rate limited per workspace. **Breaking change from the
        earlier synchronous contract**: the old one-batch `page`
        request/response is gone.


        #### Billing


        - per delivered lead, debited once when the run finalizes — an empty run
        is free, and a continuation run never re-bills previously delivered
        leads; `unitCost × count` is reserved up front and the unused remainder
        is released at finalize.


        The current cost is shown in the app under Settings → Billing and in the
        live `GET /api/v1/openapi.json`. Calls that end in a system or provider
        error (HTTP 500) or are rate limited (HTTP 429) are not billed. The
        `charged` and `pointsCharged` fields of the response state what this
        call actually consumed.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                filters:
                  type: object
                  properties:
                    people:
                      type: array
                      items:
                        type: string
                        maxLength: 200
                      maxItems: 20
                      description: Free-text hints about the people sought.
                    jobTitle:
                      type: array
                      items:
                        type: string
                        maxLength: 200
                      maxItems: 20
                      description: Current job titles, e.g. "Head of Sales".
                    company:
                      type: array
                      items:
                        type: string
                        maxLength: 200
                      maxItems: 20
                      description: >-
                        Company names. Applied as a keyword plus a server-side
                        match on each result’s stated employer.
                    location:
                      type: array
                      items:
                        type: string
                        maxLength: 200
                      maxItems: 20
                      description: Locations, e.g. "Germany" or "Berlin".
                    keywords:
                      type: array
                      items:
                        type: string
                        maxLength: 200
                      maxItems: 20
                      description: Free keywords (LinkedIn search operators work).
                    headcount:
                      type: array
                      items:
                        type: string
                      maxItems: 20
                      description: >-
                        Company headcount buckets: Self-employed, 1-10, 11-50,
                        51-200, 201-500, 501-1000, 1001-5000, 5001-10000,
                        10001+.
                    industry:
                      type: array
                      items:
                        type: string
                      maxItems: 20
                      description: >-
                        LinkedIn industry labels from the full 434-entry
                        taxonomy, e.g. "Software Development" or "Bars, Taverns,
                        and Nightclubs". The original short labels (Technology,
                        Software, SaaS, Finance, Healthcare, E-commerce,
                        Manufacturing) remain accepted aliases.
                    yearsExperience:
                      type: array
                      items:
                        type: string
                      maxItems: 20
                      description: Years of experience buckets, e.g. "3-5 years".
                    seniorityLevel:
                      type: array
                      items:
                        type: string
                      maxItems: 20
                      description: >-
                        Seniority levels: In Training, Entry Level, Senior,
                        Strategic, Entry Level Manager, Experienced Manager,
                        Director, Vice President, CXO, Owner / Partner.
                    function:
                      type: array
                      items:
                        type: string
                      maxItems: 20
                      description: >-
                        Job functions (all 26 LinkedIn functions), e.g.
                        "Engineering", "Sales" or "Information Technology".
                  additionalProperties: false
                  description: >-
                    At least one non-empty filter is required — unless
                    `continueFromRunId` is set, in which case filters may be
                    omitted (the continuation inherits the prior run’s criteria;
                    submitted filters must match them).
                count:
                  type: integer
                  minimum: 1
                  maximum: 10000
                  description: >-
                    How many leads to deliver (default 25, maximum 10000).
                    Billing is per lead actually delivered, never per lead
                    requested. On a continuation this means additional leads
                    beyond the prior run.
                continueFromRunId:
                  type: string
                  description: >-
                    Id of a finished run to continue ("load more" / retry after
                    a partial failure). The new run inherits its criteria,
                    resumes where it stopped, and never re-delivers or re-bills
                    leads the prior run already delivered.
                page:
                  type: integer
                  minimum: 1
                  maximum: 100
                  description: >-
                    Continuation only: fetch exactly this source page instead of
                    resuming sequentially — the on-demand fetch behind
                    as-if-local pagination. Leads the chain already delivered
                    are never re-billed.
              required:
                - filters
            example:
              filters:
                jobTitle:
                  - Head of Sales
                location:
                  - Germany
                headcount:
                  - 51-200
              count: 500
      responses:
        '200':
          description: >-
            Run created (poll `GET /api/v1/lead-search/runs/{runId}`), or
            refused uncharged with `reason: "insufficient-credits"` when the
            balance cannot cover the worst case — inspect `run` and `reason`.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      run:
                        type: object
                        properties:
                          id:
                            type: string
                            description: >-
                              Run id — poll it via `GET
                              /api/v1/lead-search/runs/{runId}`.
                          kind:
                            type: string
                            enum:
                              - criteria
                              - employees
                            description: >-
                              Always `criteria` on v1 (a criteria search).
                              Employee Finder runs are not part of the public
                              API and answer 404.
                          lookupMode:
                            type:
                              - string
                              - 'null'
                            enum:
                              - domain
                              - linkedin
                            description: >-
                              Employee Finder runs only; null for a criteria
                              run.
                          lookupInput:
                            type:
                              - string
                              - 'null'
                            description: >-
                              Employee Finder runs only; null for a criteria
                              run.
                          status:
                            type: string
                            enum:
                              - queued
                              - processing
                              - cancelling
                              - completed
                              - cancelled
                              - failed
                            description: >-
                              Lifecycle state.
                              `queued`/`processing`/`cancelling` are active —
                              keep polling; `completed`/`cancelled`/`failed` are
                              terminal.
                          requestedCount:
                            type: integer
                            description: Leads requested for this run.
                          deliveredCount:
                            type: integer
                            description: >-
                              Distinct leads delivered so far — the only number
                              billed.
                          sourceTotalElements:
                            type:
                              - integer
                              - 'null'
                            description: >-
                              Total results the source reports for these
                              criteria (set from the first fetched page); the
                              browsable window is capped at 100 pages.
                          sourceTotalPages:
                            type:
                              - integer
                              - 'null'
                            description: >-
                              Source-reported page count (capped at 100 by the
                              source).
                          sourceExhausted:
                            type: boolean
                            description: >-
                              True when the source ran out of matches before
                              `requestedCount`.
                          continuedFromId:
                            type:
                              - string
                              - 'null'
                            description: >-
                              Id of the run this one continues, when it is a
                              continuation.
                          unitCost:
                            type: integer
                            description: Points per delivered lead.
                          maxCost:
                            type: integer
                            description: >-
                              Worst-case cost disclosed up front: `unitCost ×
                              requestedCount` (reserved at creation; the unused
                              remainder frees at finalize).
                          creditsCharged:
                            type:
                              - integer
                              - 'null'
                            description: >-
                              Points actually debited — set at finalize to
                              `deliveredCount × unitCost`, null while the run is
                              active. An empty run charges 0.
                          error:
                            type:
                              - string
                              - 'null'
                            description: >-
                              Failure reason key when `status` is `failed` (e.g.
                              `search-failed`).
                          criteria:
                            type: object
                            additionalProperties:
                              type: array
                              items:
                                type: string
                            description: >-
                              Echo of the normalized search criteria the run
                              executes.
                          createdAt:
                            type: string
                            description: ISO timestamp the run was created.
                          startedAt:
                            type:
                              - string
                              - 'null'
                            description: ISO timestamp processing began, null while queued.
                          finishedAt:
                            type:
                              - string
                              - 'null'
                            description: ISO timestamp the run reached a terminal state.
                        required:
                          - id
                          - kind
                          - lookupMode
                          - lookupInput
                          - status
                          - requestedCount
                          - deliveredCount
                          - sourceTotalElements
                          - sourceTotalPages
                          - sourceExhausted
                          - continuedFromId
                          - unitCost
                          - maxCost
                          - creditsCharged
                          - error
                          - criteria
                          - createdAt
                          - startedAt
                          - finishedAt
                        description: The created run (absent on `insufficient-credits`).
                      balance:
                        type: object
                        properties:
                          available:
                            type: integer
                          reserved:
                            type: integer
                          granted:
                            type: integer
                        required:
                          - available
                          - reserved
                          - granted
                        description: >-
                          Post-reservation balance: `maxCost` moves to
                          `reserved` until finalize.
                      replay:
                        type: boolean
                        description: >-
                          True when this is the run an earlier call with the
                          same `Idempotency-Key` created (nothing new was held
                          or started). Absent on a refusal.
                      pointsReservedMax:
                        type: integer
                        description: >-
                          Points reserved for the run (`unitCost ×
                          requestedCount`) — the most it can cost; the actual
                          debit is per delivered lead.
                      reason:
                        type: string
                        enum:
                          - insufficient-credits
                        description: >-
                          Present when no run was created because the workspace
                          cannot cover the worst-case cost. Nothing is charged.
                    required:
                      - balance
                required:
                  - data
              example:
                data:
                  run:
                    id: cmf3x0v2b0001abcd
                    status: queued
                    requestedCount: 500
                    deliveredCount: 0
                    sourceExhausted: false
                    sourceTotalElements: 23000
                    sourceTotalPages: 100
                    continuedFromId: null
                    unitCost: 1
                    maxCost: 500
                    creditsCharged: null
                    error: null
                    criteria:
                      jobTitle:
                        - Head of Sales
                      location:
                        - Germany
                      headcount:
                        - 51-200
                    createdAt: '2026-09-01T09:00:00.000Z'
                    startedAt: null
                    finishedAt: null
                  balance:
                    available: 734
                    reserved: 500
                    granted: 1234
                  pointsReservedMax: 500
        '400':
          description: >-
            `validation_error`: the whole body is parsed — no filter, an
            unsupported filter key, an unknown label for a listed criterion,
            more than 20 values or one over 200 characters, a `count` outside
            1–10000 or not a number. `invalid_continuation`: `continueFromRunId`
            names no finished search of this workspace, or the submitted filters
            differ from its criteria. Never billed.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                        enum:
                          - validation_error
                          - unauthenticated
                          - not_found
                          - forbidden
                          - method_not_allowed
                          - idempotency_conflict
                          - idempotency_in_progress
                          - rate_limited
                          - internal_error
                          - run_already_active
                          - search_unavailable
                          - invalid_continuation
                        description: >-
                          Machine-readable error code. Branch on this, not on
                          `message`.
                      message:
                        type: string
                        description: Human-readable explanation.
                      retryAfterSeconds:
                        type: integer
                        description: >-
                          Present on a 429 only: seconds until the workspace may
                          call again (same value as the `Retry-After` header).
                    required:
                      - code
                      - message
                required:
                  - error
        '401':
          description: >-
            API key missing (`Missing API key. Pass it as: Authorization: Bearer
            <key>`), invalid (`Invalid API key`), or expired (`This API key has
            expired`).
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      message:
                        type: string
                    required:
                      - message
                required:
                  - error
        '405':
          description: Method not allowed — see the `Allow` response header.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      message:
                        type: string
                    required:
                      - message
                required:
                  - error
        '409':
          description: >-
            `run_already_active`: a run is already active for this workspace —
            wait for it or cancel it. `idempotency_conflict`: the
            `Idempotency-Key` was used for another request. Never billed.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                        enum:
                          - validation_error
                          - unauthenticated
                          - not_found
                          - forbidden
                          - method_not_allowed
                          - idempotency_conflict
                          - idempotency_in_progress
                          - rate_limited
                          - internal_error
                          - run_already_active
                          - search_unavailable
                          - invalid_continuation
                        description: >-
                          Machine-readable error code. Branch on this, not on
                          `message`.
                      message:
                        type: string
                        description: Human-readable explanation.
                      retryAfterSeconds:
                        type: integer
                        description: >-
                          Present on a 429 only: seconds until the workspace may
                          call again (same value as the `Retry-After` header).
                    required:
                      - code
                      - message
                required:
                  - error
        '429':
          description: >-
            Rate limit exceeded for this workspace. Retry after the number of
            seconds in the `Retry-After` header (60). Rate-limited calls are
            never billed.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      message:
                        type: string
                    required:
                      - message
                required:
                  - error
        '500':
          description: '`internal_error`: failed on our side. Never billed.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                        enum:
                          - validation_error
                          - unauthenticated
                          - not_found
                          - forbidden
                          - method_not_allowed
                          - idempotency_conflict
                          - idempotency_in_progress
                          - rate_limited
                          - internal_error
                          - run_already_active
                          - search_unavailable
                          - invalid_continuation
                        description: >-
                          Machine-readable error code. Branch on this, not on
                          `message`.
                      message:
                        type: string
                        description: Human-readable explanation.
                      retryAfterSeconds:
                        type: integer
                        description: >-
                          Present on a 429 only: seconds until the workspace may
                          call again (same value as the `Retry-After` header).
                    required:
                      - code
                      - message
                required:
                  - error
        '503':
          description: >-
            `search_unavailable`: lead search is not configured right now. Never
            billed.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                        enum:
                          - validation_error
                          - unauthenticated
                          - not_found
                          - forbidden
                          - method_not_allowed
                          - idempotency_conflict
                          - idempotency_in_progress
                          - rate_limited
                          - internal_error
                          - run_already_active
                          - search_unavailable
                          - invalid_continuation
                        description: >-
                          Machine-readable error code. Branch on this, not on
                          `message`.
                      message:
                        type: string
                        description: Human-readable explanation.
                      retryAfterSeconds:
                        type: integer
                        description: >-
                          Present on a 429 only: seconds until the workspace may
                          call again (same value as the `Retry-After` header).
                    required:
                      - code
                      - message
                required:
                  - error
      security:
        - workspaceApiKey: []
components:
  securitySchemes:
    workspaceApiKey:
      type: http
      scheme: bearer
      description: >-
        Workspace API key (`loice_sk_` + 48 hex characters), created under
        Settings → API Keys. Sent as `Authorization: Bearer loice_sk_…`.
        Invalid, missing, or expired keys are rejected with 401 before any
        billable work happens.

````

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