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

# Look up a recipient

> Tells whether a phone number or an email address can receive iMessage.

* **Credential:** an API key. A trial key returns `401 unauthorized`. See [Recipient lookup](/guides/lookup).
* **Line-scoped keys:** the list of a [line-scoped key](/authentication#line-scoped-keys) is not applied: such a key can name any line of the account in `from_line_id`.
* **Test keys:** a lookup with an `isnap_test_` account key counts against the same lookup limits: see [Test keys](/authentication#test-keys).
* **`Idempotency-Key`:** not honored.
* **Events:** none, also when a check finishes.
* **Personal data:** the request and the response carry the handle, and the response says whether it can receive iMessage. A definite answer is kept 24 hours and returned to every caller of iSnap. Request it only when needed, do not copy it into logs, tickets or other tools, and refer to a contact by an id of your own.
* **Response:** `200` with `imessage` `true`, `false` or `null`, or `202` with `pending: true` when a check was started and has given no definite answer: resend the same request after `retry_after` seconds. A call that starts a check waits up to 3 seconds for the device's answer before it returns `202`. See [The three answers](/guides/lookup#the-three-answers).
* **Limits:** 400 calls per account per UTC day, and, with `from_line_id`, 30 per line per UTC hour and 100 per line per UTC day. Every call that passes validation counts: an answer from the 24-hour store, a `202` and a repeat of a pending check count against every limit that applies to the call. A call refused with `429 lookup_rate_limit_exceeded` counts against the limit that refused it and the limits checked before it, in this order: the account limit, the hourly limit of the line, the daily limit of the line. A call refused with `400` or `404` counts against no limit. See [Limits](/guides/lookup#limits).

## Errors

| Status | `error.code` | When |
| - | - | - |
| 400 | `validation_failed` | `handle` is shorter than 3 or longer than 255 characters, or is not a valid phone number or email address. `from_line_id` is malformed. |
| 404 | `not_found` | `from_line_id` is not a line of your account. |
| 429 | `lookup_rate_limit_exceeded` | A lookup limit is reached. `Retry-After` gives the seconds until the next UTC hour or the next UTC day. |

The errors that every operation shares are in [Errors](/errors-and-rate-limits#errors).


## OpenAPI

````yaml POST /v1/lookup
openapi: 3.0.0
info:
  title: iSnap API
  version: 2.45.0
  description: >-
    The iSnap API sends and receives iMessage, RCS and SMS on real phone lines,
    manages those lines, and reports events through webhooks. Every message is
    sent to a real recipient, with an `isnap_live_` key and with an
    `isnap_test_` key alike. Retry a `429` after `Retry-After` seconds. Retry a
    `500` or a `503` up to 2 times, with exponential backoff. Do not retry any
    other `4xx`: fix the request. Retry a write only on an operation that honors
    `Idempotency-Key`, with the key of the first attempt. Responses carry
    personal data: handles, names and message text.
servers:
  - url: https://api.isnap.ai
security:
  - bearerAuth: []
paths:
  /v1/lookup:
    post:
      summary: Look up a recipient
      description: >-
        Tells whether a phone number or an email address can receive iMessage.
        The response `handle` is the normalized value: E.164 for a phone number,
        lower case for an email address. A phone number without a country code
        is read as a US number. The handle is personal data, and so is the
        answer about it.


        There are three outcomes. `200` with `imessage: true` or `false` is a
        definite answer. `200` with `imessage: null` means unknown, and no check
        is running. `202` with `imessage: null`, `pending: true`, `probe_id` and
        `retry_after` means that a check started and has not given a definite
        answer: resend the same request after `retry_after` seconds. No event
        announces the result.


        A check runs only on a connected device of one of your lines: the line
        named by `from_line_id`, or any line of your account when you name none.
        When no such device exists, which is always the case when your only
        lines are shared lines, the answer is `imessage: null` unless a definite
        answer is already known. iSnap keeps a definite answer for 24 hours and
        reuses it for every account: an answer obtained for one account also
        answers the lookups of others. It does not keep an unknown result, which
        only prevents a new check on that handle for 60 seconds.


        Every call that passes validation counts against the lookup limits: a
        call answered from a kept answer, a `202`, a repeat of a pending check,
        and a call refused with `429 lookup_rate_limit_exceeded`. A call refused
        with `400` or `404` counts against no limit. The limits are 400 calls
        per account per UTC calendar day and, when `from_line_id` is given, 30
        per line per UTC clock hour and 100 per line per UTC calendar day. Above
        a limit the API returns `429 lookup_rate_limit_exceeded`, with
        `Retry-After` set to the seconds until the next UTC hour or day.


        Accepts an API key. The `line_ids` of a line-scoped key are not checked:
        the key can name any line of the account in `from_line_id`. A
        `from_line_id` that is not a line of your account returns `404
        not_found`. A lookup is never required before a send, does not change
        how a message is sent and does not count against the sending limits.
        `imessage: true` for an email address does not make it a valid `to` of
        `POST /v1/messages`, which accepts phone numbers only.
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LookupRequest'
      responses:
        '200':
          description: 'The answer: `imessage` is `true`, `false`, or `null` when unknown.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LookupEnvelope'
        '202':
          description: >-
            A check started and has not given a definite answer. Resend the same
            request after `retry_after` seconds: the repeat returns a definite
            answer, `200` with `imessage: null`, or another `202`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LookupEnvelope'
        '400':
          description: >-
            Invalid request. `error.code` is `validation_failed`: `handle` is
            shorter than 3 or longer than 255 characters, is not a valid phone
            number or email address, or `from_line_id` is malformed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '401':
          description: No valid credential. `error.code` is `unauthorized`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '429':
          description: >-
            Too many requests. `error.code` is `lookup_rate_limit_exceeded` (a
            lookup limit is passed: 400 calls per account per UTC calendar day,
            or with `from_line_id` 30 per line per UTC clock hour or 100 per
            line per UTC calendar day) or `rate_limit_exceeded` (more than 100
            requests came from the same network address in 60 seconds).
            `Retry-After` gives the seconds to wait. A call refused with
            `lookup_rate_limit_exceeded` counts against the limit that refused
            it and the limits checked before it, in this order: the account
            limit, the hourly limit of the line, the daily limit of the line.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '500':
          description: >-
            Unexpected failure. `error.code` is `internal_error`. An operation
            that takes a JSON body also returns it when the body, sent as
            `application/json`, is not valid JSON. Quote `trace_id` when you
            report it to iSnap.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
      security:
        - bearerAuth: []
components:
  schemas:
    LookupRequest:
      type: object
      properties:
        handle:
          type: string
          minLength: 3
          maxLength: 255
          description: >-
            The handle to check: a phone number or an email address, 3 to 255
            characters. A phone number without a country code is read as a US
            number.
        from_line_id:
          type: string
          pattern: ^line_[0-9A-Za-z]{25}$
          description: >-
            A line of your account (`line_` followed by 25 letters or digits).
            When given, only that line's device runs the check, and the limits
            per line apply. When omitted, any line of your account can run the
            check and only the limit per account applies.
      required:
        - handle
      description: The handle to check.
    LookupEnvelope:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - true
          description: Always `true` on a success response.
        data:
          $ref: '#/components/schemas/Lookup'
        trace_id:
          type: string
          description: >-
            Trace id of the request that produced this response: 32 lowercase
            hexadecimal characters. Quote it when you report a problem to iSnap.
        request_id:
          type: string
          description: >-
            Id of the request that produced this response: `req_` followed by 25
            letters or digits.
      required:
        - success
        - data
        - trace_id
        - request_id
    ErrorEnvelope:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - false
          description: Always `false` on an error response.
        data:
          nullable: true
          description: Always `null` on an error response.
        error:
          type: object
          properties:
            status:
              type: integer
              description: The HTTP status of the response.
            code:
              type: string
              description: >-
                Stable machine-readable code that names the refusal. Branch on
                this value.
            message:
              type: string
              description: >-
                Text for a person. It can change between versions: do not branch
                on it.
            retry_after:
              type: integer
              nullable: true
              description: >-
                Seconds to wait before the next attempt. Set on the `429`
                responses that know a delay, where the `Retry-After` header
                carries the same value. When it is above 60 seconds, do not wait
                in a request: schedule the call, or report the time. `null` on
                every other error.
            retry_at:
              type: string
              nullable: true
              description: >-
                ISO 8601 timestamp at which the refusal ends. Set only on `429
                rebind_cap_reached`. `null` on every other error.
            issues:
              type: array
              nullable: true
              items:
                type: object
                properties:
                  path:
                    type: array
                    items:
                      anyOf:
                        - type: string
                        - type: number
                    description: >-
                      Where the problem is: the field names and array indexes
                      that lead to it.
                  message:
                    type: string
                    description: Text for a person.
                  code:
                    type: string
                    description: >-
                      Kind of problem. On `validation_failed`, for example
                      `invalid_type`, `invalid_value`, `invalid_format`,
                      `invalid_union`, `invalid_key`, `invalid_element`,
                      `too_small`, `too_big`, `not_multiple_of`,
                      `unrecognized_keys` or `custom`. The list is open: read
                      `path` and `message`.
                  min:
                    type: integer
                    description: >-
                      Smallest accepted value. Present only on
                      `limit_out_of_bounds`, `mac_quantity_not_a_pack`,
                      `byop_reduction_strands_mac_sessions` and
                      `byop_reduction_strands_byod_lines`.
                  max:
                    type: integer
                    description: >-
                      Largest accepted value. Present only on
                      `limit_out_of_bounds` and
                      `mac_sessions_exceed_byop_lines`, and on the `too_big`
                      issue of an `Idempotency-Key` header longer than 255
                      characters.
                required:
                  - path
                  - message
                  - code
              description: >-
                Problems found in the request, one entry each. Set on most `400
                validation_failed` responses and on the refusals that name a
                field: a refused `handle` of `POST /v1/lookup` has `issues:
                null`. `null` on every other error.
          required:
            - status
            - code
            - message
            - retry_after
            - retry_at
            - issues
          description: >-
            What went wrong. The six fields are always present; `retry_after`,
            `retry_at` and `issues` are `null` when they do not apply.
        trace_id:
          type: string
          description: >-
            Trace id of the request that produced this response: 32 lowercase
            hexadecimal characters. Quote it when you report a problem to iSnap.
        request_id:
          type: string
          description: >-
            Id of the request that produced this response: `req_` followed by 25
            letters or digits.
      required:
        - success
        - data
        - error
        - trace_id
        - request_id
      description: The body of an error response.
    Lookup:
      type: object
      properties:
        handle:
          type: string
          description: >-
            The normalized handle: E.164 for a phone number, lower case for an
            email address.
        imessage:
          type: boolean
          nullable: true
          description: >-
            `true`: the handle can receive iMessage. `false`: it cannot. `null`:
            unknown.
        pending:
          type: boolean
          description: >-
            `true` on a `202`: a check started and has not given a definite
            answer. Absent on a `200`.
        probe_id:
          type: string
          description: Id of the running check. Present only on a `202`.
        retry_after:
          type: integer
          minimum: 1
          description: >-
            Seconds to wait before you resend the same request. Present only on
            a `202`.
      required:
        - handle
        - imessage
      description: The answer of a lookup, or the state of a check that is running.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        An API key created in the iSnap dashboard, or a trial key, sent as
        `Authorization: Bearer <key>`. An API key starts with `isnap_live_` or
        `isnap_test_`; both act on the same account, lines and billing. A trial
        key acts on its trial only.

````

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