Developers
Shutter Speed API and MCP
Read and edit your saved locations, lists and location photos from your own scripts, or connect an AI assistant like Claude or ChatGPT. Available on Pro.
- Base URL
- https://api.shutterspeed.art/api/v1
- MCP
- https://api.shutterspeed.art/api/v1/mcp
All requests and responses are JSON. Timestamps are ISO 8601 in UTC. Fields an endpoint doesn't take are refused with a 400, not ignored.
A successful call returns 201 for a create or add, 204 with no body for a delete, and 200 for everything else. Over MCP, the delete tools return {"ok":true}.
Authentication
API keys
Create a key in the app under Settings, Account, API & Connected Apps. The full key is shown once, right after you create it, so copy it somewhere safe. Keys start with ss_live_.
You can have up to 10 keys. Revoke one from the same screen and it stops working right away.
Send the key as a Bearer token on every request:
curl https://api.shutterspeed.art/api/v1/locations \
-H "Authorization: Bearer ss_live_..."OAuth for AI assistants
Assistants that support MCP's OAuth sign-in don't need a key. Add the MCP server URL as a custom connector, sign in to your Shutter Speed account when asked, and choose Allow.
If an assistant asks you for an OAuth client ID and secret, or can't finish signing in, use an API key instead wherever it lets you set a request header.
Each connected assistant is listed under Settings, Account, API & Connected Apps, where you can disconnect it.
Both kinds of access need an active Pro subscription. If Pro ends, requests return 403 PRO_REQUIRED until you renew.
Rate limits
Up to 120 requests every 60 seconds per account. Past that you get a 429 with code RATE_LIMITED and a Retry-After header with the seconds to wait.
Pagination
List endpoints take skip and limit (1 to 100, default 50) and return the page with the total count:
{
"data": [
"..."
],
"skip": 0,
"limit": 50,
"total": 128
}Locations
The spots you've saved in Location Planner, with their address, notes, lists and photos.
GET/locations
MCP: list_locationsList your saved locations, newest first. Filter by list or by a text match on name, address, city, state, country or notes.
- Read only
| Name | Description |
|---|---|
| skipinteger, query, optional | Rows to skip. Default 0. |
| limitinteger, query, optional | Rows to return, 1 to 100. Default 50. |
| listIduuid, query, optional | Only locations in this list. |
| qstring, query, optional | Case-insensitive text search across name, address, city, state, country and notes. |
Request
curl https://api.shutterspeed.art/api/v1/locations \
-H "Authorization: Bearer ss_live_..."Response 200 OK
{
"data": [
{
"id": "3f2a9c1e-7b4d-4e2a-9c61-0d8e5f4a7b21",
"name": "Gapstow Bridge",
"latitude": 40.7661,
"longitude": -73.9742,
"address": "Gapstow Bridge, New York, NY 10019",
"streetNumber": null,
"street": null,
"unit": null,
"city": "New York",
"state": "NY",
"country": "US",
"zipCode": "10019",
"notes": "Best at sunrise, from the west bank.",
"googlePlaceId": "ChIJs1Ugm_ZYwokRu3kzF0cVxt4",
"wikipediaPageId": null,
"createdAt": "2026-09-12T14:03:27.000Z",
"listIds": [
"b81d47e0-2c3a-4f19-8e6d-5a9c0b7e3f42"
],
"images": [
{
"id": "e4c7a2b9-1d6f-4a83-b5e0-9f2c8d1a6b73",
"url": "https://d1dkd0xzfyqgiq.cloudfront.net/eyJidWNrZXQiOi...",
"thumbnailUrl": "https://d1dkd0xzfyqgiq.cloudfront.net/eyJidWNrZXQiOi...",
"width": 4032,
"height": 3024,
"uploaderOwnsRights": true,
"position": 0
}
]
}
],
"skip": 0,
"limit": 50,
"total": 1
}GET/locations/:id
MCP: get_locationGet one of your saved locations, with its lists and photos.
- Read only
| Name | Description |
|---|---|
| iduuid, path, required | The location's id (path). |
Request
curl https://api.shutterspeed.art/api/v1/locations/3f2a9c1e-7b4d-4e2a-9c61-0d8e5f4a7b21 \
-H "Authorization: Bearer ss_live_..."Response 200 OK
{
"id": "3f2a9c1e-7b4d-4e2a-9c61-0d8e5f4a7b21",
"name": "Gapstow Bridge",
"latitude": 40.7661,
"longitude": -73.9742,
"address": "Gapstow Bridge, New York, NY 10019",
"streetNumber": null,
"street": null,
"unit": null,
"city": "New York",
"state": "NY",
"country": "US",
"zipCode": "10019",
"notes": "Best at sunrise, from the west bank.",
"googlePlaceId": "ChIJs1Ugm_ZYwokRu3kzF0cVxt4",
"wikipediaPageId": null,
"createdAt": "2026-09-12T14:03:27.000Z",
"listIds": [
"b81d47e0-2c3a-4f19-8e6d-5a9c0b7e3f42"
],
"images": [
{
"id": "e4c7a2b9-1d6f-4a83-b5e0-9f2c8d1a6b73",
"url": "https://d1dkd0xzfyqgiq.cloudfront.net/eyJidWNrZXQiOi...",
"thumbnailUrl": "https://d1dkd0xzfyqgiq.cloudfront.net/eyJidWNrZXQiOi...",
"width": 4032,
"height": 3024,
"uploaderOwnsRights": true,
"position": 0
}
]
}POST/locations
MCP: create_locationSave a new location from a name and coordinates.
| Name | Description |
|---|---|
| namestring, body, required | Display name. |
| latitudenumber, body, required | -90 to 90. |
| longitudenumber, body, required | -180 to 180. |
| addressstring, body, optional | Address part. |
| streetNumberstring, body, optional | Address part. |
| streetstring, body, optional | Address part. |
| unitstring, body, optional | Address part. |
| citystring, body, optional | Address part. |
| statestring, body, optional | Address part. |
| countrystring, body, optional | Address part. |
| zipCodestring, body, optional | Address part. |
| notesstring, body, optional | Private notes, up to 5000 characters. |
| googlePlaceIdstring, body, optional | Google Place id, if known. |
| wikipediaPageIdinteger, body, optional | Wikipedia page id, if known. |
| listIdsuuid[], body, optional | Lists to add it to. |
Request
curl -X POST https://api.shutterspeed.art/api/v1/locations \
-H "Authorization: Bearer ss_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Gapstow Bridge",
"latitude": 40.7661,
"longitude": -73.9742
}'Response 201 Created
{
"id": "3f2a9c1e-7b4d-4e2a-9c61-0d8e5f4a7b21",
"name": "Gapstow Bridge",
"latitude": 40.7661,
"longitude": -73.9742,
"address": "Gapstow Bridge, New York, NY 10019",
"streetNumber": null,
"street": null,
"unit": null,
"city": "New York",
"state": "NY",
"country": "US",
"zipCode": "10019",
"notes": "Best at sunrise, from the west bank.",
"googlePlaceId": "ChIJs1Ugm_ZYwokRu3kzF0cVxt4",
"wikipediaPageId": null,
"createdAt": "2026-09-12T14:03:27.000Z",
"listIds": [
"b81d47e0-2c3a-4f19-8e6d-5a9c0b7e3f42"
],
"images": [
{
"id": "e4c7a2b9-1d6f-4a83-b5e0-9f2c8d1a6b73",
"url": "https://d1dkd0xzfyqgiq.cloudfront.net/eyJidWNrZXQiOi...",
"thumbnailUrl": "https://d1dkd0xzfyqgiq.cloudfront.net/eyJidWNrZXQiOi...",
"width": 4032,
"height": 3024,
"uploaderOwnsRights": true,
"position": 0
}
]
}PATCH/locations/:id
MCP: update_locationChange a saved location's name, coordinates, address or notes.
- Idempotent
| Name | Description |
|---|---|
| iduuid, path, required | The location's id (path). |
| namestring, body, optional | Display name. |
| latitudenumber, body, optional | -90 to 90. |
| longitudenumber, body, optional | -180 to 180. |
| addressstring | null, body, optional | Address part. null clears it. |
| streetNumberstring | null, body, optional | Address part. null clears it. |
| streetstring | null, body, optional | Address part. null clears it. |
| unitstring | null, body, optional | Address part. null clears it. |
| citystring | null, body, optional | Address part. null clears it. |
| statestring | null, body, optional | Address part. null clears it. |
| countrystring | null, body, optional | Address part. null clears it. |
| zipCodestring | null, body, optional | Address part. null clears it. |
| notesstring | null, body, optional | Private notes. null clears them. |
Request
curl -X PATCH https://api.shutterspeed.art/api/v1/locations/3f2a9c1e-7b4d-4e2a-9c61-0d8e5f4a7b21 \
-H "Authorization: Bearer ss_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Gapstow Bridge"
}'Response 200 OK
{
"id": "3f2a9c1e-7b4d-4e2a-9c61-0d8e5f4a7b21",
"name": "Gapstow Bridge",
"latitude": 40.7661,
"longitude": -73.9742,
"address": "Gapstow Bridge, New York, NY 10019",
"streetNumber": null,
"street": null,
"unit": null,
"city": "New York",
"state": "NY",
"country": "US",
"zipCode": "10019",
"notes": "Best at sunrise, from the west bank.",
"googlePlaceId": "ChIJs1Ugm_ZYwokRu3kzF0cVxt4",
"wikipediaPageId": null,
"createdAt": "2026-09-12T14:03:27.000Z",
"listIds": [
"b81d47e0-2c3a-4f19-8e6d-5a9c0b7e3f42"
],
"images": [
{
"id": "e4c7a2b9-1d6f-4a83-b5e0-9f2c8d1a6b73",
"url": "https://d1dkd0xzfyqgiq.cloudfront.net/eyJidWNrZXQiOi...",
"thumbnailUrl": "https://d1dkd0xzfyqgiq.cloudfront.net/eyJidWNrZXQiOi...",
"width": 4032,
"height": 3024,
"uploaderOwnsRights": true,
"position": 0
}
]
}DELETE/locations/:id
MCP: delete_locationDelete a saved location and its photos.
- Destructive
- Idempotent
| Name | Description |
|---|---|
| iduuid, path, required | The location's id (path). |
Request
curl -X DELETE https://api.shutterspeed.art/api/v1/locations/3f2a9c1e-7b4d-4e2a-9c61-0d8e5f4a7b21 \
-H "Authorization: Bearer ss_live_..."Response 204 No Content
No body.
PUT/locations/:id/lists
MCP: set_location_listsReplace the set of lists a location belongs to.
- Destructive
- Idempotent
| Name | Description |
|---|---|
| iduuid, path, required | The location's id (path). |
| listIdsuuid[], body, required | Every list it should be in. [] removes it from all. |
Request
curl -X PUT https://api.shutterspeed.art/api/v1/locations/3f2a9c1e-7b4d-4e2a-9c61-0d8e5f4a7b21/lists \
-H "Authorization: Bearer ss_live_..." \
-H "Content-Type: application/json" \
-d '{
"listIds": [
"b81d47e0-2c3a-4f19-8e6d-5a9c0b7e3f42"
]
}'Response 200 OK
{
"id": "3f2a9c1e-7b4d-4e2a-9c61-0d8e5f4a7b21",
"name": "Gapstow Bridge",
"latitude": 40.7661,
"longitude": -73.9742,
"address": "Gapstow Bridge, New York, NY 10019",
"streetNumber": null,
"street": null,
"unit": null,
"city": "New York",
"state": "NY",
"country": "US",
"zipCode": "10019",
"notes": "Best at sunrise, from the west bank.",
"googlePlaceId": "ChIJs1Ugm_ZYwokRu3kzF0cVxt4",
"wikipediaPageId": null,
"createdAt": "2026-09-12T14:03:27.000Z",
"listIds": [
"b81d47e0-2c3a-4f19-8e6d-5a9c0b7e3f42"
],
"images": [
{
"id": "e4c7a2b9-1d6f-4a83-b5e0-9f2c8d1a6b73",
"url": "https://d1dkd0xzfyqgiq.cloudfront.net/eyJidWNrZXQiOi...",
"thumbnailUrl": "https://d1dkd0xzfyqgiq.cloudfront.net/eyJidWNrZXQiOi...",
"width": 4032,
"height": 3024,
"uploaderOwnsRights": true,
"position": 0
}
]
}Lists
The lists you group locations into. Deleting a list keeps its locations.
GET/lists
MCP: list_listsList your location lists with how many locations each holds.
- Read only
| Name | Description |
|---|---|
| skipinteger, query, optional | Rows to skip. Default 0. |
| limitinteger, query, optional | Rows to return, 1 to 100. Default 50. |
Request
curl https://api.shutterspeed.art/api/v1/lists \
-H "Authorization: Bearer ss_live_..."Response 200 OK
{
"data": [
{
"id": "b81d47e0-2c3a-4f19-8e6d-5a9c0b7e3f42",
"name": "Central Park",
"locationCount": 12
}
],
"skip": 0,
"limit": 50,
"total": 1
}GET/lists/:id
MCP: get_listGet one of your lists and every location in it (not paginated).
- Read only
| Name | Description |
|---|---|
| iduuid, path, required | The list's id (path). |
Request
curl https://api.shutterspeed.art/api/v1/lists/b81d47e0-2c3a-4f19-8e6d-5a9c0b7e3f42 \
-H "Authorization: Bearer ss_live_..."Response 200 OK
{
"id": "b81d47e0-2c3a-4f19-8e6d-5a9c0b7e3f42",
"name": "Central Park",
"locationCount": 1,
"locations": [
{
"id": "3f2a9c1e-7b4d-4e2a-9c61-0d8e5f4a7b21",
"name": "Gapstow Bridge",
"latitude": 40.7661,
"longitude": -73.9742,
"address": "Gapstow Bridge, New York, NY 10019",
"streetNumber": null,
"street": null,
"unit": null,
"city": "New York",
"state": "NY",
"country": "US",
"zipCode": "10019",
"notes": "Best at sunrise, from the west bank.",
"googlePlaceId": "ChIJs1Ugm_ZYwokRu3kzF0cVxt4",
"wikipediaPageId": null,
"createdAt": "2026-09-12T14:03:27.000Z",
"listIds": [
"b81d47e0-2c3a-4f19-8e6d-5a9c0b7e3f42"
],
"images": [
{
"id": "e4c7a2b9-1d6f-4a83-b5e0-9f2c8d1a6b73",
"url": "https://d1dkd0xzfyqgiq.cloudfront.net/eyJidWNrZXQiOi...",
"thumbnailUrl": "https://d1dkd0xzfyqgiq.cloudfront.net/eyJidWNrZXQiOi...",
"width": 4032,
"height": 3024,
"uploaderOwnsRights": true,
"position": 0
}
]
}
]
}POST/lists
MCP: create_listCreate a new location list.
| Name | Description |
|---|---|
| namestring, body, required | List name. |
Request
curl -X POST https://api.shutterspeed.art/api/v1/lists \
-H "Authorization: Bearer ss_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Central Park"
}'Response 201 Created
{
"id": "b81d47e0-2c3a-4f19-8e6d-5a9c0b7e3f42",
"name": "Central Park",
"locationCount": 0
}PATCH/lists/:id
MCP: update_listRename a list.
- Idempotent
| Name | Description |
|---|---|
| iduuid, path, required | The list's id (path). |
| namestring, body, required | New name. |
Request
curl -X PATCH https://api.shutterspeed.art/api/v1/lists/b81d47e0-2c3a-4f19-8e6d-5a9c0b7e3f42 \
-H "Authorization: Bearer ss_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Central Park"
}'Response 200 OK
{
"id": "b81d47e0-2c3a-4f19-8e6d-5a9c0b7e3f42",
"name": "Central Park",
"locationCount": 12
}DELETE/lists/:id
MCP: delete_listDelete a list. The locations in it are kept.
- Destructive
- Idempotent
| Name | Description |
|---|---|
| iduuid, path, required | The list's id (path). |
Request
curl -X DELETE https://api.shutterspeed.art/api/v1/lists/b81d47e0-2c3a-4f19-8e6d-5a9c0b7e3f42 \
-H "Authorization: Bearer ss_live_..."Response 204 No Content
No body.
Images
Photos on a location, in the order the app shows them.
GET/locations/:id/images
MCP: list_location_imagesList a location's photos in display order, with full-size and thumbnail URLs.
- Read only
| Name | Description |
|---|---|
| iduuid, path, required | The location's id (path). |
Request
curl https://api.shutterspeed.art/api/v1/locations/3f2a9c1e-7b4d-4e2a-9c61-0d8e5f4a7b21/images \
-H "Authorization: Bearer ss_live_..."Response 200 OK
[
{
"id": "a19f3c5d-8e2b-4c70-9d4a-6b1e7f0c2d58",
"url": "https://d1dkd0xzfyqgiq.cloudfront.net/eyJidWNrZXQiOi...",
"thumbnailUrl": "https://d1dkd0xzfyqgiq.cloudfront.net/eyJidWNrZXQiOi...",
"width": 4032,
"height": 3024,
"uploaderOwnsRights": true,
"position": 0
},
{
"id": "e4c7a2b9-1d6f-4a83-b5e0-9f2c8d1a6b73",
"url": "https://d1dkd0xzfyqgiq.cloudfront.net/eyJidWNrZXQiOi...",
"thumbnailUrl": "https://d1dkd0xzfyqgiq.cloudfront.net/eyJidWNrZXQiOi...",
"width": 4032,
"height": 3024,
"uploaderOwnsRights": true,
"position": 1
}
]POST/locations/:id/images/from-url
MCP: add_location_image_from_urlAdd a photo to a location by giving a public https image URL. The server downloads and stores it.
- Fetches a URL
| Name | Description |
|---|---|
| iduuid, path, required | The location's id (path). |
| urlstring, body, required | Public https URL of a JPEG, PNG, WebP or HEIC image, up to 10 MB. |
| uploaderOwnsRightsboolean, body, required | true if you took the photo or have the rights to it. |
Request
curl -X POST https://api.shutterspeed.art/api/v1/locations/3f2a9c1e-7b4d-4e2a-9c61-0d8e5f4a7b21/images/from-url \
-H "Authorization: Bearer ss_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/photos/gapstow-bridge.jpg",
"uploaderOwnsRights": true
}'Response 201 Created
{
"id": "e4c7a2b9-1d6f-4a83-b5e0-9f2c8d1a6b73",
"url": "https://d1dkd0xzfyqgiq.cloudfront.net/eyJidWNrZXQiOi...",
"thumbnailUrl": "https://d1dkd0xzfyqgiq.cloudfront.net/eyJidWNrZXQiOi...",
"width": 4032,
"height": 3024,
"uploaderOwnsRights": true,
"position": 0
}POST/locations/:id/images/upload-url
REST onlyStart a direct upload: returns an image id and a presigned URL to PUT the file to, valid for 5 minutes.
| Name | Description |
|---|---|
| iduuid, path, required | The location's id (path). |
| contentTypestring, body, required | image/jpeg, image/png, image/webp, image/heic or image/heif. |
| contentLengthinteger, body, required | File size in bytes, up to 10 MB. |
| uploaderOwnsRightsboolean, body, required | true if you took the photo or have the rights to it. |
Request
curl -X POST https://api.shutterspeed.art/api/v1/locations/3f2a9c1e-7b4d-4e2a-9c61-0d8e5f4a7b21/images/upload-url \
-H "Authorization: Bearer ss_live_..." \
-H "Content-Type: application/json" \
-d '{
"contentType": "image/jpeg",
"contentLength": 2483120,
"uploaderOwnsRights": true
}'Response 201 Created
{
"imageId": "e4c7a2b9-1d6f-4a83-b5e0-9f2c8d1a6b73",
"uploadUrl": "https://shutterspeed-images.s3.amazonaws.com/...?X-Amz-Signature=...",
"expiresInSeconds": 300
}POST/locations/:id/images/:imageId/finalize
REST onlyFinish a direct upload after the PUT succeeds. Converts HEIC and records the size.
- Idempotent
| Name | Description |
|---|---|
| iduuid, path, required | The location's id (path). |
| imageIduuid, path, required | The image's id (path). |
Request
curl -X POST https://api.shutterspeed.art/api/v1/locations/3f2a9c1e-7b4d-4e2a-9c61-0d8e5f4a7b21/images/e4c7a2b9-1d6f-4a83-b5e0-9f2c8d1a6b73/finalize \
-H "Authorization: Bearer ss_live_..."Response 200 OK
{
"id": "e4c7a2b9-1d6f-4a83-b5e0-9f2c8d1a6b73",
"url": "https://d1dkd0xzfyqgiq.cloudfront.net/eyJidWNrZXQiOi...",
"thumbnailUrl": "https://d1dkd0xzfyqgiq.cloudfront.net/eyJidWNrZXQiOi...",
"width": 4032,
"height": 3024,
"uploaderOwnsRights": true,
"position": 0
}PUT/locations/:id/images/order
MCP: reorder_location_imagesSet the display order of a location's photos. Send every photo id, first to last.
- Destructive
- Idempotent
| Name | Description |
|---|---|
| iduuid, path, required | The location's id (path). |
| imageIdsuuid[], body, required | All of the location's photo ids in the new order. |
Request
curl -X PUT https://api.shutterspeed.art/api/v1/locations/3f2a9c1e-7b4d-4e2a-9c61-0d8e5f4a7b21/images/order \
-H "Authorization: Bearer ss_live_..." \
-H "Content-Type: application/json" \
-d '{
"imageIds": [
"a19f3c5d-8e2b-4c70-9d4a-6b1e7f0c2d58",
"e4c7a2b9-1d6f-4a83-b5e0-9f2c8d1a6b73"
]
}'Response 200 OK
[
{
"id": "a19f3c5d-8e2b-4c70-9d4a-6b1e7f0c2d58",
"url": "https://d1dkd0xzfyqgiq.cloudfront.net/eyJidWNrZXQiOi...",
"thumbnailUrl": "https://d1dkd0xzfyqgiq.cloudfront.net/eyJidWNrZXQiOi...",
"width": 4032,
"height": 3024,
"uploaderOwnsRights": true,
"position": 0
},
{
"id": "e4c7a2b9-1d6f-4a83-b5e0-9f2c8d1a6b73",
"url": "https://d1dkd0xzfyqgiq.cloudfront.net/eyJidWNrZXQiOi...",
"thumbnailUrl": "https://d1dkd0xzfyqgiq.cloudfront.net/eyJidWNrZXQiOi...",
"width": 4032,
"height": 3024,
"uploaderOwnsRights": true,
"position": 1
}
]DELETE/locations/:id/images/:imageId
MCP: delete_location_imageDelete one photo from a location.
- Destructive
- Idempotent
| Name | Description |
|---|---|
| iduuid, path, required | The location's id (path). |
| imageIduuid, path, required | The image's id (path). |
Request
curl -X DELETE https://api.shutterspeed.art/api/v1/locations/3f2a9c1e-7b4d-4e2a-9c61-0d8e5f4a7b21/images/e4c7a2b9-1d6f-4a83-b5e0-9f2c8d1a6b73 \
-H "Authorization: Bearer ss_live_..."Response 204 No Content
No body.
Adding photos
There are two ways to add a photo. If the image is already online, POST its https URL to /images/from-url and the server downloads it. This one also works over MCP.
To upload a file from disk, use the direct upload, which takes three steps:
- POST /locations/:id/images/upload-url with the file's content type and size. You get back an image id and a presigned upload URL, valid for 5 minutes.
- PUT the file to that URL: exactly contentLength bytes, with the same Content-Type you sent in step 1. A different size or type is refused.
- POST /locations/:id/images/:imageId/finalize. The photo then shows up on the location. Finalizing before the PUT has finished returns 404.
Direct upload is REST only, since an assistant can't send file bytes to a presigned URL.
MCP server
The MCP server uses Streamable HTTP and accepts either OAuth or an API key. It exposes the same operations as the REST API, minus the direct upload.
https://api.shutterspeed.art/api/v1/mcpClaude.ai and Claude Desktop
- Open Settings, then Connectors, and choose Add custom connector.
- Paste the MCP URL and save.
- Choose Connect, sign in to Shutter Speed and choose Allow.
ChatGPT
- Open Settings, then Apps & Connectors, and turn on developer mode under Advanced.
- Create a connector with the MCP URL and OAuth authentication.
- Sign in to Shutter Speed when asked and choose Allow.
- If ChatGPT asks for a client ID and secret instead, it can't register itself with Shutter Speed. Use an API key with a client that lets you set headers.
Claude Code
With OAuth (run /mcp in Claude Code afterwards to sign in):
claude mcp add --transport http shutterspeed https://api.shutterspeed.art/api/v1/mcpOr with an API key:
claude mcp add --transport http shutterspeed https://api.shutterspeed.art/api/v1/mcp \
--header "Authorization: Bearer ss_live_..."Cursor and other clients
Clients that can send a header work with an API key. In Cursor, add the server to mcp.json with your key in the headers (this skips OAuth entirely):
{
"mcpServers": {
"shutterspeed": {
"url": "https://api.shutterspeed.art/api/v1/mcp",
"headers": {
"Authorization": "Bearer ss_live_..."
}
}
}
}Tools
Each MCP tool maps to one REST endpoint and takes the same fields. Tools are marked read only, destructive or idempotent, so an assistant can tell a lookup from a delete before it asks you.
| Tool |
|---|
list_locationsGET/locationsList your saved locations, newest first. Filter by list or by a text match on name, address, city, state, country or notes. |
get_locationGET/locations/:idGet one of your saved locations, with its lists and photos. |
create_locationPOST/locationsSave a new location from a name and coordinates. |
update_locationPATCH/locations/:idChange a saved location's name, coordinates, address or notes. |
delete_locationDELETE/locations/:idDelete a saved location and its photos. |
set_location_listsPUT/locations/:id/listsReplace the set of lists a location belongs to. |
list_listsGET/listsList your location lists with how many locations each holds. |
get_listGET/lists/:idGet one of your lists and every location in it (not paginated). |
create_listPOST/listsCreate a new location list. |
update_listPATCH/lists/:idRename a list. |
delete_listDELETE/lists/:idDelete a list. The locations in it are kept. |
list_location_imagesGET/locations/:id/imagesList a location's photos in display order, with full-size and thumbnail URLs. |
add_location_image_from_urlPOST/locations/:id/images/from-urlAdd a photo to a location by giving a public https image URL. The server downloads and stores it. |
reorder_location_imagesPUT/locations/:id/images/orderSet the display order of a location's photos. Send every photo id, first to last. |
delete_location_imageDELETE/locations/:id/images/:imageIdDelete one photo from a location. |
Errors
Errors come back with a non-2xx status and a JSON body like this:
{
"error": "Location not found",
"code": "NOT_FOUND"
}| Code | Meaning |
|---|---|
| 400VALIDATION_FAILED | The body or query didn't validate, including any field the endpoint doesn't take (unknown fields are refused, not ignored), or the body isn't valid JSON. error says which field. |
| 401UNAUTHORIZED | Missing, unknown or revoked API key or access token. |
| 403PRO_REQUIRED | The account isn't on Pro. |
| 404NOT_FOUND | No such location, list or image in your account, or a direct upload finalized before its PUT arrived. |
| 409CONFLICT | The change conflicts with current data, for example a stale photo order or the photo limit. |
| 413PAYLOAD_TOO_LARGE | The JSON body is over 512 KB. |
| 429RATE_LIMITED | More than 120 requests in 60 seconds. The Retry-After header says how many seconds to wait. |
| 500INTERNAL_ERROR | Something went wrong on our side. Retry later. |
| 503UNAVAILABLE | Photo storage is temporarily unavailable. Retry later. |
Privacy
The API and MCP server only ever see your own spots. Community spots and other people's saved locations aren't reachable through either.
Questions or a bug in the API? Contact us.