--- title: Location Sharing API | Sendblue Docs description: Request, read, and watch Find My location shares via the Sendblue API --- Request and read [Find My](https://support.apple.com/find-my) location shares from your recipients. Location Sharing lets your line ask a contact to share their location over iMessage, read the location of contacts already sharing with your line, and stream live location updates as they move. Location Sharing is available on dedicated Sendblue lines with iMessage enabled, and only works with iMessage recipients. There are three capabilities: | Capability | Endpoint | Description | | ----------- | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | **Request** | `POST /api/request-location` | Send a Find My location-request card to a contact. When they accept, the shared location is delivered to your `receive` webhook. | | **Read** | `GET /api/location` · `GET /api/location/{number}` | Read the current location of contacts already sharing with your line. | | **Watch** | `GET /api/location/{number}/watch` | Stream live location updates for a contact over Server-Sent Events (SSE). | A contact must be sharing their location with your line before **Read** and **Watch** return data. Use **Request** to prompt a contact who is not sharing yet — once they accept, they become a sharing contact for the duration they selected. --- ## Request a Location Share Send a Find My location-request card to a contact. This appears in their iMessage conversation as a native “Share My Location” request that they can accept and choose a duration for. ``` POST https://api.sendblue.com/api/request-location ``` ### Request Body | Parameter | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------------- | | `number` | string | Yes | Recipient phone number (E.164 format) | | `from_number` | string | Yes | Your Sendblue line (E.164 format) | ### Example Request Terminal window ``` curl -X POST 'https://api.sendblue.com/api/request-location' \ -H 'sb-api-key-id: YOUR_API_KEY' \ -H 'sb-api-secret-key: YOUR_API_SECRET' \ -H 'Content-Type: application/json' \ -d '{ "number": "+19175551234", "from_number": "+14155559876" }' ``` ### Node.js Example ``` const axios = require('axios'); await axios.post( 'https://api.sendblue.com/api/request-location', { number: '+19175551234', from_number: '+14155559876' }, { headers: { 'sb-api-key-id': 'YOUR_API_KEY', 'sb-api-secret-key': 'YOUR_API_SECRET', 'Content-Type': 'application/json' } } ); ``` ### Success Response (202) ``` { "status": "QUEUED", "message": "Location request accepted for dispatch", "number": "+19175551234", "message_handle": "6f1b2c3d4e5f6789", "uuid": "6f1b2c3d4e5f6789" } ``` The request is queued and sent to your line. The recipient sees a location-request card in iMessage. The response does **not** contain a location — the shared location is delivered later, via webhook, once the recipient accepts. ### Receiving the shared location When the recipient accepts the request, Sendblue POSTs an inbound message to your configured `receive` webhook. It’s a standard [inbound message webhook](/getting-started/webhooks#inbound-message-webhook/index.md) with two additions for location shares: `message_type` is `"location"`, and a `location` object is included. ``` { "is_outbound": false, "message_handle": "99DCC379-DD76-4712-BA65-11EFB33B8CD6", "from_number": "+19175551234", "message_type": "location", "location": { "latitude": 37.331413, "longitude": -122.030731, "accuracy": 12.5, "altitude": 8.2, "timestamp": "2025-12-12T15:41:20.000Z", "duration": "endOfDay" } } ``` The standard message fields (`number`, `to_number`, `sendblue_number`, `content`, `media_url`, and so on) are omitted above for brevity — see the [inbound message webhook](/getting-started/webhooks#inbound-message-webhook/index.md) reference for the full payload. #### `location` object | Field | Type | Description | | ----------- | ------------------- | --------------------------------------------------------------------------------------------------------------- | | `latitude` | number | Latitude in decimal degrees | | `longitude` | number | Longitude in decimal degrees | | `accuracy` | number \| undefined | Horizontal accuracy in meters, when available | | `altitude` | number \| undefined | Altitude in meters, when available | | `timestamp` | string \| undefined | ISO 8601 time the fix was captured | | `duration` | string \| undefined | The share duration the recipient selected, as an Apple-defined key (e.g. `oneHour`, `endOfDay`, `indefinitely`) | --- ## Read Current Shared Location Read the current location of contacts already sharing with your line. These endpoints return the latest cached fix — they do not send a request card. A contact must already be sharing (for example, after accepting a [location request](#request-a-location-share)). ### List all sharing contacts ``` GET https://api.sendblue.com/api/location ``` #### Query Parameters | Parameter | Type | Required | Description | | ------------- | ------ | -------- | --------------------------------- | | `from_number` | string | Yes | Your Sendblue line (E.164 format) | #### Example Request Terminal window ``` curl -X GET 'https://api.sendblue.com/api/location?from_number=%2B14155559876' \ -H 'sb-api-key-id: YOUR_API_KEY' \ -H 'sb-api-secret-key: YOUR_API_SECRET' ``` #### Success Response (200) ``` { "status": "OK", "from_number": "+14155559876", "locations": [ { "number": "+19175551234", "state": "shared_with_fix", "location": { "address": "1 Apple Park Way, Cupertino, CA", "latitude": 37.331413, "longitude": -122.030731, "accuracy": 12.5, "altitude": 8.2, "timestamp": "2025-12-12T15:41:20.000Z", "locationType": "live", "expiresAt": "2025-12-12T23:59:59.000Z" } } ] } ``` ### Get one contact’s location ``` GET https://api.sendblue.com/api/location/{number} ``` The `{number}` path parameter is the recipient phone number in E.164 format (URL-encode the leading `+` as `%2B`). #### Query Parameters | Parameter | Type | Required | Description | | ------------- | ------ | -------- | --------------------------------- | | `from_number` | string | Yes | Your Sendblue line (E.164 format) | #### Example Request Terminal window ``` curl -X GET 'https://api.sendblue.com/api/location/%2B19175551234?from_number=%2B14155559876' \ -H 'sb-api-key-id: YOUR_API_KEY' \ -H 'sb-api-secret-key: YOUR_API_SECRET' ``` #### Success Response (200) ``` { "status": "OK", "from_number": "+14155559876", "number": "+19175551234", "state": "shared_with_fix", "location": { "address": "1 Apple Park Way, Cupertino, CA", "latitude": 37.331413, "longitude": -122.030731, "accuracy": 12.5, "altitude": 8.2, "timestamp": "2025-12-12T15:41:20.000Z", "locationType": "live", "expiresAt": "2025-12-12T23:59:59.000Z" } } ``` If the contact is not sharing with your line, the response has `state: "not_shared"` and no `location` object. ### Read response schema | Field | Type | Description | | ---------- | -------------- | -------------------------------------------------------------------------- | | `number` | string \| null | The contact’s phone number | | `state` | string | One of `not_shared`, `shared_no_fix_yet`, or `shared_with_fix` (see below) | | `location` | object | Present when a fix is available (omitted for `not_shared`) | #### `state` values | Value | Meaning | | ------------------- | ------------------------------------------------------------------ | | `not_shared` | The contact is not sharing their location with your line | | `shared_no_fix_yet` | The contact is sharing, but no coordinates are available yet | | `shared_with_fix` | The contact is sharing and coordinates are available in `location` | #### `location` object | Field | Type | Description | | -------------- | -------------- | ------------------------------------------------------------------ | | `address` | string \| null | Reverse-geocoded address, when available | | `latitude` | number | Latitude in decimal degrees (omitted for a placeholder `0,0` fix) | | `longitude` | number | Longitude in decimal degrees (omitted for a placeholder `0,0` fix) | | `accuracy` | number | Horizontal accuracy in meters, when available | | `altitude` | number | Altitude in meters, when available | | `timestamp` | string | ISO 8601 time the fix was captured | | `locationType` | string | One of `live`, `shallow`, `legacy`, or `unknown` | | `expiresAt` | string | ISO 8601 time the share expires, when known | --- ## Watch Live Location Stream live location updates for a single contact over [Server-Sent Events (SSE)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events). The connection stays open and emits a `location` event each time a new fix arrives, until you close it, the contact stops sharing, or the line disconnects. ``` GET https://api.sendblue.com/api/location/{number}/watch ``` The `{number}` path parameter is the recipient phone number in E.164 format (URL-encode the leading `+` as `%2B`). The contact must already be sharing with your line. #### Query Parameters | Parameter | Type | Required | Description | | ------------- | ------ | -------- | --------------------------------- | | `from_number` | string | Yes | Your Sendblue line (E.164 format) | ### Example Request Terminal window ``` curl -N 'https://api.sendblue.com/api/location/%2B19175551234/watch?from_number=%2B14155559876' \ -H 'sb-api-key-id: YOUR_API_KEY' \ -H 'sb-api-secret-key: YOUR_API_SECRET' \ -H 'Accept: text/event-stream' ``` The `-N` (`--no-buffer`) flag keeps `curl` from buffering the stream so events print as they arrive. ### Event Stream The server responds with `Content-Type: text/event-stream` and emits named events: ``` event: ready data: {"status":"OK","from_number":"+14155559876","number":"+19175551234"} event: location data: {"from_number":"+14155559876","number":"+19175551234","state":"shared_with_fix","location":{"latitude":37.331413,"longitude":-122.030731,"accuracy":12.5,"altitude":8.2,"timestamp":"2025-12-12T15:41:20.000Z","locationType":"live"}} event: complete data: {"reason":"sharing_ended"} ``` | Event | Description | | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `ready` | The watch is established. Sent once, before the first fix. | | `location` | A new location fix. The `data` payload matches the [read response](#read-response-schema) (`number`, `state`, and an optional `location` object). | | `error` | The watch failed. The `data` payload contains a `message`. The stream then ends. | | `complete` | The watch ended normally. The `data` payload contains a `reason` such as `sharing_ended`, `authorization_revoked`, or `worker_disconnected`. | The stream also sends SSE comment lines (`: keepalive`) roughly every 15 seconds to keep the connection alive. Ignore any line beginning with `:`. ### Node.js Example ``` // Uses the built-in fetch (Node 18+) to read the SSE stream. const res = await fetch( 'https://api.sendblue.com/api/location/%2B19175551234/watch?from_number=%2B14155559876', { headers: { 'sb-api-key-id': 'YOUR_API_KEY', 'sb-api-secret-key': 'YOUR_API_SECRET', Accept: 'text/event-stream' } } ); const reader = res.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; while (true) { const { value, done } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const chunks = buffer.split('\n\n'); buffer = chunks.pop() ?? ''; for (const chunk of chunks) { const eventLine = chunk.split('\n').find((l) => l.startsWith('event:')); const dataLine = chunk.split('\n').find((l) => l.startsWith('data:')); if (!eventLine || !dataLine) continue; // skip keepalive comments const event = eventLine.slice('event:'.length).trim(); const data = JSON.parse(dataLine.slice('data:'.length).trim()); if (event === 'location') { console.log('New fix:', data.location); } else if (event === 'complete' || event === 'error') { break; } } } ``` To stop watching, close the HTTP connection. --- ## Error Responses | HTTP Status | Message / `message` field | Description | | ----------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | 400 | `You must specify a valid from_number...` | Missing or invalid `number` / `from_number` | | 400 | `Your account has been blocked...` | The account is blocked | | 401 | `Unauthorized` | Invalid or missing API credentials | | 403 | `Location request blocked by account policy` | The request was declined by account send policy (e.g. rate/line limits) | | 404 | `The from_number is not registered` | The `from_number` is not a line on your account | | 422 | `unsupported_on_this_line` | This line doesn’t support Location Sharing — it isn’t a dedicated iMessage line, is a shared line, or doesn’t have iMessage enabled | | 422 | `unsupported_recipient` | The recipient is known to be SMS/RCS; Find My requires an iMessage recipient | | 503 | `worker_unavailable` | The line is not currently connected | | 503 | `worker_version_unsupported` | This line doesn’t support Location Sharing yet; try again shortly | | 504 | `Timeout waiting for location read` | A read did not complete in time; retry | ``` { "status": "ERROR", "message": "Description of what went wrong" } ``` Some 422 responses include a `detail` field with a human-readable explanation. --- ## Notes 1. **Line support**: Location Sharing requires a dedicated Sendblue line with iMessage enabled. It isn’t available on shared lines, and some lines may not support it — those return a `422` or `503` error. 2. **iMessage only**: Find My location sharing is an iMessage feature. Requests to numbers known to be SMS/RCS are rejected with `unsupported_recipient`. Unknown numbers are allowed and resolved as iMessage on send. 3. **Request is asynchronous**: `POST /api/request-location` only sends the request card. The location arrives later, via your `receive` webhook, once the recipient accepts — and only if they accept. 4. **Read/Watch need an active share**: `GET /api/location*` and the watch endpoint only return data for contacts already sharing with your line. Use a location request to prompt a contact who is not sharing yet. 5. **Shares expire**: Recipients choose how long to share (for example, one hour, until end of day, or indefinitely). Once a share expires, the contact reverts to `not_shared` and watches for them end with a `complete` event. 6. **Requests count toward your limits**: A location request counts as a message and is subject to your account’s sending limits.