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

# Set a number's iMessage name and photo

> Sets the iMessage name and photo for one of your dedicated numbers. Upload the photo first with `POST /v1/attachments` (JPEG or PNG, up to 25 MiB); Blujay center-crops it to a square and resizes it for iMessage. `PUT` replaces the name. Omit `avatar_attachment_id` to keep the current photo, or send `null` to remove it so iMessage shows the number's initials.

`PUT` waits up to about 3 seconds for the number to publish the change. Usually it does, and you get `200` with `status: active`. If Apple is slow to take the change, you get `202` with `status: applying`; the number keeps going on its own, and `GET .../contact-card` about 15 seconds later reports `active`. There is no need to send the change again. The response confirms the number accepted the change, not that anyone has seen it yet. Blujay offers the card automatically with your next message in each iMessage chat and refreshes it once a day, so there is nothing to call. If you change it several times in a row, an iPhone may show an earlier version until the next daily refresh. People who saved the number as a contact keep their own card.

Refusals: Free Plan numbers `400 FEATURE_NOT_ENABLED`; a photo that isn't JPEG or PNG `415 UNSUPPORTED_ATTACHMENT_TYPE`; a photo still uploading `409 ATTACHMENT_NOT_READY`; a number that is busy with another change, not set up, or not reachable `503 PHONE_NUMBER_UNAVAILABLE` with `Retry-After`. After a `503`, read `GET .../contact-card` before retrying: if the number applied the change after all, the read reports it.




## OpenAPI

````yaml /api-reference/openapi.yaml put /v1/phone-numbers/{phone_number_id}/contact-card
openapi: 3.1.0
info:
  title: Blujay API
  version: 1.0.0
  description: >-
    The Blujay API lets you send and receive iMessage, RCS and SMS messages from
    your

    Blujay phone numbers, manage contacts, and receive real-time webhooks.


    Every response uses the same JSON envelope and carries a `trace_id` you can
    share

    with support. Authenticate with your workspace API key.
servers:
  - url: https://api.blujay.io
    description: Production
security:
  - PublicApiKey: []
tags:
  - name: Auth
    description: Check which workspace and credential a request is using.
  - name: Chats
    description: >-
      Direct and group conversations between your Blujay numbers and your
      contacts.
  - name: Messages
    description: Send, read, edit, unsend and react to messages.
  - name: Phone Numbers
    description: >-
      Your Blujay numbers, whether each can send right now, and their live
      sending limits.
  - name: Capability
    description: >-
      Check whether a phone number can be reached by iMessage, RCS or SMS before
      you send.
  - name: Contacts
    description: Create, find, update, tag, assign and opt out contacts.
  - name: vCards
    description: >-
      Saved contact cards that you can send in a message. Recipients get a
      native contact card over iMessage, SMS or RCS.
  - name: Team Members
    description: People in your workspace you can assign contacts to.
  - name: Workflows
    description: Enroll contacts in workflows you start manually.
  - name: Attachments
    description: >-
      Upload files once and reuse them in messages, or download files from
      messages.
  - name: Calls
    description: Download call recordings referenced by call webhooks.
  - name: Webhooks
    description: Subscribe to events, inspect delivery attempts and verify signatures.
  - name: Partners
    description: >-
      For Blujay reseller partners. A partner runs many customer workspaces
      through one integration. Partner keys carry a role (`admin` or
      `messaging`) and name a customer in the `Blujay-Workspace` header.
paths:
  /v1/phone-numbers/{phone_number_id}/contact-card:
    put:
      tags:
        - Phone Numbers
      summary: Set a number's iMessage name and photo
      description: >
        Sets the iMessage name and photo for one of your dedicated numbers.
        Upload the photo first with `POST /v1/attachments` (JPEG or PNG, up to
        25 MiB); Blujay center-crops it to a square and resizes it for iMessage.
        `PUT` replaces the name. Omit `avatar_attachment_id` to keep the current
        photo, or send `null` to remove it so iMessage shows the number's
        initials.


        `PUT` waits up to about 3 seconds for the number to publish the change.
        Usually it does, and you get `200` with `status: active`. If Apple is
        slow to take the change, you get `202` with `status: applying`; the
        number keeps going on its own, and `GET .../contact-card` about 15
        seconds later reports `active`. There is no need to send the change
        again. The response confirms the number accepted the change, not that
        anyone has seen it yet. Blujay offers the card automatically with your
        next message in each iMessage chat and refreshes it once a day, so there
        is nothing to call. If you change it several times in a row, an iPhone
        may show an earlier version until the next daily refresh. People who
        saved the number as a contact keep their own card.


        Refusals: Free Plan numbers `400 FEATURE_NOT_ENABLED`; a photo that
        isn't JPEG or PNG `415 UNSUPPORTED_ATTACHMENT_TYPE`; a photo still
        uploading `409 ATTACHMENT_NOT_READY`; a number that is busy with another
        change, not set up, or not reachable `503 PHONE_NUMBER_UNAVAILABLE` with
        `Retry-After`. After a `503`, read `GET .../contact-card` before
        retrying: if the number applied the change after all, the read reports
        it.
      operationId: putPublicPhoneNumberContactCard
      parameters:
        - $ref: '#/components/parameters/PhoneNumberId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContactCardProfileInput'
            examples:
              supportLine:
                summary: Name and a new photo
                value:
                  first_name: Acme
                  last_name: Support
                  avatar_attachment_id: att_abc123
              nameOnly:
                summary: Change the name, keep the photo
                value:
                  first_name: Acme
                  last_name: Sales
              removePhoto:
                summary: Remove the photo
                value:
                  first_name: Acme
                  last_name: Support
                  avatar_attachment_id: null
      responses:
        '200':
          description: The number published the change. `status` is `active`.
          headers:
            X-Trace-ID:
              $ref: '#/components/headers/XTraceID'
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    required:
                      - data
                    properties:
                      data:
                        $ref: '#/components/schemas/ContactCardProfile'
              examples:
                active:
                  value:
                    status: success
                    data:
                      phone_number_id: phone_abc123
                      status: active
                      first_name: Acme
                      last_name: Support
                      display_name: Acme Support
                      photo:
                        download_url: /v1/phone-numbers/phone_abc123/contact-card/photo
                        content_type: image/png
                      updated_at: '2026-10-22T17:00:01.000Z'
                    trace_id: trc_8f2a1c0e4b7d
                    timestamp: '2026-10-22T17:00:02.000Z'
        '202':
          description: >-
            The change was accepted and the number is still publishing it.
            `status` is `applying`; read `GET .../contact-card` about 15 seconds
            later to confirm `active`.
          headers:
            X-Trace-ID:
              $ref: '#/components/headers/XTraceID'
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    required:
                      - data
                    properties:
                      data:
                        $ref: '#/components/schemas/ContactCardProfile'
              examples:
                applying:
                  value:
                    status: success
                    data:
                      phone_number_id: phone_abc123
                      status: applying
                      first_name: Acme
                      last_name: Support
                      display_name: Acme Support
                      photo:
                        download_url: /v1/phone-numbers/phone_abc123/contact-card/photo
                        content_type: image/png
                      updated_at: '2026-10-22T17:00:00.000Z'
                    trace_id: trc_8f2a1c0e4b7d
                    timestamp: '2026-10-22T17:00:01.000Z'
        '400':
          description: >-
            The body failed validation (`VALIDATION_ERROR`), the photo could not
            be processed (`VALIDATION_ERROR`), or the number is on the Free Plan
            (`FEATURE_NOT_ENABLED`).
          headers:
            X-Trace-ID:
              $ref: '#/components/headers/XTraceID'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: >-
            The phone number or the attachment is not in your workspace
            (`NOT_FOUND`).
          headers:
            X-Trace-ID:
              $ref: '#/components/headers/XTraceID'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '409':
          description: >-
            The attachment's file has not finished uploading
            (`ATTACHMENT_NOT_READY`).
          headers:
            X-Trace-ID:
              $ref: '#/components/headers/XTraceID'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '413':
          description: The photo is over 25 MiB (`ATTACHMENT_TOO_LARGE`).
          headers:
            X-Trace-ID:
              $ref: '#/components/headers/XTraceID'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '415':
          description: >-
            The attachment is not a JPEG or PNG image
            (`UNSUPPORTED_ATTACHMENT_TYPE`).
          headers:
            X-Trace-ID:
              $ref: '#/components/headers/XTraceID'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '503':
          $ref: '#/components/responses/PhoneNumberUnavailable'
      security:
        - PublicApiKey: []
components:
  parameters:
    PhoneNumberId:
      name: phone_number_id
      in: path
      required: true
      schema:
        $ref: '#/components/schemas/PhoneNumberId'
  schemas:
    ContactCardProfileInput:
      type: object
      additionalProperties: false
      description: >-
        The iMessage name and photo to set on a dedicated number. `PUT` replaces
        the name. Omit `avatar_attachment_id` to keep the current photo; send
        `null` to remove it.
      required:
        - first_name
      properties:
        first_name:
          type: string
          minLength: 1
          maxLength: 80
          examples:
            - Acme
        last_name:
          type: string
          maxLength: 80
          description: Optional. Blank when omitted.
          examples:
            - Support
        avatar_attachment_id:
          description: >-
            A ready JPEG or PNG attachment from `POST /v1/attachments`, up to 25
            MiB. Blujay center-crops it to a square and resizes it to 512 by 512
            for iMessage. `null` removes the photo.
          oneOf:
            - $ref: '#/components/schemas/AttachmentId'
            - type: 'null'
    SuccessEnvelope:
      type: object
      description: Wraps every successful response. The result is in `data`.
      required:
        - status
        - trace_id
        - timestamp
      properties:
        status:
          type: string
          enum:
            - success
        trace_id:
          $ref: '#/components/schemas/TraceId'
        timestamp:
          type: string
          examples:
            - '2026-10-14T17:01:00.000Z'
          format: date-time
        client_request_id:
          type: string
          description: >-
            Your `X-Request-ID` header value, echoed back when you send one. Not
            included on the send responses (`POST /v1/chats` and `POST
            /v1/chats/{chat_id}/messages`); keep your own mapping from your
            request ID to the returned message ID there.
    ContactCardProfile:
      type: object
      additionalProperties: false
      description: >-
        The iMessage name and photo a dedicated number offers to people who
        don't have it saved.
      required:
        - phone_number_id
        - status
        - first_name
        - last_name
        - display_name
        - photo
        - updated_at
      properties:
        phone_number_id:
          $ref: '#/components/schemas/PhoneNumberId'
        status:
          type: string
          enum:
            - not_set
            - applying
            - active
            - unavailable
          description: >
            - `not_set`: the number shares no name and photo.

            - `applying`: a change was accepted and the number is finishing it;
            read again in a few seconds.

            - `active`: the name and photo are in place.

            - `unavailable`: the number can't report its state right now.
        first_name:
          type:
            - string
            - 'null'
          examples:
            - Acme
        last_name:
          type:
            - string
            - 'null'
          examples:
            - Support
        display_name:
          type:
            - string
            - 'null'
          description: The name recipients see, built from the first and last name.
          examples:
            - Acme Support
        photo:
          description: '`null` when the number has no photo (iMessage shows initials).'
          oneOf:
            - type: object
              additionalProperties: false
              required:
                - download_url
                - content_type
              properties:
                download_url:
                  type: string
                  description: >-
                    Path of `GET
                    /v1/phone-numbers/{phone_number_id}/contact-card/photo`;
                    send it with your API key.
                  examples:
                    - /v1/phone-numbers/phone_abc123/contact-card/photo
                content_type:
                  type: string
                  enum:
                    - image/png
            - type: 'null'
        updated_at:
          type:
            - string
            - 'null'
          format: date-time
          description: When the number last changed its name and photo state.
    ErrorEnvelope:
      type: object
      description: >-
        Wraps every error response. Branch on `error.code`, and include
        `trace_id` when you contact support.
      required:
        - status
        - error
        - trace_id
        - timestamp
      properties:
        status:
          const: error
        error:
          $ref: '#/components/schemas/PublicError'
        trace_id:
          $ref: '#/components/schemas/TraceId'
        timestamp:
          type: string
          examples:
            - '2026-10-14T17:01:00.000Z'
          format: date-time
        client_request_id:
          type: string
          description: Your `X-Request-ID` header value, echoed back when you send one.
    PhoneNumberId:
      type: string
      examples:
        - phone_abc123
      pattern: ^phone_[A-Za-z0-9_-]+$
    AttachmentId:
      type: string
      examples:
        - att_abc123
      pattern: ^att_[A-Za-z0-9_-]+$
    TraceId:
      type: string
      examples:
        - trc_8f2a1c0e4b7d
      pattern: ^trc_[A-Za-z0-9_-]+$
    PublicError:
      type: object
      required:
        - code
        - message
        - type
        - http_status
        - retryable
        - doc_url
        - details
      properties:
        code:
          type: string
          description: Stable, machine-readable error code. Branch on this value.
          examples:
            - RATE_LIMIT_EXCEEDED
            - QUEUE_UNAVAILABLE
            - FEATURE_NOT_ENABLED
        message:
          type: string
          description: Human-readable explanation. It can change, so don't parse it.
        type:
          type: string
          description: Broad error category.
          examples:
            - validation
            - auth
            - idempotency
            - rate_limit
            - queue
            - feature_gate
        http_status:
          type: integer
          description: The HTTP status code of the response.
          enum:
            - 400
            - 401
            - 403
            - 404
            - 409
            - 413
            - 415
            - 422
            - 429
            - 500
            - 502
            - 503
        retryable:
          type: boolean
          description: Whether sending the same request again later can succeed.
        doc_url:
          type: string
          format: uri
          description: Link to documentation for this error code.
        details:
          type: array
          description: >-
            Extra structured context, such as the fields that failed validation
            or the limit you hit.
          items:
            type: object
            additionalProperties: true
        retry_after:
          type: integer
          minimum: 1
          description: >-
            Seconds to wait before retrying. Also sent as the `Retry-After`
            header.
        reset_at:
          type: string
          examples:
            - '2026-10-14T17:01:00.000Z'
          format: date-time
          description: When the limit you hit resets.
  headers:
    XTraceID:
      description: >-
        Unique ID for this request. It matches `trace_id` in the body. Include
        it when you contact Blujay support.
      schema:
        $ref: '#/components/schemas/TraceId'
    RetryAfter:
      description: Seconds to wait before retrying.
      schema:
        type: integer
        minimum: 1
  responses:
    Unauthorized:
      description: >-
        The API key is missing or invalid (`UNAUTHORIZED`), revoked
        (`API_KEY_REVOKED`) or expired (`API_KEY_EXPIRED`).
      headers:
        X-Trace-ID:
          $ref: '#/components/headers/XTraceID'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    PhoneNumberUnavailable:
      description: >-
        The messaging network couldn't accept the request right now
        (`PHONE_NUMBER_UNAVAILABLE`). Nothing was changed. Retry after
        `Retry-After` seconds.
      headers:
        X-Trace-ID:
          $ref: '#/components/headers/XTraceID'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          examples:
            phoneNumberUnavailable:
              value:
                status: error
                error:
                  code: PHONE_NUMBER_UNAVAILABLE
                  message: This service is temporarily unavailable
                  type: phone_number
                  http_status: 503
                  retryable: true
                  doc_url: https://docs.blujay.io/errors/phone-number-unavailable
                  details: []
                  retry_after: 30
                trace_id: trc_8f2a1c0e4b7d
                timestamp: '2026-10-14T00:00:00.000Z'
  securitySchemes:
    PublicApiKey:
      type: http
      scheme: bearer
      bearerFormat: blu_...
      description: >
        Your workspace API key, sent as `Authorization: Bearer blu_...`.
        Workspace admins can find it in the Blujay app under **API**.


        An API key has full access to your workspace and works on every
        endpoint. There is no test mode: every key sends real messages and
        changes real data, so keep it secret and use it only from your server.


        Each API key can make 600 read requests and 120 write requests per
        minute, plus 60 attachment requests per minute. Over a limit you get
        `429 RATE_LIMIT_EXCEEDED` with `Retry-After`.


        **Reseller partner keys.** A partner key acts for one of the partner's
        customers when the request names it in the `Blujay-Workspace` header
        (the customer's `workspace_` ID). Without the header it reaches only
        `/v1/partner/*` and `/v1/me`. Each customer gets the limits above for
        each partner key, and all of a partner's requests together get ten times
        those limits; a `429` says which in `error.details[0].scope`
        (`partner_customer` or `partner`). A `messaging` key can read anything
        for a customer and change chats, messages, contacts, attachments and
        vCards; everything else needs an `admin` key (`403
        PARTNER_ROLE_FORBIDDEN`). Paused or closing customers can be read but
        not changed (`409 CUSTOMER_PAUSED` or `CUSTOMER_CLOSING`). Any other
        workspace ID in the header returns `404 NOT_FOUND`; using the header
        with any other credential returns `400 WORKSPACE_HEADER_NOT_ALLOWED`.

````

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