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

# Find and verify contact data

> Runs one enrichment lookup for the workspace behind the API key. Three modes share this endpoint: `email` (default) finds a verified work email from a name and company domain, `company` resolves a company name to its domain and then finds a verified work email there, and `linkedin` finds a verified work email from a LinkedIn profile URL. Every result is verified before delivery — when we cannot verify, `found` is `false` with a `reason` rather than a guess. Rate limited per workspace; check `GET /api/v1/points` for headroom first.

#### Billing

- **email mode (default)**: charged per completed lookup — found or not — never on provider errors.
- **company mode**: two parts, each charged on its own. The company lookup is charged once the name search answers — resolved or not; the email search is then charged per completed lookup — found or not. If the company can’t be resolved, or it resolves to a domain that cannot receive email (`unusable-domain`), only the company part is charged.
- **linkedin mode**: charged per completed lookup — found or not — never on provider errors.

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/find
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/find:
    post:
      summary: Find and verify contact data
      description: >-
        Runs one enrichment lookup for the workspace behind the API key. Three
        modes share this endpoint: `email` (default) finds a verified work email
        from a name and company domain, `company` resolves a company name to its
        domain and then finds a verified work email there, and `linkedin` finds
        a verified work email from a LinkedIn profile URL. Every result is
        verified before delivery — when we cannot verify, `found` is `false`
        with a `reason` rather than a guess. Rate limited per workspace; check
        `GET /api/v1/points` for headroom first.


        #### Billing


        - **email mode (default)**: charged per completed lookup — found or not
        — never on provider errors.

        - **company mode**: two parts, each charged on its own. The company
        lookup is charged once the name search answers — resolved or not; the
        email search is then charged per completed lookup — found or not. If the
        company can’t be resolved, or it resolves to a domain that cannot
        receive email (`unusable-domain`), only the company part is charged.

        - **linkedin mode**: charged per completed lookup — found or not — never
        on provider errors.


        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:
                mode:
                  type: string
                  enum:
                    - email
                    - company
                    - linkedin
                  description: >-
                    Lookup mode. `email` (default, also used for an unrecognised
                    value): find a verified work email from a name and the
                    company domain. `company`: resolve a company NAME to its
                    domain, then find a verified work email there. `linkedin`:
                    find a verified work email from a LinkedIn profile URL.
                name:
                  type: string
                  description: >-
                    Full name of the person. Required for `email` and `company`
                    modes.
                company:
                  type: string
                  description: >-
                    Required for `email` and `company` modes. `email` mode: the
                    company domain or website URL (reduced to its bare host).
                    `company` mode: the company name.
                url:
                  type: string
                  description: LinkedIn profile URL. Required for `linkedin` mode.
            example:
              mode: email
              name: Satya Nadella
              company: microsoft.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 a verified email was found.
                      email:
                        type:
                          - string
                          - 'null'
                        description: The verified email address; null when not found.
                      status:
                        type:
                          - string
                          - 'null'
                        enum:
                          - valid
                          - risky
                        description: >-
                          Verification status of the returned email; null when
                          not found.
                      confidence:
                        type:
                          - number
                          - 'null'
                        minimum: 0
                        maximum: 1
                        description: >-
                          Verification confidence from 0 to 1 (e.g. 0.97); null
                          when not found.
                      charged:
                        type: boolean
                        description: Whether this call consumed points.
                      creditsCharged:
                        type: integer
                        description: >-
                          Points consumed (legacy field name, kept for
                          compatibility).
                      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:
                          - company-not-resolved
                          - profile-not-found
                          - profile-no-domain
                          - incomplete
                          - rate-limited
                          - company-unavailable
                          - profile-unavailable
                          - invalid-input
                          - needs-domain
                          - unusable-domain
                          - invalid-linkedin-url
                          - suppressed
                          - insufficient-credits
                        description: >-
                          Why no email was delivered. `null` for a delivered
                          email and for a completed lookup that found none (a
                          charged miss). Retryable means asking the same
                          question again can produce a different answer; a
                          retryable reason is never a permanent "no email".


                          - `company-not-resolved` — company mode: the company
                          name did not resolve to a company. Charged: the
                          company part only. Retryable: no.

                          - `profile-not-found` — linkedin mode: the profile
                          does not exist. Charged: yes. Retryable: no.

                          - `profile-no-domain` — linkedin mode: the profile
                          states no usable employer domain. Charged: yes.
                          Retryable: no.

                          - `incomplete` — a source could not answer and none
                          delivered, so the lookup could not be completed.
                          Charged: no. Retryable: yes.

                          - `rate-limited` — an upstream source rate-limited the
                          lookup beyond the configured wait. Charged: no.
                          Retryable: yes.

                          - `company-unavailable` — company mode: company
                          resolution is not configured or its source could not
                          be read. Charged: no. Retryable: yes.

                          - `profile-unavailable` — linkedin mode: the profile
                          resolver is not configured or did not answer. Charged:
                          no. Retryable: yes.

                          - `invalid-input` — the name cannot be split into
                          first and last, or the company is neither a domain, a
                          URL nor (company mode) a name. Charged: no. Retryable:
                          no.

                          - `needs-domain` — email mode was given a company name
                          instead of a domain — use company mode. Charged: no.
                          Retryable: no.

                          - `unusable-domain` — the domain cannot receive email:
                          a deny-listed domain (e.g. a free-mail or social site)
                          or a definitive no-MX answer. Charged: no. Retryable:
                          no.

                          - `invalid-linkedin-url` — linkedin mode: the URL is
                          not a LinkedIn profile URL. Charged: no. Retryable:
                          no.

                          - `suppressed` — the person is on a do-not-contact
                          list; checked before any work and again before
                          delivery. Charged: no. Retryable: no.

                          - `insufficient-credits` — the workspace balance
                          cannot cover the lookup. Charged: no. Retryable: yes.


                          In company mode, a reason that follows a resolved
                          company keeps that company part's charge (`charged:
                          true`). `pointsCharged` always states exactly what was
                          consumed.
                      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`).
                      resolvedProfile:
                        type:
                          - object
                          - 'null'
                        properties:
                          name:
                            type: string
                          companyName:
                            type:
                              - string
                              - 'null'
                          domain:
                            type:
                              - string
                              - 'null'
                          url:
                            type: string
                        required:
                          - name
                          - companyName
                          - domain
                          - url
                        description: >-
                          Deprecated — use person. linkedin mode only: who the
                          profile resolved to — present whenever resolution
                          succeeded, including on not-found outcomes; otherwise
                          null.
                      person:
                        $ref: '#/components/schemas/Person'
                    required:
                      - found
                      - email
                      - status
                      - confidence
                      - charged
                      - creditsCharged
                      - pointsCharged
                      - balance
                      - reason
                      - replay
                      - resolvedProfile
                      - person
                required:
                  - data
              example:
                data:
                  found: true
                  email: satya.nadella@microsoft.com
                  status: valid
                  confidence: 0.97
                  charged: true
                  pointsCharged: 1
                  balance:
                    available: 99
                    reserved: 0
                    granted: 100
                  reason: null
                  replay: false
                  person: null
                  creditsCharged: 1
                  resolvedProfile: null
        '400':
          description: >-
            `validation_error`. Missing input — a field that is absent, `null`
            or blank: `name and company are required` (email/company modes; an
            unrecognised or absent `mode` is `email`) or `url is required`
            (linkedin mode); or a malformed header: `Idempotency-Key must be 1
            to 255 printable ASCII characters`. Non-string field values are
            converted to strings, never rejected.
          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 with a different
            request, 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 request
            budget is spent. Retry after the number of seconds in the
            `Retry-After` header, also returned as `error.retryAfterSeconds`.
            Rate-limited calls are 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 any part of a lookup 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.