API
Introduction
Our RESTful API lets you programatically create projects and products (360° views), as well as list existing projects and products.
The following guide will go through the endpoints that are required to create a 360° view.
It consists of:
- Create a project
- Create a product (360° view)
- Upload images
- Finish upload
- Embed
- List all products (360° views)
- List all projects
- List all products for a given project
- Update a product
- Delete a product
- Download a product’s source files
- List a product’s images
- Update image alt text
1. Create a project
All products (360° views) are grouped into projects. In order to create a product, you must first create a project and obtain a project UUID.
POST https://app.spinshot.io/api/v2/projects
Params
- title: Project title
Example
curl -u "USER:PASSWORD" -X POST -H "Content-Type: application/json" -H "Accept: application/json" "https://app.spinshot.io/api/v2/projects" -d '
{
"title": "Model cars"
}'
In return, you’ll receive a JSON with the project credentials. Take special note of the project UUID, you’ll need it in the next step.
Example
{
"uuid": "sAQizc_u9gM",
"title": "Model cars",
"created_at": 2014-06-24T07:23:51.071Z
}
2. Create a product (360° view)
Create a product and customize the viewer settings.
POST https://app.spinshot.io/api/v2/products
Params
- title: Title
- sku: SKU
- manufacturer: Manufacturer name
- year: Year
- project: Project UUID
- images: Number of images (“1” for video uploads)
Example
curl -u "USER:PASSWORD" -X POST -H "Content-Type: application/json" -H "Accept: application/json" "https://app.spinshot.io/api/v2/products" -d '
{
"title": "VW van",
"sku": "10023",
"manufacturer": "Lego",
"year": "2013",
"project": "sAQizc_u9gM",
"images": 24
}'
In return, you’ll receive a JSON with the credentials needed to upload the images.
Example
{
"uuid": "123456789",
"title": "VW van",
"path": "123456789/00000/1-rotation/row-1",
"policy": "jAwMFonLAogICAg...",
"signature": "GP35g00a3NsILoIf3...",
"hostname": "upload-app.spinshot.io",
"access_key_id": "KTFSMTRXEMSGR...",
"acl": "private"
}
3. Upload images
Upload the images to S3. Make sure that the image’s file names are sortable, as this will later provide the order for the 360 view.
https://HOSTNAME where HOSTNAME is returned by the previous call’s JSON object.
Params
- key: PATH**/**FILENAME
- acl: ACL
- AWSAccessKeyId: ACCESS_KEY_ID
- Policy: POLICY
- Signature: SIGNATURE
- Content-Type: image/jpeg, video/mp4, etc.
- file: PATH_TO_FILE
Example
curl -s \
-F "key=123456789/00000/1-rotation/row-1/vw-van-01.jpg" \
-F "acl=private" \
-F "AWSAccessKeyId=KTFSMTRXEMSGR..." \
-F "Policy=jAwMFonLAogICAg..." \
-F "Signature=GP35g00a3NsILoIf3..." \
-F "Content-Type=image/jpeg" \
-F "file=@360-views/model-cars/vw-van/vw-van-01.jpg" \
https://$hostname
Repeat this step for each single image.
4. Finish upload
Notify SpinShot that the upload to S3 has completed.
POST https://app.spinshot.io/api/v2/products/UUID/uploaded
Params
- none
Example
curl -u "USER:PASSWORD" -X POST -H "Content-Type: application/json" -H "Accept: application/json" "https://app.spinshot.io/api/v2/products/123456789/uploaded"
5. Embed
Retrieve an iframe embed URL for a 360° view.
GET https://app.spinshot.io/api/v2/products/UUID/embed
Params
- type: “iframe” or “responsive”
- width: maximum width
- height: maximum height
Note: if type “responsive” is chosen, width and height will be ignored as the embed link adjusts its size responsively.
Example
curl -X GET -H "Accept: application/json" "https://app.spinshot.io/api/v2/products/123456789/embed?type=iframe&width=640&height=480"
In return, you’ll receive an iframe embed link to paste into a website.
Example
<iframe width="640" height="480" src="http://embed.spinshot.io/PQUDbEJO_N4" frameborder="0" scrolling="no" style="background-color: #fff" allowfullscreen>
Complete Upload Example
The following bash script will upload all images found in the current working directory. Use the first 5 lines to define the user and password for your account, the project UUID, the title for the product to be created, and the host for the API.
Depending on your setup, you might have to install JQ (https://jqlang.github.io/jq/)
user=<USER>
password=<PASSWORD>
project=<PROJECT_UUID>
title=<PRODUCT_TITLE>
host="https://app.spinshot.io"
count=$(ls -1q * | wc -l)
params='{ "title": "'$title'", "project": "'$project'", "images": "'$count'"}'
command="curl -u '"$user":"$password"' -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' '"$host"https://app.spinshot.io/api/v2/products' -d '"${params}"'"
result=$(eval $command)
hostname=$(echo $result | jq -r '.hostname')
uuid=$(echo $result | jq -r '.uuid')
policy=$(echo $result | jq -r '.policy')
signature=$(echo $result | jq -r '.signature')
directory=$(echo $result | jq -r '.path')
for f in * ; do
echo "Uploading $f";
curl \
-F "key=$directory/$f" \
-F "acl=private" \
-F "AWSAccessKeyId=AKIAJLYJVDJ7RMESCYNA" \
-F "Policy=$policy" \
-F "Signature=$signature" \
-F "Content-Type=image/jpeg" \
-F "file=@$f" \
https://$hostname
;
done
curl -u "$user:$password" -X POST -H "Content-Type: application/json" -H "Accept: application/json" "$host/api/v2/products/$uuid/uploaded"
Listing products (360° views) and projects
6. List all Products (360° views)
Retrieve a list of all 360° views.
GET https://app.spinshot.io/api/v2/products
Params (optional)
- title
- manufacturer
- sku
- year
Example 1: filter by year
curl -u "USER:PASSWORD" -X GET -H "Accept: application/json" "https://app.spinshot.io/api/v2/products?year=2000"
Example 2: no filter
curl -u "USER:PASSWORD" -X GET -H "Accept: application/json" "https://app.spinshot.io/api/v2/products"
…returning a list of products, e.g.
[{"uuid":"123456789","title":"My product title","sku":"SKU-0001","manufacturer":"","year":null,"project":"987654321","created_at":"2019-06-03T14:11:42.410Z"}, {...}]
7. List all projects
Retrieve a list of all projects.
GET https://app.spinshot.io/api/v2/projects
Params
- No params
Example
curl -u "USER:PASSWORD" -X GET -H "Accept: application/json" "https://app.spinshot.io/api/v2/projects"
…returning a list of projects, e.g.
[{"uuid":"123456789","title":"My project title","created_at":"2019-06-03T14:11:42.410Z", products: 5}, {...}]
8. List all products for a given project
Retrieve a list of all products within a certain project.
GET https://app.spinshot.io/api/v2/projects/UUID
Params (optional)
- title
- manufacturer
- sku
- year
Example 1: filter by year
curl -u "USER:PASSWORD" -X GET -H "Accept: application/json" "https://app.spinshot.io/api/v2/projects/PQUDbEJO_N4?year=2000"
Example 2: no filter
curl -u "USER:PASSWORD" -X GET -H "Accept: application/json" "https://app.spinshot.io/api/v2/projects/PQUDbEJO_N4"
… returning a list of products, e.g.
[{"uuid":"123456789","title":"My product title","sku":"SKU-0001","manufacturer":"","year":null,"project":"987654321","created_at":"2019-06-03T14:11:42.410Z"}
9. Update an existing product (360° view)
Updates an existing product, with the option to upload new images. Alt text for individual images is not updated here; use List a product’s images and Update image alt text for that.
PATCH https://app.spinshot.io/api/v2/products/123456789
Params
- title: Title
- sku: SKU
- manufacturer: Manufacturer name
- year: Year
- images: Number of images (optional, to indicate a reupload)
Example
curl -u "USER:PASSWORD" -X PATCH -H "Content-Type: application/json" -H "Accept: application/json" "https://app.spinshot.io/api/v2/products/123456789" -d '
{
"title": "VW van",
"sku": "10023",
"manufacturer": "Lego",
"year": "2013",
"images": 24
}'
If you provided a value for “images”, please proceed from2. Create a product
Otherwise, you’ll receive a JSON object, containing either {notice: “OK”}or{errors: […]}.
10. Delete a product
Deletes a product.
DELETE https://app.spinshot.io/api/v2/products/123456789
Params
- No params
Example
curl -u "USER:PASSWORD" -X DELETE -H "Accept: application/json" "https://app.spinshot.io/api/v2/products/123456789"
You’ll receive a JSON object, containing either {notice: “OK”} or {errors: […]}.
11. Download a product’s source files
Downloads all source files for a product.
GET https://app.spinshot.io/api/v2/products/123456789.zip
Params
- No params
Example
curl -u "USER:PASSWORD" -X GET -H "Accept: application/zip" "https://app.spinshot.io/api/v2/products/123456789.zip"
You’ll receive a ZIP-file if successful, or a JSON containing {errors: […]}.
12. List a product’s images
Returns a JSON array of images for a product, including id, ordial, alt_texts, storage metadata, and Media CDN URLs. Use the image id when updating alt text via Update image alt text.
Compatibility note: The former name field has been removed. Use the numeric ordial for sequence ordering and source for the actual stored filename; do not derive filenames from ordial.
Uses the existing HTTP Basic authentication and requires edit permission for the product’s project. Responses use Cache-Control: no-store. An empty product returns []. Image records remain in the response while processing, so their IDs and alt texts are still available even when media URLs are not ready.
GET https://app.spinshot.io/api/v2/products/UUID/images
Params
- No params
Example
curl -u "USER:PASSWORD" -X GET -H "Accept: application/json" "https://app.spinshot.io/api/v2/products/123456789/images"
Illustrative response (all identifiers, filenames, and the reserved example hostname below are fictional, not customer values):
[
{
"id": 101,
"ordial": 1,
"alt_texts": {"en": "Front view"},
"source_revision": 0,
"storage_version": 1,
"image_status": "finished",
"source": "sample front.jpg",
"row": 1,
"image_group": "rotation",
"media_ready": true,
"media_url": "https://media.example.com/example-account/frames/example-product/0;1;sample%20front.jpg",
"thumbnail_url": "https://media.example.com/example-account/frames/example-product/0;1;sample%20front.jpg/resize;w_320/image.webp"
}
]
Response fields
| Field | Meaning |
|---|---|
id |
Image ID, used for updating alt text. |
ordial |
Numeric sequence position (spelled ordial); not the source filename. Images are returned in this order. |
alt_texts |
Locale-to-alt-text mapping. |
source_revision |
The variant’s current upload revision, including valid revision 0; not a guarantee that processing has finished. |
storage_version |
The variant’s storage version, which determines the media path layout. |
image_status |
The product/variant’s current image processing status. |
source |
The actual stored source filename or path. For example, the image at ordial: 1 may reference source: "4.jpg". |
row |
This image’s stored row; do not substitute a product-level row or assume every image is row 1. |
image_group |
This image’s stored group, such as rotation, additional, or gallery. |
media_ready |
Boolean: true only when the product’s image_status is finished and usable image-path and account metadata are available. Otherwise false. |
media_url |
Absolute HTTPS base URL using the account’s media host, account public UUID, and image.image_path, with source path segments URL-encoded. No trailing slash, transformations, or output filename; append these to request an image. null unless media_ready is true. |
thumbnail_url |
Ready-to-use thumbnail: media_url plus /resize;w_320/image.webp, requesting a 320 px wide WebP with the source aspect ratio preserved. null unless media_ready is true. |
These URLs use the current Flyster media stack. Storage v1 uses frames/{product_uuid}/{revision};{row};{source}, stills/{product_uuid}/{revision};{source}, or gallery/{product_uuid}/{revision};{source}. Storage v2 uses images/{product_uuid}/{source} with no revision in the path, even though source_revision is returned as metadata.
Use media_url when building custom sizes or formats: append the desired Media CDN transformation segments and output filename. For example, append /resize;w_1200/image.webp for WebP. media_url is a base for constructing requests, not a ready-to-display image URL. Use thumbnail_url directly for previews; do not append transforms to it, since it already includes an output filename. Neither URL automatically applies viewer settings or watermarks; the thumbnail adds only the fixed resize and WebP output.
Readiness and reshoots
The response describes the latest state at the request snapshot, not a permanent link to whichever upload is newest later. Refetch the image list after reshoots or reuploads: image IDs and stored source filenames can change. Do not synthesize a path by incrementing a revision or replacing a filename.
While processing (or when required path/account metadata is unavailable), each image record still includes its alt-text fields, but has "media_ready": false, "media_url": null, and "thumbnail_url": null.
To poll processing status, use GET https://app.spinshot.io/api/v2/products/UUID/status. This existing status endpoint is public; unlike /images, it does not require authentication or project edit permission:
curl -X GET -H "Accept: application/json" "https://app.spinshot.io/api/v2/products/123456789/status"
Read variant.source_revision and variant.image_status in the status response (variant is null for an unknown product). Status responses use Cache-Control: no-cache, requiring revalidation, rather than the image listing’s no-store policy. Poll with backoff rather than a tight loop. The revision increments before media is ready, so a new revision alone is not a readiness signal. Once variant.image_status is finished, refetch /images and use only entries with media_ready: true; do not construct URLs from the status response.
13. Update image alt text
Updates the alt text (accessibility description) for a specific image of a product. Use List a product’s images to get the image id for the product.
PATCH https://app.spinshot.io/api/v2/images/IMAGE_ID
Params (JSON body)
- alt_texts: Object mapping locale codes to alt text strings, e.g.
{"en": "Front view", "de": "Ansicht von vorne"}. Send the full object; it replaces all alt texts for this image. Empty or blank values are omitted from the stored result.
Example
curl -u "USER:PASSWORD" -X PATCH -H "Content-Type: application/json" -H "Accept: application/json" "https://app.spinshot.io/api/v2/images/101" -d '
{
"alt_texts": {
"en": "Front view of product",
"de": "Ansicht von vorne"
}
}'
You’ll receive a JSON object with the updated image, e.g. { "id": 101, "variant_uuid": "123456789", "alt_texts": { "en": "Front view of product", "de": "Ansicht von vorne" } }, or { "errors": [...] } on failure.