Polycam Reconstruction API
Create captures from uploaded session data and run Polycam cloud reconstruction.
The Polycam Reconstruction API lets approved Enterprise integrations create a
capture, upload a session.zip archive, and queue Polycam cloud
reconstruction. Reconstruction jobs use prepaid API credits and are subject to
workspace rate limits.
reconstruct scope, including tokens created earlier. Contact
Polycam to enable access for your workspace.
Base URL
All API v1 endpoints are served under:
https://poly.cam/api/v1
Token management endpoints (create, list, revoke) live on the main application API under a separate path:
https://poly.cam/api/account/api-tokens
Authentication
API Token Authentication
Reconstruction endpoints require a bearer token in the
Authorization header. Tokens are prefixed with poly_
followed by 64 hexadecimal characters.
Authorization: Bearer poly_abc123...def456
Key facts about API tokens:
- Tokens are scoped to a single workspace (user or organization).
- The
reconstructscope is required for creating captures and uploading session data. -
A token's scopes mirror the API access Polycam has enabled for its
workspace: enabling the Reconstruction API adds the
reconstructscope to every token in the workspace, and disabling it removes the scope again. - Tokens that only have the
reconstructscope can manage API-reconstructed captures. -
Workspaces with Content Management API access
also carry the
content_managementscope on their tokens. - The raw token is shown only once at creation time — store it securely.
- Tokens are hashed (SHA-256) before storage; Polycam never stores raw tokens.
- Tokens may have an optional expiration date.
- Tokens can be revoked at any time (soft-delete).
Firebase User Authentication (Token Management Endpoints)
The token management endpoints (create / list / revoke) use Firebase
authentication. Pass a valid Firebase ID token in the
Authorization header:
Authorization: Bearer <firebase-id-token>
The authenticated user must be the owner of the target workspace (for personal workspaces) or have owner-level access to the organization.
Errors
The API uses conventional HTTP status codes and returns errors as JSON:
{
"error": "Human-readable error message"
}
| Status | Meaning |
|---|---|
200 | Success |
202 | Accepted — request accepted, processing asynchronously |
400 | Bad Request — invalid parameters or body |
401 | Unauthorized — missing or invalid authentication |
403 | Forbidden — valid credentials but insufficient permissions |
404 | Not Found — resource does not exist or is not in your workspace |
409 | Conflict — a conflicting operation is already in progress |
422 | Unprocessable Entity — resource exists but cannot be processed as requested |
429 | Too Many Requests — rate limit or concurrent job limit exceeded |
500 | Internal Server Error |
Common 401 error messages
| Message | Cause |
|---|---|
Missing Authorization header | No Authorization header sent |
Bearer authorization required | Header doesn't start with Bearer |
Invalid API token format | Token doesn't start with poly_ |
Invalid API token | Token not recognized |
API token has been revoked | Token was previously revoked |
API token has expired | Token's expiration date has passed |
Common 403 error messages
| Message | Cause |
|---|---|
Token does not have reconstruct scope | Reconstruction API access is not enabled for the workspace; contact Polycam |
API access is not enabled for this workspace | Every API capability has been disabled for the workspace, so its tokens have no scopes; contact Polycam |
Token Management
These endpoints let you create, list, and revoke API tokens for a workspace. They use Firebase user authentication (not API token auth).
Create an API Token
Creates a new API token for the specified workspace. The raw token value is returned only in this response — store it immediately.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
workspace |
object |
Yes | Target workspace. Contains id (string) and type ("user" or "org"). |
name |
string |
Yes | Human-readable label (1–100 characters). |
expiresInDays |
number |
No | Number of days until the token expires. Omit for a non-expiring token. |
Example Request
curl -X POST https://poly.cam/api/account/api-tokens \
-H "Authorization: Bearer <firebase-id-token>" \
-H "Content-Type: application/json" \
-d '{
"workspace": { "id": "ws_abc123", "type": "org" },
"name": "CI Pipeline Token",
"expiresInDays": 90
}'
Response 200
{
"token": "poly_a1b2c3d4e5f6...64 hex chars",
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "CI Pipeline Token",
"prefix": "poly_a1b2c3d4",
"scopes": ["content_management", "reconstruct"],
"createdAt": 1706140800000,
"expiresAt": 1713916800000,
"message": "Save this token now - it will not be shown again!"
}
token field contains the full raw token. This is the
only time it will be returned. Store it in a secure location
(e.g. a secrets manager).
Error Responses
| Status | Reason |
|---|---|
400 | Invalid user account |
401 | Missing or invalid Firebase authentication |
403 | User does not have permission to create tokens for this workspace |
403 | API access is not enabled for this workspace, so there are no scopes to mint |
List API Tokens
Lists all API tokens for a workspace. Raw token values are never
returned — only the prefix (first 13 characters) is shown.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
workspaceId |
string |
Yes | The workspace ID. |
workspaceType |
string |
Yes | "user" or "org" |
Example Request
curl https://poly.cam/api/account/api-tokens?workspaceId=ws_abc123&workspaceType=org \
-H "Authorization: Bearer <firebase-id-token>"
Response 200
{
"tokens": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "CI Pipeline Token",
"prefix": "poly_a1b2c3d4",
"scopes": ["content_management", "reconstruct"],
"createdAt": 1706140800000,
"createdBy": {
"id": "user_xyz",
"username": "alice"
},
"expiresAt": 1713916800000,
"revoked": false,
"revokedAt": undefined
}
]
}
Error Responses
| Status | Reason |
|---|---|
401 | Missing or invalid Firebase authentication |
403 | User does not have permission to view tokens for this workspace |
Revoke an API Token
Revokes an API token. Revoked tokens can no longer be used for
authentication. This is a soft delete — the token record
is preserved with a revokedAt timestamp.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
tokenId |
string |
The ID of the token to revoke. |
Example Request
curl -X DELETE https://poly.cam/api/account/api-tokens/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer <firebase-id-token>"
Response 200
{
"success": true,
"message": "Token revoked"
}
If the token was already revoked:
{
"success": true,
"message": "Token already revoked"
}
Error Responses
| Status | Reason |
|---|---|
400 | Invalid user account |
401 | Missing or invalid Firebase authentication |
403 | User does not have permission to revoke this token |
404 | Token not found |
Credits
Credits endpoints let API-token callers inspect the prepaid balance for the token's workspace and review the ledger of deposits, charges, and refunds. These endpoints are read-only and use API token authentication. To add credits, use the API Keys page in the Polycam dashboard.
Get Credit Balance
Returns the current prepaid API credit balance for the authenticated token's workspace.
Example Request
curl https://poly.cam/api/v1/credits \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..."
Response 200
{
"balanceCredits": 1500,
"updatedAt": 1706140800000
}
Error Responses
| Status | Reason |
|---|---|
401 | Missing or invalid API token |
Get Credit Ledger
Returns a paginated ledger of credit changes for the authenticated token's workspace, ordered by creation time with newest entries first.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
integer |
50 |
Number of ledger entries per page. Min: 1, Max: 200. |
cursor |
string |
— | Pagination cursor from a previous response's cursor field. |
Ledger Entry Fields
| Field | Description |
|---|---|
kind | "deposit", "charge", "refund", or "adjustment". |
amountCredits | Positive for deposits/refunds; negative for charges. |
balanceAfterCredits | Workspace balance after this ledger entry was applied. |
endpoint | API endpoint associated with a charge or refund, when applicable. |
referenceId | Internal audit/debug reference, such as a job id or original ledger entry id, when applicable. |
Example Request
curl https://poly.cam/api/v1/credits/ledger?limit=25 \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..."
Response 200
{
"entries": [
{
"id": "ledger_abc123",
"kind": "charge",
"amountCredits": -1050,
"balanceAfterCredits": 450,
"createdAt": 1706140800000,
"endpoint": "POST /api/v1/captures",
"description": "Reconstruction (splat)"
},
{
"id": "ledger_def456",
"kind": "deposit",
"amountCredits": 1500,
"balanceAfterCredits": 1500,
"createdAt": 1706137200000,
"description": "Credit purchase"
}
],
"cursor": "1706137200000"
}
When there are no more entries, the cursor field is omitted
from the response.
Error Responses
| Status | Reason |
|---|---|
401 | Missing or invalid API token |
Create a Capture
Creates a new capture, reserves its reconstruction job in
waitingForUpload, and returns a signed upload URL for the
session.zip file. Upload to this URL, then call
Confirm session.zip Upload to make the
job runnable.
reconstruct scope on the API token.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name |
string |
No | Display name for the capture (max 256 characters). Defaults to empty string. |
externalId |
string |
No | External reference ID for linking to external systems (max 256 characters). |
mode |
string |
Yes | Reconstruction mode: "photo" or "splat". |
numKeyframes |
integer |
Yes | Number of image keyframes for the reconstruction. Must be at least 20 and must exactly match the number of files in session.zip under keyframes/images/. |
totalSize |
integer |
No | Byte size of the session.zip you will upload. Provide it to receive a multipart upload (pre-signed part URLs) instead of a single PUT URL — recommended for large (multi-gigabyte) uploads. Must be a positive integer. |
Example Request
curl -X POST https://poly.cam/api/v1/captures \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..." \
-H "Content-Type: application/json" \
-d '{ "name": "Office Scan", "externalId": "JOB-123", "mode": "photo", "numKeyframes": 20 }'
Response 201
{
"capture": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Office Scan",
"externalId": "JOB-123",
// ... same shape as the Capture Object
},
"jobId": "7f4f2b6c-0a5d-4b5a-9f1f-0f3d0d3f9a12",
"mode": "photo",
"status": "pending",
"upload": {
"url": "https://storage.example.com/signed-upload-url?token=..."
},
"charge": {
"amountCredits": 600,
"ledgerEntryId": "ledger_abc123"
}
}
PUT your session.zip file to the upload.url.
When you send totalSize, upload is instead a
multipart descriptor
(id, partSize, and pre-signed
parts[].url values).
Error Responses
| Status | Reason |
|---|---|
400 | Invalid or missing mode, invalid numKeyframes, invalid totalSize, name exceeds 256 characters, or externalId exceeds 256 characters |
401 | Missing or invalid API token |
402 | Insufficient API credits |
403 | Token does not have the reconstruct scope |
429 | Rate limit or concurrent job limit exceeded (see Rate Limits) |
Session Upload
Creating a capture reserves reconstruction in waitingForUpload.
Upload session.zip to the signed URL returned by
Create Capture, then confirm the upload to make
the job runnable. Use webhooks or poll the capture to track processing.
session.zip can be multiple gigabytes. To upload it as parallel
parts instead of a single PUT, send totalSize (the
archive's byte size) when creating the capture
or when refreshing the upload URL.
Recommended for any large (multi-gigabyte) upload — and necessary for archives
that exceed the storage provider's single-request size limit. The
upload object (or the refresh response) is then a multipart
descriptor instead of a single URL:
{
"id": "2~abc123...",
"partSize": 8388608,
"parts": [
{ "url": "https://storage.example.com/...&partNumber=1" },
{ "url": "https://storage.example.com/...&partNumber=2" }
]
}
Split the archive into partSize-byte chunks (the final part may be
smaller), PUT each chunk to the matching parts[].url,
and keep the ETag response header returned by each. Then
confirm the upload, passing the collected
tags as upload. The parts array order is the part
number: parts[0] is part 1, parts[1] is part 2, and
so on.
{
"upload": {
"id": "2~abc123...",
"parts": [
{ "etag": "\"e1...\"" },
{ "etag": "\"e2...\"" }
]
}
}
frames.json inside session.zip
Gaussian splatting needs per-photo camera poses and intrinsics. The archive
must contain a Polycam-format frames.json file at the zip root,
alongside the keyframes/ directory. Put the source photos under
keyframes/images/, and make each frame's name match
the photo filename. This is Polycam's own format, not COLMAP or NeRF
transforms.json. Photo mode does not require this file.
Download session.zip
Returns a signed download URL for the capture's source
session.zip archive. For captures with source images, the
archive contains the raw keyframe files under
keyframes/images/.
Tokens with only reconstruct can download session archives
for captures created through the API.
Example Request
curl https://poly.cam/api/v1/captures/550e8400-e29b-41d4-a716-446655440000/session.zip \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..."
Response 200
{
"url": "https://storage.example.com/signed-session-zip-url?token=...",
"expiresIn": 3600
}
Error Responses
| Status | Reason |
|---|---|
401 | Missing or invalid API token |
404 | Capture not found or not accessible to this token |
Refresh session.zip Upload URL
Returns a fresh upload target for the capture's session.zip.
Use this if the original URL expires or an upload needs to be retried.
reconstruct scope on the API token.
Request Body
Optional — send no body for a single PUT URL.
| Field | Type | Required | Description |
|---|---|---|---|
totalSize |
integer |
No | Byte size of the session.zip. Provide it to start a multipart upload (the response is the multipart descriptor instead of { url }). Must be a positive integer. |
Example Request
curl -X POST https://poly.cam/api/v1/captures/550e8400-e29b-41d4-a716-446655440000/session.zip \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..."
Response 200
{
"url": "https://storage.example.com/signed-upload-url?token=..."
}
When totalSize is sent, the response is instead a
multipart descriptor
(id, partSize, and parts[].url).
Error Responses
| Status | Reason |
|---|---|
400 | totalSize is not a positive integer |
401 | Missing or invalid API token |
403 | Token does not have the reconstruct scope |
404 | Capture not found or does not belong to your workspace |
Confirm session.zip Upload
Confirms that the capture's session.zip upload has completed.
This records the upload metadata and wakes the reconstruction job that was
waiting for the file.
For single-PUT uploads, Polycam can also confirm server-side when storage
reports that the object was created. Multipart uploads must be confirmed
with the collected ETag values so Polycam can assemble the
final object.
reconstruct scope on the API token.
Request Body
Optional — send no body to confirm a single-PUT upload. For a
multipart upload, send upload
to assemble the parts into the final object.
| Field | Type | Required | Description |
|---|---|---|---|
upload |
object |
Multipart only | The multipart bundle: { "id": string, "parts": [{ "etag": string }] }, where id is from the multipart descriptor and each etag is the ETag returned when that part was uploaded. Keep parts in upload order; the array index determines the part number. |
Example Request
curl -X PUT https://poly.cam/api/v1/captures/550e8400-e29b-41d4-a716-446655440000/session.zip \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..."
Response 200
{
"sessionZip": {
"size": 104857600,
"timestamp": 1706140800000,
"md5": "abc123..."
}
}
Error Responses
| Status | Reason |
|---|---|
400 | Malformed upload bundle |
401 | Missing or invalid API token |
403 | Token does not have the reconstruct scope |
404 | Capture not found, or session.zip has not been uploaded yet |
Rate Limits
The API enforces two types of limits on reconstruction jobs to ensure fair usage and system stability:
| Limit | Description | Default |
|---|---|---|
| Daily rate limit | Maximum number of reconstruction jobs that can be reserved within a rolling 24-hour window. | 25 per 24 hours |
| Concurrent limit | Maximum number of reconstruction jobs that can be active at the same time, including jobs waiting for session.zip upload confirmation. |
2 concurrent jobs |
When either limit is exceeded, the
Create Capture endpoint returns
429 Too Many Requests with a Retry-After header
indicating how many seconds to wait before retrying.
Get Usage
Returns current API usage and rate limit status for the workspace associated with the API token.
Example Request
curl https://poly.cam/api/v1/usage \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..."
Response 200
{
"reconstruct": {
"used": 6,
"limit": 25,
"remaining": 19,
"windowHours": 24,
"resetAt": 1706227200000,
"concurrent": {
"active": 1,
"limit": 2,
"remaining": 1
}
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
reconstruct.used | number | Number of reconstruction jobs started in the current rolling window. |
reconstruct.limit | number | Maximum jobs allowed per rolling window. |
reconstruct.remaining | number | Jobs remaining before hitting the daily limit. |
reconstruct.windowHours | number | Rolling window duration in hours. |
reconstruct.resetAt | number? | Unix timestamp (ms) when the oldest in-window job expires and a slot opens. Absent when no jobs are in the window. |
reconstruct.concurrent.active | number | Number of currently active reconstruction jobs. |
reconstruct.concurrent.limit | number | Maximum concurrent reconstruction jobs allowed. |
reconstruct.concurrent.remaining | number | Concurrent slots available. |
Error Responses
| Status | Reason |
|---|---|
401 | Missing or invalid API token |
Reconstruction Modes
Pass one of these values as the mode field when you
create a capture.
| Mode | Description |
|---|---|
photo | Photogrammetry reconstruction from image keyframes. |
splat | Gaussian splat reconstruction. Requires Polycam-format frames.json in session.zip. |
Tracking Results
Reconstruction runs asynchronously after session.zip is confirmed.
Poll the capture metadata endpoint or subscribe to capture webhooks in the
Content Management API documentation to track
status and inspect generated artifacts.
Health Check
Simple health check. No authentication required.
curl https://poly.cam/api/v1/health
Response 200
{
"status": "ok",
"service": "api-v1"
}