> ## 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.

# Enrich an email address into a contact profile

> Turns one email address into the canonical Person behind it plus the contact channels a Person does not carry (associated emails, X, Telegram). Sources: our own records first, then two reverse-lookup sources and, when they name someone at a company domain or state a LinkedIn profile, a LinkedIn cross-reference. Every delivered field was stated by a source — nothing is inferred from the address. If any source the enrichment needs cannot answer, nothing is delivered and nothing is charged. Phone numbers are never returned. People on the workspace do-not-contact list are refused before any work. Rate limited per workspace, shared with the in-app tool.

#### Billing

- charged per completed enrichment — found or not, or answered from our own records — never on a refusal, a source that could not answer, or a replay.

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/enrich/email
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/enrich/email:
    post:
      summary: Enrich an email address into a contact profile
      description: >-
        Turns one email address into the canonical Person behind it plus the
        contact channels a Person does not carry (associated emails, X,
        Telegram). Sources: our own records first, then two reverse-lookup
        sources and, when they name someone at a company domain or state a
        LinkedIn profile, a LinkedIn cross-reference. Every delivered field was
        stated by a source — nothing is inferred from the address. If any source
        the enrichment needs cannot answer, nothing is delivered and nothing is
        charged. Phone numbers are never returned. People on the workspace
        do-not-contact list are refused before any work. Rate limited per
        workspace, shared with the in-app tool.


        #### Billing


        - charged per completed enrichment — found or not, or answered from our
        own records — never on a refusal, a source that could not answer, or a
        replay.


        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.
      parameters:
        - schema:
            type: string
            minLength: 1
            maxLength: 255
            description: >-
              Optional, 1–255 printable ASCII characters, scoped to your
              workspace. Send the same key when you retry the same request: the
              original outcome is replayed (`replay: true`, `charged: false`,
              `pointsCharged: 0`) instead of running and charging again. The
              same key with a different request is refused with 409
              `idempotency_conflict`, as is a key reused after its replay record
              expired (kept 30 days); a key whose first request is still running
              answers 409 `idempotency_in_progress`. Without a key, every
              request is a new lookup, billed at most once on its own.
          required: false
          description: >-
            Optional, 1–255 printable ASCII characters, scoped to your
            workspace. Send the same key when you retry the same request: the
            original outcome is replayed (`replay: true`, `charged: false`,
            `pointsCharged: 0`) instead of running and charging again. The same
            key with a different request is refused with 409
            `idempotency_conflict`, as is a key reused after its replay record
            expired (kept 30 days); a key whose first request is still running
            answers 409 `idempotency_in_progress`. Without a key, every request
            is a new lookup, billed at most once on its own.
          name: Idempotency-Key
          in: header
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                email:
                  type: string
                  description: >-
                    The email address to enrich. Trimmed and lower-cased before
                    anything else; a non-blank value that is not a well-formed
                    address is answered `reason: "invalid-input"`, uncharged.
              required:
                - email
            example:
              email: jane.doe@acme.com
      responses:
        '200':
          description: >-
            Every outcome, including refusals — inspect `found`, `reason`,
            `charged` and `replay`. A refusal is a 200 with `found: false` and a
            `reason`.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      found:
                        type: boolean
                        description: >-
                          Whether any source stated a fact about the person
                          behind the address.
                      email:
                        type: string
                        description: >-
                          The normalised address (the trimmed input for
                          `invalid-input`).
                      person:
                        allOf:
                          - $ref: '#/components/schemas/Person'
                          - description: >-
                              THE canonical Person — every attribute present,
                              `null` or `[]` when no source stated it. Nothing
                              is inferred from the address. `null` when nothing
                              was found or the request was refused.
                      channels:
                        type:
                          - object
                          - 'null'
                        properties:
                          emails:
                            type: array
                            items:
                              type: string
                            description: Addresses associated with the person.
                          x:
                            type:
                              - string
                              - 'null'
                            description: X (Twitter) handle, without the @.
                          telegram:
                            type:
                              - string
                              - 'null'
                            description: Telegram handle, without the @.
                        required:
                          - emails
                          - x
                          - telegram
                        description: >-
                          The contact channels a Person does not carry. `null`
                          when nothing was found.
                      sources:
                        type: array
                        items:
                          type: string
                          enum:
                            - enrich
                            - leakcheck
                            - linkedin
                        description: The sources that contributed a fact.
                      cached:
                        type: boolean
                        description: >-
                          Answered from our own records (still charged at the
                          same price).
                      charged:
                        type: boolean
                        description: Whether this call consumed points.
                      pointsCharged:
                        type: integer
                        description: Points consumed by this call.
                      balance:
                        type:
                          - object
                          - 'null'
                        properties:
                          available:
                            type: integer
                          reserved:
                            type: integer
                          granted:
                            type: integer
                        required:
                          - available
                          - reserved
                          - granted
                        description: >-
                          Workspace balance after this call. Null only when the
                          balance could not be read after charging — the charge
                          still stands; re-read `GET /api/v1/points`.
                      reason:
                        type:
                          - string
                          - 'null'
                        enum:
                          - invalid-input
                          - suppressed
                          - enrichment-unavailable
                          - rate-limited
                          - provider-error
                          - insufficient-credits
                        description: >-
                          Why nothing was delivered. `null` for a found person
                          and for a completed enrichment that found nobody (a
                          charged miss). Retryable means asking again can
                          produce a different answer.


                          - `invalid-input` — the value is not a well-formed
                          email address. Charged: no. Retryable: no.

                          - `suppressed` — the address, or a person it names, is
                          on a do-not-contact list; checked before any work,
                          before the LinkedIn search and again before delivery.
                          Charged: no. Retryable: no.

                          - `enrichment-unavailable` — no enrichment source is
                          configured and no fresh owned record answers the
                          address. Charged: no. Retryable: yes.

                          - `rate-limited` — a source rate-limited the
                          enrichment beyond the configured wait; nothing was
                          delivered. Charged: no. Retryable: yes.

                          - `provider-error` — a source could not answer
                          (timeout, error, unreadable reply) or the charge could
                          not be completed; nothing was delivered. Charged: no.
                          Retryable: yes.

                          - `insufficient-credits` — the workspace balance
                          cannot cover the enrichment. Charged: no. Retryable:
                          yes.
                      replay:
                        type: boolean
                        description: >-
                          true when this is the stored outcome of an earlier
                          request with the same `Idempotency-Key`: nothing was
                          re-run and nothing was charged again (`charged:
                          false`, `pointsCharged: 0`).
                    required:
                      - found
                      - email
                      - person
                      - channels
                      - sources
                      - cached
                      - charged
                      - pointsCharged
                      - balance
                      - reason
                      - replay
                required:
                  - data
              example:
                data:
                  found: false
                  email: jane.doe@acme.com
                  person: null
                  channels: null
                  sources: []
                  cached: false
                  charged: true
                  pointsCharged: 5
                  balance:
                    available: 245
                    reserved: 0
                    granted: 250
                  reason: null
                  replay: false
        '400':
          description: >-
            `validation_error`. `email is required` — `email` absent, `null`,
            not a string or blank; or a malformed header: `Idempotency-Key must
            be 1 to 255 printable ASCII characters`. Unknown fields are ignored.
          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: >-
            `unauthenticated`. 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:
                      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
        '405':
          description: >-
            `method_not_allowed` (`Method not allowed`) — see the `Allow`
            response header.
          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
        '409':
          description: >-
            The `Idempotency-Key` cannot be used for this request.
            `idempotency_conflict`: the key was already used for a different
            address, or reused after its replay record expired — send a new key.
            `idempotency_in_progress`: the first request with this key is still
            running — retry once it finishes to get its outcome. 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_limited` (`Rate limit exceeded`): this workspace’s enrichment
            budget is spent (shared with the in-app tool). Retry after the
            number of seconds in the `Retry-After` header, also returned as
            `error.retryAfterSeconds`. Never billed.
          headers:
            Retry-After:
              schema:
                type: string
                description: Seconds to wait before calling again.
              required: true
              description: Seconds to wait before calling again.
          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
              example:
                error:
                  code: rate_limited
                  message: Rate limit exceeded
                  retryAfterSeconds: 12
        '500':
          description: >-
            `internal_error` (`Internal error`): an unexpected failure on our
            side. Never billed — once an enrichment has been charged, the call
            answers 200 with its outcome, never an error.
          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
              example:
                error:
                  code: internal_error
                  message: Internal error
      security:
        - workspaceApiKey: []
components:
  schemas:
    Person:
      type:
        - object
        - 'null'
      properties:
        avatarUrl:
          type:
            - string
            - 'null'
          description: >-
            Owned image address (`/api/images/<id>`, authenticated). Null ⇒
            render initials.
        fullName:
          type:
            - string
            - 'null'
          description: The source's own statement of the name.
        linkedinUrl:
          type: string
          description: Canonical profile URL — the identity. Never null.
        title:
          type:
            - string
            - 'null'
          description: Title of the primary current position.
        about:
          type:
            - string
            - 'null'
        company:
          $ref: '#/components/schemas/Company'
        companyName:
          type:
            - string
            - 'null'
          description: Alias of `company.name`.
        companyLinkedinUrl:
          type:
            - string
            - 'null'
          description: Alias of `company.linkedinUrl`.
        companyLogoUrl:
          type:
            - string
            - 'null'
          description: Alias of `company.logoImageUrl` (owned).
        domain:
          type:
            - string
            - 'null'
          description: >-
            The company's website domain, else the domain the profile resolver
            stated.
        industry:
          type:
            - string
            - 'null'
          description: Alias of `company.industry`.
        location:
          type:
            - string
            - 'null'
          description: The person's own location (never company HQ).
        department:
          type:
            - string
            - 'null'
          enum:
            - Accounting
            - Administrative
            - Arts and Design
            - Business Development
            - Community & Social Services
            - Consulting
            - Customer Success and Support
            - Education
            - Engineering
            - Entrepreneurship
            - Finance
            - Healthcare Services
            - Human Resources
            - Information Technology
            - Legal
            - Marketing
            - Media and Communication
            - Military and Protective Services
            - Operations
            - Product Management
            - Program and Project Management
            - Purchasing
            - Quality Assurance
            - Real Estate
            - Research
            - Sales
          description: Source-stated only; never classified from the title.
        seniority:
          type:
            - string
            - 'null'
          enum:
            - In Training
            - Entry Level
            - Senior
            - Strategic
            - Entry Level Manager
            - Experienced Manager
            - Director
            - Vice President
            - CXO
            - Owner / Partner
          description: Source-stated only; never classified from the title.
        yearsOfExperience:
          type:
            - string
            - 'null'
          enum:
            - Less than 1 year
            - 1-2 years
            - 3-5 years
            - 6-10 years
            - 11-15 years
            - 16-20 years
            - 20+ years
          description: >-
            Overlap-free union of stated positions; null when any start is
            missing or the stated precision leaves it between two buckets.
        employment:
          type: array
          items:
            $ref: '#/components/schemas/EmploymentEntry'
          description: Source order; [] when not available.
        education:
          type: array
          items:
            $ref: '#/components/schemas/EducationEntry'
          description: Source order; [] when not available.
        historyStatus:
          type: object
          properties:
            employment:
              type: string
              enum:
                - available
                - not-yet-available
              description: >-
                `not-yet-available`: no source has supplied this history yet
                (the list is []). `available` with [] means the source supplied
                an empty history.
            education:
              type: string
              enum:
                - available
                - not-yet-available
              description: >-
                `not-yet-available`: no source has supplied this history yet
                (the list is []). `available` with [] means the source supplied
                an empty history.
          required:
            - employment
            - education
      required:
        - avatarUrl
        - fullName
        - linkedinUrl
        - title
        - about
        - company
        - companyName
        - companyLinkedinUrl
        - companyLogoUrl
        - domain
        - industry
        - location
        - department
        - seniority
        - yearsOfExperience
        - employment
        - education
        - historyStatus
      description: >-
        linkedin mode only: THE canonical Person, present whenever resolution
        found someone; otherwise null.
    Company:
      type: object
      properties:
        name:
          type:
            - string
            - 'null'
        linkedinUrl:
          type:
            - string
            - 'null'
        tagline:
          type:
            - string
            - 'null'
        description:
          type:
            - string
            - 'null'
        industry:
          type:
            - string
            - 'null'
        employeeBand:
          type:
            - string
            - 'null'
          description: Size band as the source displays it.
        employeeCount:
          type:
            - integer
            - 'null'
          description: Exact headcount, when stated.
        followerCount:
          type:
            - integer
            - 'null'
        websiteUrl:
          type:
            - string
            - 'null'
        websiteDomain:
          type:
            - string
            - 'null'
        headquarters:
          type:
            - string
            - 'null'
        hqLocality:
          type:
            - string
            - 'null'
        hqRegion:
          type:
            - string
            - 'null'
        hqCountry:
          type:
            - string
            - 'null'
        foundedYear:
          type:
            - integer
            - 'null'
        companyType:
          type:
            - string
            - 'null'
        specialties:
          type: array
          items:
            type: string
        logoUrl:
          type: 'null'
          description: >-
            Always null. Reserved so the shape stays stable: a provider image
            address is never returned (it is signed, expires, and discloses the
            viewer). Render `logoImageUrl`.
        logoImageUrl:
          type:
            - string
            - 'null'
          description: >-
            Our own copy of the logo (`/api/images/<id>`, same authentication as
            this API). Null when the source states no logo or no copy is held.
        phone:
          type:
            - string
            - 'null'
          description: >-
            The company phone. Always null until phone verification is
            available; reserved so the shape does not change when it is.
        email:
          type:
            - string
            - 'null'
          description: >-
            The company's general contact email: only a generic role address
            (info@, sales@, …) on the company's own domain, stated by the
            company, that passed email verification. Null when none is stated or
            it did not verify. Never a personal address.
        xUrl:
          type:
            - string
            - 'null'
          description: >-
            X (Twitter) profile the company states as its own, as
            `https://x.com/<handle>`. Null when not stated; never constructed
            from the name.
        facebookUrl:
          type:
            - string
            - 'null'
          description: >-
            Facebook page the company states as its own, as
            `https://www.facebook.com/<page>`. Null when not stated.
        instagramUrl:
          type:
            - string
            - 'null'
          description: >-
            Instagram profile the company states as its own, as
            `https://www.instagram.com/<user>`. Null when not stated.
        youtubeUrl:
          type:
            - string
            - 'null'
          description: >-
            YouTube channel the company states as its own, as
            `https://www.youtube.com/` followed by `@handle`, `channel/…`, `c/…`
            or `user/…`. Null when not stated.
        confirmedAt:
          type:
            - string
            - 'null'
          description: >-
            When this record was last confirmed against the source. Null when
            the Company is built only from what a lead or list row observed (no
            company record).
      required:
        - name
        - linkedinUrl
        - tagline
        - description
        - industry
        - employeeBand
        - employeeCount
        - followerCount
        - websiteUrl
        - websiteDomain
        - headquarters
        - hqLocality
        - hqRegion
        - hqCountry
        - foundedYear
        - companyType
        - specialties
        - logoUrl
        - logoImageUrl
        - phone
        - email
        - xUrl
        - facebookUrl
        - instagramUrl
        - youtubeUrl
        - confirmedAt
      description: >-
        The canonical Company for the current employer (same schema as `POST
        /api/v1/company`).
    EmploymentEntry:
      type: object
      properties:
        companyName:
          type:
            - string
            - 'null'
        companyLinkedinUrl:
          type:
            - string
            - 'null'
        title:
          type:
            - string
            - 'null'
        start:
          $ref: '#/components/schemas/StatedDate'
        end:
          $ref: '#/components/schemas/StatedDate'
        current:
          type: boolean
        location:
          type:
            - string
            - 'null'
          description: Where the position was held, as the source stated it.
        description:
          type:
            - string
            - 'null'
          description: The person's own description of the position, as stated.
      required:
        - companyName
        - companyLinkedinUrl
        - title
        - start
        - end
        - current
        - location
        - description
    EducationEntry:
      type: object
      properties:
        school:
          type:
            - string
            - 'null'
        schoolLinkedinUrl:
          type:
            - string
            - 'null'
        degree:
          type:
            - string
            - 'null'
        field:
          type:
            - string
            - 'null'
        start:
          $ref: '#/components/schemas/StatedDate'
        end:
          $ref: '#/components/schemas/StatedDate'
      required:
        - school
        - schoolLinkedinUrl
        - degree
        - field
        - start
        - end
    StatedDate:
      type:
        - object
        - 'null'
      properties:
        year:
          type: integer
        month:
          type:
            - integer
            - 'null'
          description: 1–12, or null when the source stated the year only (never padded).
      required:
        - year
        - month
  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.