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

# Bulk search

> Submit many v3 searches as one resumable job.

Submit a list of searches once, then poll one job. Orbit queues the work and retries temporary processing failures automatically. You do not need to send or retry a separate HTTP request for every CSV row.

Requires `search:read`. Items with `include_profile: true` also require `profile:read` when submitting and reading results.

```bash theme={"dark"}
curl -X POST "https://api.orbitsearch.com/v3/search/bulk" \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "request_id": "contacts-import-2026-09-15",
    "items": [
      {
        "id": "csv-row-2",
        "signals": { "email": "person@example.com" },
        "limit": 1,
        "candidate_discovery": true,
        "candidate_discovery_limit": 1,
        "profile_depth": "partial",
        "include_profile": false
      }
    ]
  }'
```

Each item has a unique `id` and the fields from [Search](/api/search/search), except `request_id`. Put `request_id` on the whole job. Face searches use the individual Search endpoint.

## Submit and retry

A successful submission returns `202` with `job_id`, `request_id`, `status`, `counts`, `billing`, and links to status, results, and cancellation.

Keep the same `request_id` when retrying submission. With the same API key, normalized items, and search-session context, it returns the original job. Changing the items or adding, removing, or changing the signed-in session under that identity returns `409 bulk_search_idempotency_conflict`.

Limits:

* 1–5,000 items per job and an 8 MiB request body.
* At most 32 KiB per item.
* `request_id` and item `id` values must contain 1–128 characters.
* At most four unfinished jobs per billing account.

A full queue returns `409 bulk_search_queue_full` with `Retry-After`. Retry the same job identity after the indicated delay. An accepted job does not consume the individual Search request-rate allowance for every item.

Submission and cancellation each allow a burst of eight HTTP requests per API key, refilling at one request every two seconds. Status and results share a separate bucket with a burst of 30, refilling at five requests per second. Excessive requests return `429` with `Retry-After`; accepted jobs keep processing. These buckets are separate from individual Search.

When submitting with an authenticated search session, use the same signed-in person for status, results, cancellation, and idempotent replay. Directory access is checked again during processing and result reads.

## Track progress

```http theme={"dark"}
GET /v3/search/bulk/{job_id}
```

Poll about every three seconds. Job statuses are `queued`, `running`, `waiting_for_credits`, `needs_attention`, `completed`, `completed_with_errors`, and `canceled`.

`counts` includes `total`, `completed`, `failed`, `canceled`, and `pending`. Failed counts include items that completed with some errors. An empty successful search is completed; it is not a processing failure.

Temporary errors appear in `last_error` while the job retries. If the job enters `waiting_for_credits`, add credits to the same billing account. It resumes automatically.

After repeated recovery failures, `needs_attention` asks you to contact support with the job id. Automatic recovery continues about every five minutes. Started work and its reserved credits remain tracked until their outcome is established.

## Read results

```http theme={"dark"}
GET /v3/search/bulk/{job_id}/results?offset=0
```

Each page contains at most ten items and a `next_offset`. The offset follows the original input order, including unfinished items. Revisit unfinished pages after the job advances.

Each item includes:

* `id`: your original item id.
* `index`: its zero-based position.
* `status`: `queued`, `processing`, `completed`, `completed_with_errors`, `failed`, or `canceled`.
* `result`: a v3 search snapshot, or `null` until its results are ready to publish.

Use the result's status link to return to that item's bulk results page. Its internal search identifier does not create an independently pollable Search resource. Search confidence, candidate-discovery behavior, and profile depth have the same meanings as in an individual v3 search.

Results become available in small groups after their billing completes. A slow item can delay publication of the other items in its group.

## Billing and cancellation

Credits are reserved as groups of items begin processing. Orbit charges for the results under the v3 pricing rules and releases unused credits. Retrying a job or reading its saved results does not repeat its search charges. Different item ids represent separate searches, even if their inputs are identical.

```http theme={"dark"}
POST /v3/search/bulk/{job_id}/cancel
```

Cancellation skips undispatched items. Work that already started finishes reconciling and billing, so cancellation can remain pending while that work completes. Published results stay available.


## OpenAPI

````yaml openapi.json POST /v3/search/bulk
openapi: 3.1.0
info:
  title: Orbit API
  version: 3.0.0
  description: Search for people and enrich known Orbit profiles.
servers:
  - url: https://api.orbitsearch.com
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Search
    description: Find people and poll search results.
  - name: Enrich
    description: Read or enrich known Orbit profiles.
  - name: Watchers
    description: Watch a profile on a schedule and read what each run found.
  - name: Webhooks
    description: Register endpoints that receive signed event deliveries.
paths:
  /v3/search/bulk:
    post:
      tags:
        - Search
      summary: Submit a bulk search job
      description: >-
        Requires search:read and profile:read when any item includes profiles.
        Accepted items are queued and retry temporary execution failures
        automatically.
      operationId: submitBulkSearch
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BulkSearchRequest'
      responses:
        '202':
          description: Bulk search response.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkSearchJob'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          description: >-
            Bulk HTTP request rate exceeded. Honor Retry-After; accepted jobs
            continue processing.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds before retrying this HTTP request.
components:
  schemas:
    BulkSearchRequest:
      type: object
      required:
        - request_id
        - items
      properties:
        request_id:
          type: string
          minLength: 1
          maxLength: 128
        items:
          type: array
          minItems: 1
          maxItems: 5000
          items:
            $ref: '#/components/schemas/BulkSearchItemRequest'
    BulkSearchJob:
      type: object
      required:
        - job_id
        - request_id
        - status
        - cancel_requested
        - counts
        - billing
        - last_error
        - created_at
        - updated_at
        - links
      properties:
        job_id:
          type: string
          format: uuid
        request_id:
          type: string
        status:
          type: string
          enum:
            - queued
            - running
            - waiting_for_credits
            - needs_attention
            - completed
            - completed_with_errors
            - canceled
        cancel_requested:
          type: boolean
        counts:
          type: object
          properties:
            total:
              type: integer
              minimum: 0
            completed:
              type: integer
              minimum: 0
            failed:
              type: integer
              minimum: 0
            canceled:
              type: integer
              minimum: 0
            pending:
              type: integer
              minimum: 0
          required:
            - total
            - completed
            - failed
            - canceled
            - pending
        billing:
          type: object
          properties:
            consumed_credits:
              type: integer
              minimum: 0
            held_credits:
              type: integer
              minimum: 0
          required:
            - consumed_credits
            - held_credits
        last_error:
          type:
            - object
            - 'null'
          properties:
            code:
              type: string
            message:
              type: string
          required:
            - code
            - message
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        links:
          type: object
          properties:
            status:
              type: string
            results:
              type: string
            cancel:
              type: string
          required:
            - status
            - results
            - cancel
    BulkSearchItemRequest:
      type: object
      description: >-
        Provide `query`, `intent`, at least one identity signal, or a supported
        combination. When query and intent are both present, Orbit derives a
        base intent from query and overlays the caller-provided intent. Arrays,
        scalar values, and null replace derived values; nested objects merge
        field by field.
      additionalProperties: false
      properties:
        query:
          type: string
          minLength: 1
          description: >-
            A plain-English description of the people you want. When intent is
            also present, Orbit derives a base intent from this query before
            applying the caller-provided structured fields.
        intent:
          $ref: '#/components/schemas/StructuredIntent'
        signals:
          allOf:
            - $ref: '#/components/schemas/IdentitySignals'
            - not:
                required:
                  - face_source
          description: >-
            Identity signals for bulk Search. Face search uses the individual
            Search endpoint.
        candidate_discovery:
          type: boolean
          default: false
          description: >-
            Continue beyond known matches to find additional people. Address,
            email, and phone signals can each resolve to multiple associated
            people.
        candidate_discovery_limit:
          type: integer
          minimum: 1
          maximum: 50
          default: 10
          description: >-
            Maximum number of Candidate Discovery results. Valid only with
            candidate_discovery set to true. A lower value reduces downstream
            profile-generation fanout, although source discovery and clustering
            can still dominate initial discovery time.
        profile_depth:
          type: string
          enum:
            - partial
            - full
          default: partial
          description: The minimum profile depth required for each ready result.
        include_profile:
          type: boolean
          default: true
          description: >-
            Embed each readable result's profile with its identity fields,
            contact fields, and generated sections. Read GET
            /v3/enrich/{profile_id} for the image gallery and source links. This
            controls the response shape; profile_depth controls how deep each
            profile is built.
        limit:
          type: integer
          minimum: 1
          maximum: 100
          default: 20
          description: >-
            The maximum number of matches returned from the existing Orbit
            index. Candidate Discovery results append outside this limit, and
            profile upgrades update selected results in place.
        id:
          type: string
          minLength: 1
          maxLength: 128
      anyOf:
        - required:
            - query
        - required:
            - intent
        - required:
            - signals
      example:
        query: Machine learning engineers at Anthropic near San Francisco
        intent:
          experiences:
            - titleAnyOf:
                - Research Engineer
                - ML Engineer
              organization: Anthropic
              temporalScope: current
          geo:
            distance: 25 miles
        candidate_discovery: true
        profile_depth: partial
        include_profile: true
        limit: 10
        id: search-ml-sf-001
      required:
        - id
    ErrorResponse:
      type: object
      required:
        - status
        - error
      properties:
        status:
          type: string
          enum:
            - failed
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
            message:
              type: string
            requiredCredits:
              type: integer
              minimum: 0
              description: >-
                Credits required when the error is
                developer_api_credits_insufficient.
            remainingCredits:
              type: integer
              minimum: 0
              description: >-
                Available credits when the error is
                developer_api_credits_insufficient.
    StructuredIntent:
      type: object
      description: Advanced structured criteria. Do not include a `version` field.
      minProperties: 1
      additionalProperties: false
      properties:
        names:
          type: array
          items:
            type: string
            minLength: 1
        experiences:
          type: array
          items:
            $ref: '#/components/schemas/ExperienceIntent'
        semanticClauses:
          type: array
          items:
            $ref: '#/components/schemas/SemanticClause'
        schools:
          type: array
          items:
            $ref: '#/components/schemas/SchoolIntent'
        geo:
          oneOf:
            - $ref: '#/components/schemas/GeoIntent'
            - type: 'null'
        demographics:
          oneOf:
            - $ref: '#/components/schemas/DemographicsIntent'
            - type: 'null'
        personalization:
          oneOf:
            - $ref: '#/components/schemas/PersonalizationIntent'
            - type: 'null'
    IdentitySignals:
      type: object
      description: Known identity signals. Send only the signals you have.
      minProperties: 1
      additionalProperties: false
      properties:
        address:
          type: string
          minLength: 1
        email:
          type: string
          format: email
        phone:
          type: string
          minLength: 1
        linkedin_url:
          type: string
          format: uri
        urls:
          type: array
          items:
            type: string
            format: uri
        usernames:
          type: array
          items:
            type: string
            minLength: 1
    ExperienceIntent:
      type: object
      additionalProperties: false
      properties:
        title:
          type: string
        titleAnyOf:
          type: array
          items:
            type: string
        organization:
          type: string
        year:
          type: integer
        startYear:
          type: integer
        startYearLte:
          type: integer
        endYear:
          type: integer
        temporalScope:
          type: string
          enum:
            - current
            - historical
            - both
    SemanticClause:
      type: object
      additionalProperties: false
      properties:
        text:
          type: string
          minLength: 1
        anyOf:
          type: array
          minItems: 1
          items:
            type: string
            minLength: 1
      anyOf:
        - required:
            - text
        - required:
            - anyOf
    SchoolIntent:
      type: object
      additionalProperties: false
      properties:
        school:
          type: string
        schoolAnyOf:
          type: array
          items:
            type: string
        graduationYear:
          type: integer
        startYear:
          type: integer
        endYear:
          type: integer
        temporalScope:
          type: string
          enum:
            - current
            - historical
            - both
        relation:
          type: string
    GeoIntent:
      type: object
      additionalProperties: false
      properties:
        place:
          type: string
        isHistorical:
          type: boolean
        distance:
          type: string
    DemographicsIntent:
      type: object
      additionalProperties: false
      properties:
        ageRange:
          $ref: '#/components/schemas/IntegerRange'
        birthYearRange:
          $ref: '#/components/schemas/IntegerRange'
        gender:
          type: string
    PersonalizationIntent:
      type: object
      additionalProperties: false
      properties:
        network:
          type: object
          additionalProperties: false
          required:
            - scope
          properties:
            scope:
              type: string
              enum:
                - first_degree
        nearMe:
          type: object
          additionalProperties: false
          required:
            - distance
          properties:
            distance:
              type: string
    IntegerRange:
      type: object
      additionalProperties: false
      required:
        - min
        - max
      properties:
        min:
          type: integer
        max:
          type: integer
  responses:
    BadRequest:
      description: The request is not valid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Unauthorized:
      description: The API key is missing or not valid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Forbidden:
      description: The API key does not have the required scope.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Conflict:
      description: The request ID was already used with different inputs.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Orbit API key
      description: Use an Orbit API key from the developer dashboard.

````