Primitive Search API

Semantic and hybrid search across received and sent mail

Operations 1

POST /semantic-search Semantic search across received and sent mail #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/primitive-search-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

primitive-search-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Primitive Search API
  version: 1.0.0
  description: Primitive is email infrastructure for AI agents.
  contact:
    name: Primitive
    url: https://primitive.dev
  license:
    name: Proprietary
    url: https://primitive.dev/terms
  x-stability-level: stable
  x-deprecation-policy: 'Breaking changes are announced at least 6 months in advance. Deprecated fields carry x-deprecated: true. The current stable version is v1.'
servers:
- url: https://api.primitive.dev/v1
  description: Canonical API host (PRIMITIVE_API_BASE_URL). Carries every public API operation.
tags:
- name: Search
  description: Semantic and hybrid search across received and sent mail
paths:
  /semantic-search:
    post:
      operationId: semanticSearch
      summary: Semantic search across received and sent mail
      description: 'Ranked search across both received and sent mail. The `mode`

        field selects the ranking strategy:


        - `keyword`: lexical full-text matching only (no embeddings).

        - `semantic`: meaning-based matching using vector embeddings.

        - `hybrid` (default): blends the semantic and keyword signals.


        Results are ordered by a relevance `score`. Every row reports the

        fields it matched (`matched_fields`), a match-centered excerpt per

        field (`snippets`), and a `score_breakdown` whose components account

        for the `score`. Page through results by passing the prior

        response''s `meta.cursor` back as `cursor`.


        Requires the Pro plan and the `semantic_search_enabled`

        entitlement; callers without them receive `403`.


        Host routing: this operation is served only by the search host

        (`https://api.primitive.dev/v1`). The typed SDKs route it there

        automatically.'
      servers:
      - url: https://api.primitive.dev/v1
        description: Search host
      tags:
      - Search
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                query:
                  type: string
                  minLength: 1
                  maxLength: 2048
                  description: 'Free-text query. Required for `semantic` and `hybrid` modes;

                    optional for `keyword` mode.

                    '
                mode:
                  type: string
                  enum:
                  - hybrid
                  - semantic
                  - keyword
                  default: hybrid
                  description: 'Ranking strategy. `keyword` is lexical only, `semantic` is

                    embedding-based, `hybrid` blends both.

                    '
                corpus:
                  type: array
                  items:
                    type: string
                    enum:
                    - inbound
                    - outbound
                  minItems: 1
                  maxItems: 2
                  description: 'Which mail to search. Defaults to both received (`inbound`)

                    and sent (`outbound`).

                    '
                search_in:
                  type: array
                  items:
                    type: string
                    enum:
                    - subject
                    - headers
                    - addresses
                    - body
                    description: A searchable email field.
                  description: Restrict matching to these fields. Defaults to all.
                exclude:
                  type: array
                  items:
                    type: string
                    enum:
                    - subject
                    - headers
                    - addresses
                    - body
                    description: A searchable email field.
                  description: Exclude these fields from matching.
                date_from:
                  type: string
                  format: date-time
                  description: Only include mail at or after this timestamp.
                date_to:
                  type: string
                  format: date-time
                  description: Only include mail at or before this timestamp.
                include:
                  type: array
                  items:
                    type: string
                    enum:
                    - coverage
                  description: 'Opt-in extras. `coverage` adds an index-coverage snapshot to

                    `meta`. Matched fields, snippets, and the score breakdown are

                    always returned regardless of this field.

                    '
                limit:
                  type: integer
                  minimum: 1
                  maximum: 100
                  default: 10
                  description: Maximum number of results to return.
                cursor:
                  type: string
                  description: Opaque pagination cursor from a prior response's `meta.cursor`.
      responses:
        '200':
          description: Ranked search results
          content:
            application/json:
              schema:
                allOf:
                - type: object
                  properties:
                    success:
                      type: boolean
                      const: true
                  required:
                  - success
                  - data
                - type: object
                  properties:
                    data:
                      type: array
                      items:
                        type: object
                        properties:
                          source_type:
                            type: string
                            enum:
                            - inbound_email
                            - sent_email
                            description: Whether this row is a received or sent message.
                          id:
                            type: string
                            description: Message id. Combine with `api_url` to fetch the full record.
                          subject:
                            type:
                            - string
                            - 'null'
                          from:
                            type:
                            - string
                            - 'null'
                          to:
                            type:
                            - string
                            - 'null'
                          timestamp:
                            type: string
                            description: Message timestamp (received_at for inbound, created_at for sent).
                          status:
                            type: string
                            description: Lifecycle status of the message.
                          score:
                            type: number
                            description: Overall relevance score; the `score_breakdown` components account for it.
                          semantic_score:
                            type:
                            - number
                            - 'null'
                            description: Raw semantic similarity signal, or null when not applicable.
                          keyword_score:
                            type:
                            - number
                            - 'null'
                            description: Raw keyword (lexical) signal, or null when not applicable.
                          matched_fields:
                            type: array
                            items:
                              type: string
                              enum:
                              - subject
                              - headers
                              - addresses
                              - body
                              description: A searchable email field.
                            description: Fields where the query matched.
                          snippets:
                            type: array
                            items:
                              type: object
                              properties:
                                field:
                                  type: string
                                  description: The field this excerpt came from.
                                text:
                                  type: string
                                  description: Plain-text excerpt centered on the match (no markup).
                              required:
                              - field
                              - text
                            description: Match-centered excerpts, one per matched field.
                          score_breakdown:
                            type: object
                            description: 'Additive contributions to `score`. `semantic` and `keyword` are the

                              raw signals times the mode''s weight (null when not applicable);

                              these plus `field_boost` and `recency` sum to `score` before each

                              value is independently rounded to 5 decimal places.

                              '
                            properties:
                              semantic:
                                type:
                                - number
                                - 'null'
                              keyword:
                                type:
                                - number
                                - 'null'
                              field_boost:
                                type: number
                              recency:
                                type: number
                            required:
                            - semantic
                            - keyword
                            - field_boost
                            - recency
                          api_url:
                            type:
                            - string
                            - 'null'
                            description: Relative API path to fetch the full message.
                        required:
                        - source_type
                        - id
                        - subject
                        - from
                        - to
                        - timestamp
                        - status
                        - score
                        - semantic_score
                        - keyword_score
                        - matched_fields
                        - snippets
                        - score_breakdown
                        - api_url
                    meta:
                      type: object
                      properties:
                        limit:
                          type: integer
                          description: Page size used for this request.
                        cursor:
                          type:
                          - string
                          - 'null'
                          description: Cursor for the next page, or null if there are no more results.
                        mode:
                          type: string
                          enum:
                          - hybrid
                          - semantic
                          - keyword
                          description: Ranking mode used for this response.
                        coverage:
                          oneOf:
                          - type: object
                            description: Index-coverage snapshot for the org, returned only when the `coverage` include option is requested.
                            properties:
                              embedded_chunks:
                                type: integer
                              pending_chunks:
                                type: integer
                              skipped_plan_chunks:
                                type: integer
                              skipped_quota_chunks:
                                type: integer
                              unsupported_attachment_chunks:
                                type: integer
                              failed_chunks:
                                type: integer
                            required:
                            - embedded_chunks
                            - pending_chunks
                            - skipped_plan_chunks
                            - skipped_quota_chunks
                            - unsupported_attachment_chunks
                            - failed_chunks
                          - type: 'null'
                          description: 'Index-coverage snapshot, present only when requested via

                            `include: [coverage]`; otherwise null.

                            '
                      required:
                      - limit
                      - cursor
                      - mode
                      - coverage
                  required:
                  - data
                  - meta
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
        '400':
          $ref: '#/components/responses/ValidationError'
          description: Invalid request parameters
        '401':
          $ref: '#/components/responses/Unauthorized'
          description: Invalid or missing API key
        '403':
          $ref: '#/components/responses/Forbidden'
          description: Authenticated caller lacks permission for the operation
        '429':
          $ref: '#/components/responses/RateLimited'
          description: Rate limit exceeded
        '500':
          $ref: '#/components/responses/InternalError'
          description: Primitive encountered an internal error
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: Primitive is temporarily unable to process the request
      security:
      - BearerAuth: []
      parameters:
      - name: Idempotency-Key
        in: header
        required: false
        description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
        schema:
          type: string
          minLength: 1
          maxLength: 255
components:
  schemas:
    ErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          const: false
        error:
          type: object
          properties:
            code:
              type: string
              enum:
              - unauthorized
              - forbidden
              - not_found
              - validation_error
              - rate_limit_exceeded
              - internal_error
              - conflict
              - mx_conflict
              - outbound_disabled
              - cannot_send_from_domain
              - recipient_not_allowed
              - outbound_key_missing
              - outbound_unreachable
              - outbound_key_invalid
              - outbound_capacity_exhausted
              - outbound_response_malformed
              - outbound_relay_failed
              - discard_not_enabled
              - inbound_not_repliable
              - search_timeout
              - authorization_pending
              - slow_down
              - access_denied
              - expired_token
              - invalid_device_code
              - invalid_signup_code
              - invalid_signup_token
              - invalid_verification_code
              - email_delivery_failed
              - clerk_signup_failed
              - no_orgs_for_user
              - org_not_accessible
              - feature_disabled
              - memory_conflict
              - developer_usage_credit_exhausted
              - no_payout_address
              - ownership_proof_failed
              - payment_verification_failed
              - payment_declined
              - challenge_expired
              - settlement_failed
              - template_not_installable
              - scaffold_only
              - invalid_variables
              - unknown_secrets
              - missing_secrets
              - no_inbound_domain
              - domain_cannot_send
              - address_taken
              - route_cap_reached
              - name_exhausted
            message:
              type: string
            details:
              type: object
              description: 'Optional structured data that callers can inspect to recover

                from the error. The fields present depend on `code`. Additional

                keys may be added over time without a major-version bump.

                '
              additionalProperties: true
              properties:
                mx_conflict:
                  type: object
                  description: Present when `code == mx_conflict`.
                  required:
                  - provider_name
                  - suggested_subdomain
                  properties:
                    provider_name:
                      type: string
                      description: Human-readable name of the detected mailbox provider (e.g. "Google Workspace").
                    suggested_subdomain:
                      type: string
                      description: Subdomain to try instead (e.g. "mail" for `mail.example.com`).
                required_entitlements:
                  type: array
                  items:
                    type: string
                  description: Entitlements that would allow a denied send when no recipient-scope gate was granted.
                sent_email_id:
                  type: string
                  description: ID of the persisted sent-email attempt associated with the error.
                content_hash:
                  type: string
                  description: Content hash of the original request on idempotency cache-hit errors.
                client_idempotency_key:
                  type: string
                  description: Effective idempotency key associated with the original request.
            gates:
              type: array
              items:
                $ref: '#/components/schemas/GateDenial'
              description: Structured per-gate denial detail for recipient-scope send-mail failures.
            request_id:
              type: string
              description: Server-issued request identifier for support and tracing.
          required:
          - code
          - message
      required:
      - success
      - error
    GateDenial:
      type: object
      properties:
        name:
          type: string
          enum:
          - send_to_confirmed_domains
          - send_to_known_addresses
          description: Public recipient-scope gate name that denied the send.
        reason:
          type: string
          enum:
          - domain_not_confirmed
          - recipient_unauthenticated
          - recipient_not_known
          description: Stable machine-readable denial reason.
        message:
          type: string
          description: Human-readable explanation of the gate denial.
        subject:
          type: string
          description: Domain or address the gate evaluated.
        fix:
          $ref: '#/components/schemas/GateFix'
        docs_url:
          type: string
          description: Public docs URL with more context.
      required:
      - name
      - reason
      - message
      - subject
    GateFix:
      type: object
      properties:
        action:
          type: string
          enum:
          - confirm_domain
          - sender_must_fix_authentication
          - wait_for_inbound
          description: Suggested next action for the caller.
        subject:
          type: string
          description: Entity the action applies to.
      required:
      - action
      - subject
  responses:
    Unauthorized:
      description: Invalid or missing API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            error:
              code: unauthorized
              message: Invalid or missing API key
    ValidationError:
      description: Invalid request parameters
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            error:
              code: validation_error
              message: Invalid domain format
    ServiceUnavailable:
      description: Primitive is temporarily unable to process the request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            error:
              code: outbound_capacity_exhausted
              message: Outbound capacity is temporarily exhausted
    Forbidden:
      description: Authenticated caller lacks permission for the operation
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            error:
              code: forbidden
              message: Insufficient permissions
    InternalError:
      description: Primitive encountered an internal error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            error:
              code: internal_error
              message: Internal server error
    RateLimited:
      description: Rate limit exceeded
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds to wait before retrying
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            error:
              code: rate_limit_exceeded
              message: Rate limit exceeded
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: 'API key with `prim_` prefix or OAuth access token with `prim_oat_` prefix: `Authorization: Bearer <token>`. Access is governed by the caller''s organization role (`owner`, `admin`, or `member`): API keys always act at `member` level regardless of who created them, and OAuth access tokens act with the authorizing user''s current organization role, resolved per request. Every operation in this spec is available to organization members; billing and organization administration are owner/admin actions performed in the dashboard and are not part of this API.'
    DownloadToken:
      type: apiKey
      in: query
      name: token
      description: Signed download token provided in webhook payloads