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

# Send a contact card

> Builds a contact card from the fields of the request and sends it to the contact of a chat as a message.

* **Credential:** an API key. A trial key returns `401 unauthorized`. See [Contact cards](/guides/conversations#contact-cards).
* **Line-scoped keys:** a [line-scoped key](/authentication#line-scoped-keys) gets `403 forbidden` when the chat's line is outside its list.
* **Test keys:** a card sent with an `isnap_test_` account key reaches the real contact: see [Test keys](/authentication#test-keys).
* **`Idempotency-Key`:** read, and never replayed. Once a call has created the card's message, a repeat with the same key returns `409 idempotency_conflict`. When no message was created, the repeat sends the card, and so does every repeat without a key: send a key with every call. A key that a reaction already used on the chat's line is not refused: the call returns `202` with the id of the reaction entry in `message_id`, and sends nothing. Use a new key for every call. Do not retry this call blindly: when you did not receive the response, look for the message with `GET /v1/messages` before you resend the card. See [Messages, reactions and contact cards](/idempotency#messages-reactions-and-contact-cards) for the scope and the lifetime of the key.
* **Events:** the events of a send, from `message.queued` or `message.scheduled` on.
* **Personal data:** the card holds a person's name, phone numbers, email addresses, organization, note and photo, and its file name holds the name. 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.
* **A message:** the card is a message with one attachment. The sending limits, the holds, the events and the errors of `POST /v1/messages` apply. One difference: on an iMessage line the card always asks for the typing indicator, whatever the line's `default_use_typing_indicator`. `ignore_quiet_hours` works as on `POST /v1/messages`: see [Sending during quiet hours](/guides/sending-limits#sending-during-quiet-hours). See [Sending messages](/guides/sending-messages).
* **Response:** `status` always reads `queued`, even when iSnap holds the message: read the message with `GET /v1/messages/{id}`. The response carries no `X-iSnap-Quota-` header.

## Errors

| Status | `error.code` | When |
| - | - | - |
| 400 | `validation_failed` | `first_name` is missing, a phone number is not in E.164, an email address is invalid, `avatar_url` is not an HTTPS URL, or none of `phone_numbers`, `emails` and `organization` is given. |
| 400 | `bad_request` | The chat's contact is not a phone number. |
| 403 | `forbidden` | The request uses a line-scoped key, and the chat's line is not in its list. |
| 404 | `not_found` | The chat is unknown, or its line is not yours. |
| 409 | `idempotency_conflict` | The `Idempotency-Key` was already used on the chat's line, by an earlier call of this operation that created the card's message or by a `POST /v1/messages`. |
| 502 | `avatar_fetch_failed` | iSnap fetches `avatar_url` once without following a redirect. The fetch failed: the host does not resolve to a public address, the response is not a `2xx` with a `Content-Type` of `image/png` or `image/jpeg`, the image is over 2,097,152 bytes (2 MiB), or the download did not finish within 5 seconds. Nothing is sent. |

The other errors of [`POST /v1/messages`](/api-reference/messages/send-message) also apply: the line errors, the `429` codes of the sending limits, and the `503` codes of a shared line. The errors that every operation shares are in [Errors](/errors-and-rate-limits#errors).


## OpenAPI

````yaml POST /v1/chats/{chat_id}/share-contact-card
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/chats/{chat_id}/share-contact-card:
    post:
      summary: Send a contact card
      description: >-
        Builds a contact card, which is a vCard 3.0 file, from the fields of the
        request and sends it to the chat's contact as a message with one
        attachment, on the chat's line. The message is sent like a message of
        `POST /v1/messages`: it counts against the line's sending limits, iSnap
        can hold it, and it produces the same `message.*` events. The handle of
        the chat's contact must be a phone number.


        Returns `202` with `message_id`, `attachment_id` and `status`. `status`
        always reads `queued`, even when iSnap holds the message: read the
        message with `GET /v1/messages/{id}` to know its `status`. The response
        carries no `X-iSnap-Quota-*` headers. `first_name` is required, with at
        least one of `phone_numbers`, `emails` or a non-empty `organization`.
        When `avatar_url` cannot be fetched, the API returns `502
        avatar_fetch_failed` and nothing is sent.


        The API cannot replay this call. Once a call has created the message, a
        repeat with the same `Idempotency-Key` returns `409
        idempotency_conflict` instead of the first result. A repeat of a call
        that failed before the message was created, for example on a `429` or on
        `502 avatar_fetch_failed`, sends the card. Do not retry this call
        automatically: after an error, check with `GET /v1/messages` whether the
        first call created the message. On a shared line, the call returns `503
        line_pool_exhausted` when no phone number is available to serve the
        contact, and `503 quota_state_unavailable` when that availability could
        not be read: retry the request.


        Accepts an API key. A line-scoped key gets `403 forbidden` when the
        chat's line is outside its `line_ids`. The fields of the card are
        personal data of the person they describe: iSnap stores them in the
        attachment it creates.
      parameters:
        - schema:
            type: string
            pattern: ^chat_[0-9A-Za-z]{25}$
            description: 'Chat id: `chat_` followed by 25 letters or digits.'
          required: true
          description: 'Chat id: `chat_` followed by 25 letters or digits.'
          name: chat_id
          in: path
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                first_name:
                  type: string
                  minLength: 1
                  maxLength: 255
                  description: First name on the card, 1 to 255 characters.
                last_name:
                  type: string
                  maxLength: 255
                  description: Last name on the card, at most 255 characters.
                avatar_url:
                  type: string
                  format: uri
                  description: >-
                    HTTPS URL of the photo of the card: a PNG or JPEG image of
                    at most 2 MiB (2,097,152 bytes). A URL that is not an HTTPS
                    URL returns `400 validation_failed`. iSnap fetches the URL
                    once, without following a redirect: the host must resolve to
                    a public address, the response must have a `2xx` status and
                    a `Content-Type` of `image/png` or `image/jpeg`, and the
                    download must finish within 5 seconds. Otherwise the API
                    returns `502 avatar_fetch_failed` and nothing is sent.
                phone_numbers:
                  type: array
                  items:
                    type: string
                    pattern: ^\+[1-9]\d{1,14}$
                    description: A phone number in E.164.
                  maxItems: 10
                  description: 'Phone numbers on the card, in E.164: at most 10.'
                emails:
                  type: array
                  items:
                    type: string
                    format: email
                    description: An email address.
                  maxItems: 10
                  description: 'Email addresses on the card: at most 10.'
                organization:
                  type: string
                  maxLength: 255
                  description: Organization on the card, at most 255 characters.
                note:
                  type: string
                  maxLength: 2000
                  description: Free text on the card, at most 2,000 characters.
                ignore_quiet_hours:
                  type: boolean
                  description: >-
                    `true` to let the message that carries the card leave during
                    the quiet hours of the line, as `ignore_quiet_hours` of
                    `POST /v1/messages`. The default is `false`. No effect on a
                    shared line.
              required:
                - first_name
              description: >-
                The contact card. `first_name` is required, with at least one of
                `phone_numbers`, `emails` or a non-empty `organization`.
      responses:
        '202':
          description: The contact card, accepted as a message.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ShareContactCardEnvelope'
        '400':
          description: >-
            Invalid request. `error.code` is `validation_failed` (a field is
            missing or malformed, or none of `phone_numbers`, `emails` and
            `organization` is given), `bad_request` (the handle of the chat's
            contact is not a phone number) or `line_not_active` (the status of
            the chat's line is neither `active` nor `suspended`).
          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'
        '403':
          description: >-
            Refused. `error.code` is `forbidden` (the request carries neither an
            API key in its `Authorization` header nor the header
            `X-Requested-With: XMLHttpRequest`, or the chat's line is outside
            the key's `line_ids`), `line_suspended` (the chat's line is
            suspended) or `outbound_first_forbidden` (the chat's line is
            inbound-only and the contact has never written to it).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '404':
          description: >-
            Not found. `error.code` is `not_found`: the chat does not exist, or
            its line is not yours.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '422':
          description: Not returned by this operation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '429':
          description: >-
            Too many requests. `error.code` is `rate_limit_exceeded` (more than
            100 requests came from the same network address in 60 seconds),
            `minute_limit_exceeded`, `hourly_limit_exceeded`,
            `total_message_quota_exceeded`, `new_contact_quota_exceeded` or
            `known_contact_quota_exceeded` (a sending limit is reached and the
            line's `quota_overflow_behavior` is `drop` or `fail`), or
            `queue_full` (the message would be held and the line already holds
            its maximum number of held messages, or no departure time exists in
            the next 8 days). `Retry-After` gives the seconds to wait.
          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:
    ShareContactCardEnvelope:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - true
          description: Always `true` on a success response.
        data:
          $ref: '#/components/schemas/ShareContactCardResult'
        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.
    ShareContactCardResult:
      type: object
      properties:
        message_id:
          type: string
          description: >-
            Id of the message that carries the card. Read it with `GET
            /v1/messages/{id}`.
        chat_id:
          type: string
          description: Id of the chat.
        attachment_id:
          type: string
          description: Id of the attachment that holds the card.
        status:
          type: string
          enum:
            - queued
          description: >-
            Always `queued`, even when iSnap holds the message. Read the message
            to know its `status`.
      required:
        - message_id
        - chat_id
        - attachment_id
        - status
      description: The message created by `POST /v1/chats/{chat_id}/share-contact-card`.
  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.