--- title: Group Photos | Sendblue Docs description: Set or clear the Apple-visible photo of an iMessage group chat via API --- Set or clear the Apple-visible photo of an existing iMessage group chat. The change is applied on the Sendblue line that serves the group and is verified on the device before the API responds: a success response means the photo (or cleared state) was observed as the actual Apple chat’s current photo, not merely requested. When more than one of your Sendblue numbers is in the group, an eligible number is selected automatically unless you choose one explicitly with `from_number`. The device-verified photo is stored as the group’s current photo, so `GET /api/v2/groups/{group_id}` returns its metadata together with a direct image URL. Replacing or clearing the photo replaces the current stored reference and attempts to delete the previous image; no photo history is exposed through the API, and photos changed directly by group participants in Messages are not synchronized back. > **Note:** Changing or clearing a photo requires the group to already have an iMessage chat (at least one iMessage has been sent or received in it), and the Sendblue line serving the group must be online and support group photo changes. SMS/RCS group chats do not support Apple group photos. ## Availability Group photos are supported on select Sendblue line types. The API checks compatibility automatically; requests for a group served by an ineligible Sendblue line return `unsupported_line`. ## Set or clear a group photo ``` POST /api/v2/groups/{group_id}/photo ``` Authenticate with either the standard [API credentials](/getting-started/credentials/index.md) headers (`sb-api-key-id` and `sb-api-secret-key`) or an account-scoped temporary API token returned by `POST /v3/auth/tokens`, sent as `Authorization: Bearer `. Line-scoped temporary API tokens cannot perform this account-wide mutation and receive the `403` response described below. #### Request Body To set a photo, either upload the image bytes as `multipart/form-data` or provide an image URL as JSON. Clearing uses JSON with `photo_url: null`. Multipart fields: | Parameter | Type | Required | Description | | ------------- | ------ | -------- | --------------------------------------------------------------------------------------------------- | | `file` | file | Yes | JPEG, PNG, or GIF image to set, at most 5 MB and 25 million aggregate decoded pixels | | `from_number` | string | No | Which of your Sendblue numbers performs the change. Omit to select an eligible number automatically | JSON fields: | Parameter | Type | Required | Description | | ------------- | ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `photo_url` | string \| `null` | Yes | Publicly downloadable `https` URL of a JPEG, PNG, or GIF image at most 5 MB and 25 million aggregate decoded pixels, or `null` to clear the photo | | `from_number` | string \| `null` | No | Which of your Sendblue numbers performs the change. Omit (or pass `null`) to select an eligible number automatically | The image bytes are fully validated before anything is sent to the device. Uploaded filenames and content types are not trusted. Images are limited to 25 million aggregate decoded pixels, including all frames of an animated GIF. For JSON requests, `photo_url` must be a direct, publicly resolvable `https` URL and redirects are not followed. Clearing a photo that is already absent succeeds without changing anything. When `from_number` is provided, it must be one of your Sendblue numbers serving this group’s iMessage chat, and the change is performed only through that number: if it cannot act — for example it is offline or does not support group photo changes — the request fails without falling back to another number. #### Example Request (upload) Terminal window ``` curl -X POST "https://api.sendblue.com/api/v2/groups/sb_group_xxxxxxxx/photo" \ -H "sb-api-key-id: YOUR_API_KEY" \ -H "sb-api-secret-key: YOUR_API_SECRET" \ -F "file=@team-photo.png" ``` Add `-F "from_number=+15550001111"` to require a specific Sendblue number. #### Example Request (set from URL) Terminal window ``` curl -X POST "https://api.sendblue.com/api/v2/groups/sb_group_xxxxxxxx/photo" \ -H "sb-api-key-id: YOUR_API_KEY" \ -H "sb-api-secret-key: YOUR_API_SECRET" \ -H "Content-Type: application/json" \ -d '{ "photo_url": "https://example.com/team-photo.png" }' ``` #### Example Request (clear) Terminal window ``` curl -X POST "https://api.sendblue.com/api/v2/groups/sb_group_xxxxxxxx/photo" \ -H "sb-api-key-id: YOUR_API_KEY" \ -H "sb-api-secret-key: YOUR_API_SECRET" \ -H "Content-Type: application/json" \ -d '{ "photo_url": null }' ``` #### Success Response (200) ``` { "status": "OK", "data": { "group_id": "sb_group_xxxxxxxx", "group_photo": { "photo_guid": "AAAAAAAA-1111-2222-3333-444444444444", "url": "https://storage.googleapis.com/..." }, "from_number": "+15550001111" } } ``` | Field | Type | Description | | ------------- | ---------------- | -------------------------------------------------------------------- | | `group_id` | string | The group whose requested photo state was verified | | `group_photo` | object \| `null` | The device-verified current photo; `null` when the photo was cleared | | `from_number` | string \| `null` | The Sendblue number that performed the change | `group_photo` fields: | Field | Type | Description | | ------------ | ------ | ----------------------------------------------------------- | | `photo_guid` | string | Device-verified identifier of the current Apple group photo | | `url` | string | Direct URL for downloading the current photo | The same `group_photo` object (or `null`) is returned by `GET /api/v2/groups/{group_id}`, so the verified photo stays retrievable after the mutation. If Sendblue cannot create the read URL, the request fails instead of returning non-retrievable photo metadata. ## Error Responses Errors generated by this endpoint use a machine-readable `error_code` alongside a human-readable `message`: ``` { "status": "ERROR", "error_code": "no_apple_chat", "message": "This group has no Apple iMessage chat yet. Send the first intended message to the group, then retry." } ``` Invalid credentials and rate-limit failures use the standard error envelope and do not include `error_code`: ``` { "status": "ERROR", "message": "Invalid Credentials" } ``` Requests with no credentials return: ``` { "message": "Did not get inputs for authorization" } ``` A line-scoped temporary-token rejection uses `code` instead of `error_code`: ``` { "status": "ERROR", "code": "temporary_token_account_scope_not_allowed", "message": "This line-scoped temporary token cannot access account-wide configuration." } ``` | HTTP Status | Code | Description | | ----------- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 400 | `invalid_photo_url` | `photo_url` is missing, not a string/null, or not a valid `https` URL | | 400 | `invalid_group_id` | `group_id` is not a valid Sendblue group identifier | | 400 | `invalid_from_number` | `from_number` is not a valid phone number string | | 401 | — | Authentication credentials are invalid | | 403 | — | Authentication credentials are missing | | 403 | `code`: `temporary_token_account_scope_not_allowed` | A line-scoped temporary token cannot perform this account-wide mutation | | 404 | `group_not_found` | No group with this `group_id` exists for your account | | 422 | `invalid_photo` | The multipart file is missing, the image could not be downloaded, exceeds 5 MB or 25 million aggregate decoded pixels, or is not a valid JPEG, PNG, or GIF image | | 422 | `no_apple_chat` | The group has no Apple iMessage chat yet — send the first intended message to the group, then retry | | 422 | `sms_rcs_only_group` | The group is served over SMS/RCS, which does not support Apple group photos | | 422 | `unsupported_line` | The Sendblue line serving this group does not support group photo changes | | 422 | `from_number_not_participant` | `from_number` is not one of your Sendblue numbers serving this group’s iMessage chat | | 429 | — | The request rate limit was exceeded — retry with backoff | | 500 | `internal_error` | The change was verified on the device but could not be recorded, or another internal failure occurred; retrying is safe | | 502 | `group_photo_update_failed` | The requested group photo state could not be verified and recorded | | 503 | — | Authentication is temporarily unavailable — retry shortly | | 503 | `line_offline` | The Sendblue line serving this group is offline — retry when it reconnects | | 503 | `group_photo_validation_busy` | A group photo is already being validated — retry shortly | | 503 | `group_photo_update_in_progress` | Another change is still settling for this group — retry shortly | | 504 | `verification_timeout` | The resulting state could not be verified in time and may or may not have applied. Wait five minutes before retrying the same request. | Failed requests are never replayed automatically. ## Privacy and retention Only the current verified photo is referenced and returned alongside the group. Its URL is unlisted but can be downloaded by anyone who has the exact URL while the image exists, so do not treat the URL as a credential or use group photos for sensitive content. Replacing or clearing the photo attempts to delete the superseded image, and the image is stored with a `no-store` cache policy so a successful deletion takes effect immediately for future downloads. Deletion is best-effort during replacement, so a previous URL can remain reachable if storage cleanup fails. Photos that existed before this feature are not backfilled. ## Propagation Apple propagates group photo changes to the other members’ devices; when their devices render the new photo is up to Apple and is not guaranteed by this API.