Polycam Enterprise API v1
Programmatic access to your Polycam workspace — captures, 3D models, and metadata.
The Polycam Enterprise API lets you integrate Polycam data into your own applications and workflows. You can create captures, trigger cloud reconstruction, list and retrieve captures, download artifacts, export to additional mesh formats (OBJ, FBX, STL, and more) and floorplan formats (PDF, SVG, DXF, CSV reports, and more), inspect processing status, receive real-time webhook notifications when captures change, and manage API tokens.
api add-on.
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 (Capture Endpoints)
All capture 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).
- Tokens grant access to resources in that workspace (read and limited write operations).
- Tokens may have scopes that grant access to additional capabilities. For example, the
reconstructscope is required for creating captures. - 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 |
Pagination
List endpoints return paginated results. Use the cursor value
from a response to fetch the next page.
# First page
GET /v1/captures?limit=25
# Next page — use the cursor from the previous response
GET /v1/captures?limit=25&cursor=1706140800000
When there are no more results, the cursor field is omitted
from the response.
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",
"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 |
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",
"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.
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 (space)"
},
{
"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 |
Captures
Capture endpoints let you create, list, and retrieve 3D capture metadata
from your workspace. All capture endpoints require API token
authentication. Creating captures additionally requires the
reconstruct scope on the API token and reserves a reconstruction
job, charged against your prepaid API credit balance.
List Captures
Returns a paginated list of captures in the authenticated workspace, ordered by creation date (newest first).
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
integer |
20 |
Number of captures per page. Min: 1, Max: 100. |
cursor |
string |
— | Pagination cursor from a previous response's cursor field. |
externalId |
string |
— | Filter captures by external reference ID. Only captures with a matching externalId are returned. |
hasExternalId |
string |
— | Filter by whether externalId is set. Use "true" to return only captures that have an externalId, or "false" to return only captures without one. |
createdAfter |
integer |
— | Only return captures created after this timestamp (epoch milliseconds, exclusive). |
createdBefore |
integer |
— | Only return captures created before this timestamp (epoch milliseconds, exclusive). |
tag |
string |
— | Filter to captures that contain this tag. |
Example Request
curl https://poly.cam/api/v1/captures?limit=10 \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..."
Response 200
{
"captures": [
{
"id": "cap_abc123",
"name": "Office Scan",
"description": "3rd floor conference room",
"tags": ["office", "interior"],
"createdAt": 1706140800000,
"updatedAt": 1706141400000,
"visibility": "private",
"session": {
"mode": "lidar",
"device": "iPhone 15 Pro",
"appVersion": "3.5.0",
"duration": 120,
"imported": false,
"drone": false
},
"processing": {
"status": "completed",
"sessionDuration": 120,
"processingDuration": 45,
"processingVersion": "2.1.0"
},
"mesh": {
"vertexCount": 150000,
"faceCount": 300000,
"bboxSize": [5.2, 3.1, 4.8],
"bboxCenter": [0.0, 1.5, 0.0]
},
"geoData": { /* location data, if not hidden */ },
"availableArtifacts": [
"thumbnail.jpg",
"video.mp4",
"skybox.jpg",
"original.gltf",
"original_geometry.bin",
"textures/0.jpg",
"textures/1.jpg"
],
"thumbnail": "https://storage.example.com/captures/cap_abc123/thumbnail.jpg",
"externalId": "MH-2024-456"
}
],
"cursor": "1706140800000"
}
When cursor is present in the response, pass it as a query
parameter to retrieve the next page. When omitted, you've reached the
end.
Error Responses
| Status | Reason |
|---|---|
401 | Missing or invalid API token |
500 | Internal server error |
Get a Capture
Returns metadata for a single capture. The capture must belong to the workspace associated with your API token.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
captureId |
string |
The unique ID of the capture. |
Example Request
curl https://poly.cam/api/v1/captures/cap_abc123 \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..."
Response 200
{
"capture": {
"id": "cap_abc123",
"name": "Office Scan",
// ... same shape as objects in the list response
}
}
Error Responses
| Status | Reason |
|---|---|
401 | Missing or invalid API token |
404 | Capture not found or does not belong to your workspace |
500 | Internal server error |
404 (not 403) to avoid leaking the
existence of resources in other workspaces.
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", "space", or "splat". |
numKeyframes |
integer |
Required for space and splat |
Number of keyframes for the reconstruction. 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" }'
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": {
"type": "simple",
"session": "https://storage.example.com/signed-upload-url?token=..."
},
"charge": {
"amountCredits": 500,
"ledgerEntryId": "ledger_abc123"
}
}
PUT your session.zip file to the
upload.session URL.
Error Responses
| Status | Reason |
|---|---|
400 | Invalid or missing mode, invalid numKeyframes, 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) |
Update a Capture
Updates fields on a capture. Supports setting or clearing the
name, description, tags, and
externalId fields. At least one field must be provided.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
captureId |
string |
The unique ID of the capture to update. |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name |
string |
No | Display name (max 256 characters). Must not be empty. |
description |
string |
No | Description text (max 500 characters). Send an empty string "" to clear. |
tags |
string[] |
No | Array of tags (max 50 tags, each max 128 characters). Send an empty array [] to clear. |
externalId |
string |
No | External reference ID (max 256 characters). Send an empty string "" to clear. |
Example Request — Update name and tags
curl -X PATCH https://poly.cam/api/v1/captures/cap_abc123 \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..." \
-H "Content-Type: application/json" \
-d '{
"name": "Office Scan - Floor 3",
"tags": ["office", "interior", "floor-3"]
}'
Example Request — Set external ID
curl -X PATCH https://poly.cam/api/v1/captures/cap_abc123 \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..." \
-H "Content-Type: application/json" \
-d '{ "externalId": "MH-2024-456" }'
Example Request — Clear external ID
curl -X PATCH https://poly.cam/api/v1/captures/cap_abc123 \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..." \
-H "Content-Type: application/json" \
-d '{ "externalId": "" }'
Response 200
{
"capture": {
"id": "cap_abc123",
"name": "Office Scan",
"externalId": "MH-2024-456",
// ... same shape as the Capture Object
}
}
Error Responses
| Status | Reason |
|---|---|
400 | No update fields provided, empty name, or field exceeds length limit |
401 | Missing or invalid API token |
404 | Capture not found or does not belong to your workspace |
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.
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 and space modes do not require this file.
Refresh session.zip Upload URL
Returns a fresh signed upload URL 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.
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=..."
}
Error Responses
| Status | Reason |
|---|---|
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.
Polycam also confirms uploads server-side when storage reports the object
was created, so this endpoint is an explicit fast path rather than the
only way processing can start.
reconstruct scope on the API token.
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 |
|---|---|
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 |
Webhooks
Webhooks let Polycam notify your application in real time when captures in
your workspace change, so you don't have to poll. You register one or more
HTTPS endpoints, choose which event types you care about, and Polycam sends
an HTTP POST with a JSON payload each time a matching event
occurs.
Every delivery is signed with an HMAC-SHA256 signature derived from a per-endpoint signing secret so you can verify it genuinely came from Polycam. Failed deliveries are retried automatically with exponential backoff, and you can inspect event history and delivery attempts (or manually redeliver) via the webhook events endpoints.
Event Types
An endpoint subscribes to one or more of the following event types:
| Event | Fires when |
|---|---|
capture.created | A new capture is created in the workspace. |
capture.updated | An existing capture changes in any way. |
capture.deleted | A capture is deleted from the workspace. |
capture.updated is not a "processing complete" event
It fires on every change to a capture — including metadata edits,
status changes, and artifact updates — not just when reconstruction
finishes. To detect that processing has completed, inspect
capture.processing.status (and, when present,
capture.processing.job.status) on the payload's
Capture Object.
Payload
The request body is a JSON object with the event type and the full
Capture Object the event relates to. For
capture.deleted, the capture object reflects the
capture's state immediately before deletion.
| Field | Type | Description |
|---|---|---|
event | string | One of the event types above. |
capture | object | The full Capture Object. |
Example Payload
{
"event": "capture.updated",
"capture": {
"id": "cap_abc123",
"name": "Office Scan",
"processing": {
"status": "completed",
"job": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed"
}
}
// ... full Capture Object
}
}
Verifying Signatures
Each delivery includes the following HTTP headers:
| Header | Description |
|---|---|
Content-Type | Always application/json. |
X-Polycam-Signature | Signature in the form sha256=<hex>, an HMAC-SHA256 of the raw request body keyed with the endpoint's signing secret. |
X-Polycam-Event-Id | The ID of the webhook event being delivered. The same event ID is reused across retries and redeliveries, which makes it useful for idempotency / deduplication. |
To verify a delivery, compute the HMAC-SHA256 of the exact raw request body
using your endpoint's signingSecret, then compare it (using a
constant-time comparison) against the hex value in the
X-Polycam-Signature header:
const crypto = require('crypto');
function verifyPolycamSignature(rawBody, signatureHeader, signingSecret) {
const expected = crypto
.createHmac('sha256', signingSecret)
.update(rawBody) // the raw bytes of the request body
.digest('hex');
const received = signatureHeader.replace('sha256=', '');
const expectedBuf = Buffer.from(expected);
const receivedBuf = Buffer.from(received);
if (expectedBuf.length !== receivedBuf.length) {
return false;
}
return crypto.timingSafeEqual(expectedBuf, receivedBuf);
}
2xx status code within 10 seconds. Any other
status code, a timeout, or a connection error is treated as a failure and
scheduled for retry. Do heavy work asynchronously and return
200 immediately.
Delivery & Retries
Each event is delivered independently to every enabled endpoint subscribed to its event type. If a delivery fails, Polycam retries up to 8 total attempts with exponential backoff. The wait before each retry follows this schedule:
| Retry | Wait before attempt |
|---|---|
| 1st retry | 30 seconds |
| 2nd retry | 1 minute |
| 3rd retry | 5 minutes |
| 4th retry | 30 minutes |
| 5th retry | 2 hours |
| 6th retry | 8 hours |
| 7th retry | 24 hours |
After the 8th failed attempt the delivery is marked failed and
no longer retried. You can inspect the status of every attempt via
List Deliveries, and trigger a fresh
set of deliveries with Redeliver Event.
Captures of the response body are truncated to 1024 characters.
Create Endpoint
Registers a new webhook endpoint and generates a signing secret for it.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | The HTTPS URL to deliver events to. Must use https://. |
events | string[] | Yes | Non-empty array of event types to subscribe to. |
Example Request
curl -X POST https://poly.cam/api/v1/webhooks \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..." \
-H "Content-Type: application/json" \
-d '{ "url": "https://example.com/hooks/polycam", "events": ["capture.created", "capture.updated"] }'
Response 201
{
"webhook": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://example.com/hooks/polycam",
"events": ["capture.created", "capture.updated"],
"signingSecret": "3f9a...<64 hex chars>",
"enabled": true,
"createdAt": 1706140800000,
"updatedAt": 1706140800000
}
}
signingSecret is returned on create, on
Get Endpoint, and on
Rotate Secret — but it is
not included in the list or
update responses. You need it to verify
signatures, so store it securely.
Error Responses
| Status | Reason |
|---|---|
400 | Invalid URL format, URL is not HTTPS, or events is empty / contains an unknown event type |
401 | Missing or invalid API token |
List Endpoints
Lists all webhook endpoints registered for your workspace. Signing secrets are omitted.
Example Request
curl https://poly.cam/api/v1/webhooks \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..."
Response 200
{
"webhooks": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://example.com/hooks/polycam",
"events": ["capture.created", "capture.updated"],
"enabled": true,
"createdAt": 1706140800000,
"updatedAt": 1706140800000
}
]
}
Get Endpoint
Retrieves a single webhook endpoint, including its
signingSecret.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
webhookId | string | The ID of the webhook endpoint. |
Example Request
curl https://poly.cam/api/v1/webhooks/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..."
Response 200
{
"webhook": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://example.com/hooks/polycam",
"events": ["capture.created", "capture.updated"],
"signingSecret": "3f9a...<64 hex chars>",
"enabled": true,
"createdAt": 1706140800000,
"updatedAt": 1706140800000
}
}
Error Responses
| Status | Reason |
|---|---|
401 | Missing or invalid API token |
404 | Webhook not found or does not belong to your workspace |
Update Endpoint
Updates a webhook endpoint. All fields are optional; only the fields you
provide are changed. Use enabled: false to temporarily pause
deliveries without deleting the endpoint.
Request Body
| Field | Type | Description |
|---|---|---|
url | string? | New HTTPS delivery URL. |
events | string[]? | New non-empty array of event types to subscribe to. |
enabled | boolean? | Whether the endpoint is active. |
Example Request
curl -X PATCH https://poly.cam/api/v1/webhooks/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..." \
-H "Content-Type: application/json" \
-d '{ "events": ["capture.created", "capture.updated", "capture.deleted"], "enabled": false }'
Response 200
Returns the updated endpoint (without signingSecret), same shape as List Endpoints items.
Error Responses
| Status | Reason |
|---|---|
400 | Invalid URL format, URL is not HTTPS, or events provided but empty |
401 | Missing or invalid API token |
404 | Webhook not found or does not belong to your workspace |
Delete Endpoint
Permanently removes a webhook endpoint. No further events are delivered to it.
Example Request
curl -X DELETE https://poly.cam/api/v1/webhooks/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..."
Response 200
{ "deleted": true }
Error Responses
| Status | Reason |
|---|---|
401 | Missing or invalid API token |
404 | Webhook not found or does not belong to your workspace |
Rotate Secret
Generates a new signing secret for the endpoint and returns it. The previous secret stops being valid immediately, so update your verification code to use the new secret.
Example Request
curl -X POST https://poly.cam/api/v1/webhooks/550e8400-e29b-41d4-a716-446655440000/rotate-secret \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..."
Response 200
{
"webhook": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://example.com/hooks/polycam",
"events": ["capture.created", "capture.updated"],
"signingSecret": "<new 64 hex chars>",
"enabled": true,
"createdAt": 1706140800000,
"updatedAt": 1706227200000
}
}
Error Responses
| Status | Reason |
|---|---|
401 | Missing or invalid API token |
404 | Webhook not found or does not belong to your workspace |
List Events
Lists webhook events recorded for your workspace, most recent first. An event is recorded whenever a matching capture change occurs and at least one enabled endpoint is subscribed to its type. Useful for auditing and debugging.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
limit | integer | Number of events to return. Clamped to the range 1–100. Defaults to 20. |
cursor | string | Pagination cursor returned by a previous response. See Pagination. |
event | string | Filter to a single event type. |
Example Request
curl "https://poly.cam/api/v1/webhook-events?limit=20&event=capture.updated" \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..."
Response 200
{
"events": [
{
"id": "e1f2a3b4-...",
"payload": {
"event": "capture.updated",
"capture": { "id": "cap_abc123" /* ... full Capture Object */ }
},
"createdAt": 1706140800000
}
],
"cursor": "1706140700000"
}
When cursor is present, pass it back as the
cursor query parameter to fetch the next page. When it is
absent, there are no more results.
Get Event
Retrieves a single webhook event, including its full payload.
Example Request
curl https://poly.cam/api/v1/webhook-events/e1f2a3b4-... \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..."
Response 200
{
"event": {
"id": "e1f2a3b4-...",
"payload": { "event": "capture.updated", "capture": { /* ... */ } },
"createdAt": 1706140800000
}
}
Error Responses
| Status | Reason |
|---|---|
401 | Missing or invalid API token |
404 | Event not found or does not belong to your workspace |
List Deliveries
Lists the delivery attempts for a single event — one delivery record per subscribed endpoint. Use this to debug failing endpoints.
Example Request
curl https://poly.cam/api/v1/webhook-events/e1f2a3b4-.../deliveries \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..."
Response 200
{
"deliveries": [
{
"id": "d1e2f3...",
"eventId": "e1f2a3b4-...",
"endpointId": "550e8400-...",
"status": "delivered",
"attempts": 1,
"maxAttempts": 8,
"lastAttemptAt": 1706140801000,
"nextRetryAt": null,
"httpStatus": 200,
"responseBody": "ok",
"error": null,
"createdAt": 1706140800000,
"completedAt": 1706140801000
}
]
}
Delivery Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique delivery identifier. |
eventId | string | The webhook event being delivered. |
endpointId | string | The endpoint this delivery targets. |
status | string | pending, delivering, delivered, or failed. |
attempts | integer | Number of attempts made so far. |
maxAttempts | integer | Maximum attempts before giving up (8). |
lastAttemptAt | number? | Unix timestamp (ms) of the most recent attempt. |
nextRetryAt | number? | Unix timestamp (ms) of the next scheduled retry, if pending. |
httpStatus | integer? | HTTP status code returned by your endpoint on the last attempt. |
responseBody | string? | Response body from your endpoint, truncated to 1024 characters. |
error | string? | Error message for transport-level failures (e.g. timeout, DNS error). |
createdAt | number | Unix timestamp (ms) when the delivery was created. |
completedAt | number? | Unix timestamp (ms) when the delivery reached a terminal state. |
Error Responses
| Status | Reason |
|---|---|
401 | Missing or invalid API token |
404 | Event not found or does not belong to your workspace |
Redeliver Event
Queues a fresh set of deliveries for an event to every currently enabled
endpoint subscribed to the event's type. Useful for replaying an event
after fixing a broken endpoint. The original event ID (and therefore the
X-Polycam-Event-Id header) is reused.
Example Request
curl -X POST https://poly.cam/api/v1/webhook-events/e1f2a3b4-.../redeliver \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..."
Response 200
{
"redelivered": true,
"deliveryCount": 2
}
Error Responses
| Status | Reason |
|---|---|
401 | Missing or invalid API token |
404 | Event not found or does not belong to your workspace |
422 | No enabled endpoints are subscribed to this event's type |
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 |
Exports
Export endpoints let you download capture artifacts directly and convert captures to additional mesh and floorplan formats. All export endpoints require API token authentication.
Download Artifacts
Returns time-limited signed download URLs for all available artifacts of a capture. Each URL is valid for 1 hour (3600 seconds).
Path Parameters
| Parameter | Type | Description |
|---|---|---|
captureId |
string |
The unique ID of the capture. |
Example Request
curl https://poly.cam/api/v1/captures/cap_abc123/export \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..."
Response 200
{
"captureId": "cap_abc123",
"exports": {
"thumbnail.jpg": {
"url": "https://storage.example.com/signed/thumbnail.jpg?token=...",
"expiresIn": 3600
},
"skybox.jpg": {
"url": "https://storage.example.com/signed/skybox.jpg?token=...",
"expiresIn": 3600
},
"original.gltf": {
"url": "https://storage.example.com/signed/original.gltf?token=...",
"expiresIn": 3600
},
"original_geometry.bin": {
"url": "https://storage.example.com/signed/original_geometry.bin?token=...",
"expiresIn": 3600
},
"textures/0.jpg": {
"url": "https://storage.example.com/signed/textures/0.jpg?token=...",
"expiresIn": 3600
}
}
}
The exports object contains one entry per available artifact.
Only artifacts that exist for this capture are included. See
Artifacts for the full list of possible artifact names.
Error Responses
| Status | Reason |
|---|---|
401 | Missing or invalid API token |
404 | Capture not found, does not belong to your workspace, or has no exportable artifacts |
500 | Failed to generate export URLs |
Start an Export Conversion Job
Starts an asynchronous conversion job to export a capture to a different
format. This supports both mesh formats (e.g. OBJ, FBX, STL)
and floorplan formats (e.g. PDF, SVG, DXF, report CSV).
The response includes a jobId that you can poll via
GET /v1/exports/:jobId to check progress
and retrieve the download URL when complete.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
captureId |
string |
The unique ID of the capture to export. |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
format |
string |
Yes | Target export format. See Export Formats below for the full list of mesh and floorplan formats. |
georeference |
boolean |
No | If true, georeference the output. Only applicable to mesh point cloud formats (PLY, LAS, PTS, XYZ). Ignored for floorplan exports. |
pointCloudDensity |
number |
No | Point density for mesh point cloud formats (PLY, LAS, PTS, XYZ). Ignored for floorplan exports. |
Example Request — Mesh export
curl -X POST https://poly.cam/api/v1/captures/cap_abc123/export \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..." \
-H "Content-Type: application/json" \
-d '{
"format": "obj"
}'
Example Request — Floorplan PDF export
curl -X POST https://poly.cam/api/v1/captures/cap_abc123/export \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..." \
-H "Content-Type: application/json" \
-d '{
"format": "pdf"
}'
Example Request — Floorplan report CSV export
curl -X POST https://poly.cam/api/v1/captures/cap_abc123/export \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..." \
-H "Content-Type: application/json" \
-d '{
"format": "report-csv"
}'
Response 202
{
"jobId": "550e8400-e29b-41d4-a716-446655440000",
"status": "pending",
"exportFormat": "pdf",
"captureId": "cap_abc123"
}
jobId with GET /v1/exports/:jobId
to poll for completion and retrieve the download URL.
png, svg, pdf,
dxf, json, report-csv,
report-pdf) require the capture to have floorplan data
(i.e. the capture mode must be floorplan). The
georeference and pointCloudDensity options
are not applicable to floorplan exports.
Error Responses
| Status | Reason |
|---|---|
400 | Invalid JSON body or invalid/missing format |
401 | Missing or invalid API token |
404 | Capture not found or does not belong to your workspace |
422 | Capture has no mesh available for export (mesh formats) or no floorplan data available (floorplan formats) |
500 | Failed to create export job |
Get Export Job Status
Returns the current status of an export conversion job. When the job
completes successfully, the response includes an outputUrl
with a download link for the exported file.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
jobId |
string |
The job ID returned from POST /v1/captures/:captureId/export. |
Example Request
curl https://poly.cam/api/v1/exports/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer poly_a1b2c3d4e5f6..."
Response 200 — Pending/Running
{
"jobId": "550e8400-e29b-41d4-a716-446655440000",
"status": "pending",
"type": "export",
"exportFormat": "obj",
"captureId": "cap_abc123",
"createdAt": 1706140800000
}
Response 200 — Complete
{
"jobId": "550e8400-e29b-41d4-a716-446655440000",
"status": "complete",
"type": "export",
"exportFormat": "obj",
"captureId": "cap_abc123",
"createdAt": 1706140800000,
"outputUrl": "https://storage.example.com/exports/capture.obj?token=..."
}
Response 200 — Failed
{
"jobId": "550e8400-e29b-41d4-a716-446655440000",
"status": "failed",
"type": "export",
"exportFormat": "obj",
"captureId": "cap_abc123",
"createdAt": 1706140800000,
"error": "Conversion failed: insufficient mesh data"
}
type field is "export" for mesh conversion
jobs and "floorplan-export" for floorplan conversion jobs.
Job Statuses
| Status | Description |
|---|---|
pending | Job is queued and waiting to be processed. |
running | Job is currently being processed. |
complete | Job finished successfully. outputUrl is available. |
failed | Job failed. error field contains the reason. |
status is
"complete" or "failed". Export jobs typically
complete within a minute.
Error Responses
| Status | Reason |
|---|---|
401 | Missing or invalid API token |
404 | Export job not found or does not belong to your workspace |
Capture Object
Every capture returned by the API has the following shape:
| Field | Type | Description |
|---|---|---|
id | string | Unique capture identifier. |
name | string | Display name of the capture. |
description | string? | Optional description. |
tags | string[] | User-assigned tags. |
createdAt | number | Unix timestamp (ms) when the capture was created. |
updatedAt | number | Unix timestamp (ms) of the last update. |
visibility | string | Visibility setting (e.g. "private", "public"). |
session | object | Session metadata — see below. |
processing | object | Processing status and timing — see below. |
mesh | object? | Mesh geometry info (if available). |
geoData | object? | Geographic data (omitted when location is hidden). |
availableArtifacts | string[] | List of artifact filenames that exist for this capture. |
thumbnail | string? | URL to the thumbnail image, if available. |
externalId | string? | External reference ID for linking to external systems (max 256 characters). Set via PATCH. |
session object
| Field | Type | Description |
|---|---|---|
mode | string | Capture mode. See Capture Modes. |
device | string? | Device used (e.g. "iPhone 15 Pro"). |
appVersion | string? | Polycam app version. |
duration | number? | Capture session duration in seconds. |
imported | boolean? | Whether the capture was imported. |
drone | boolean? | Whether the capture was taken with a drone. |
processing object
| Field | Type | Description |
|---|---|---|
status | string | "completed" or "pending". |
sessionDuration | number? | Session duration in seconds. |
processingDuration | number? | Cloud processing time in seconds. |
processingVersion | string? | Version of the processing pipeline used. |
job | object? | Details of the most recent reconstruction/processing job, if one exists — see below. |
processing.job object (optional)
Present when the capture has an associated batch job (e.g. after triggering reconstruction). This is the most reliable way to detect when processing has finished from a webhook payload.
| Field | Type | Description |
|---|---|---|
id | string | The job ID (matches the jobId returned by Start Reconstruction). |
type | string | The type of processing job. |
status | string | Job status, e.g. "pending", "running", "completed", or "failed". |
createdAt | number? | Unix timestamp (ms) when the job was created. |
finishedAt | number? | Unix timestamp (ms) when the job finished. |
error | string? | Failure reason, present when status is "failed". |
mesh object (optional)
| Field | Type | Description |
|---|---|---|
vertexCount | number | Total number of mesh vertices. |
faceCount | number | Total number of mesh faces. |
bboxSize | [x, y, z] | Bounding box dimensions in meters. |
bboxCenter | [x, y, z] | Bounding box center coordinates. |
Capture Modes
The session.mode field returns one of the following values:
| Mode | Description |
|---|---|
lidar | LiDAR scan (default for legacy captures). |
photo | Photogrammetry capture (photo, object, or objectCapture modes). |
floorplan | RoomPlan-based floorplan scan. |
space | Space capture (large-area scanning). |
360 | 360° spherical capture. |
import | Imported 3D model or camera upload. |
splat | Gaussian splat capture. |
generated | AI-generated 3D model. |
Artifacts
The availableArtifacts array lists filenames of data files
associated with a capture. These indicate which export formats and data
are available.
| Artifact | Description |
|---|---|
thumbnail.jpg | Capture thumbnail image. |
video.mp4 | Video trailer / flythrough. |
edited.gltf | Edited glTF mesh (only present if the capture has been edited). |
original.gltf | Original glTF mesh. |
edited_geometry.bin | Binary geometry data for edited.gltf. |
original_geometry.bin | Binary geometry data for original.gltf. |
textures/* | Texture image files referenced by the glTF meshes (e.g. textures/0.jpg, textures/1.jpg). The exact filenames vary per capture. |
skybox.jpg | Skybox texture (360° captures). |
original_floorplan.json | Original floorplan data (from Apple RoomPlan). |
optimized_floorplan.json | Optimized floorplan data. |
edited_floorplan.json | User-edited floorplan data. |
import.zip | Original imported file. |
edited.splat | Edited Gaussian splat (only present if the capture has been edited). |
original.splat | Original unedited Gaussian splat. |
splat.ply | Gaussian splat in PLY format. |
Export Formats
The following formats are supported by the
export conversion endpoint. Pass one of these
values as the format field in the request body.
Mesh Formats
These formats require the capture to have mesh data (3D model).
| Format | Extension | Description |
|---|---|---|
glb | .glb | GLB — self-contained binary glTF mesh. |
obj | .obj | Wavefront OBJ — widely supported mesh format. |
fbx | .fbx | Autodesk FBX — common in game engines and 3D tools. |
stl | .stl | STL — standard for 3D printing. |
dae | .dae | COLLADA — XML-based interchange format. |
usdz | .usdz | USDZ — Apple's AR-ready format. |
ply | .ply | PLY — point cloud format. Supports pointCloudDensity and georeference. |
las | .las | LAS — industry-standard point cloud. Supports pointCloudDensity and georeference. |
pts | .pts | PTS — point cloud format. Supports pointCloudDensity and georeference. |
xyz | .xyz | XYZ — simple point cloud format. Supports pointCloudDensity and georeference. |
Floorplan Formats
These formats require the capture to have floorplan data (i.e. captures made
with the floorplan mode). The georeference and
pointCloudDensity options are not applicable to floorplan exports.
| Format | Extension | Description |
|---|---|---|
png | .png | PNG — rasterized 2D floorplan image. |
svg | .svg | SVG — scalable vector floorplan drawing. |
pdf | PDF — printable 2D floorplan document. | |
dxf | .dxf | DXF — AutoCAD-compatible floorplan drawing. |
json | .json | JSON — structured floorplan data. |
report-csv | .csv | CSV report — tabular room measurements and areas. |
report-pdf | PDF report — formatted report with room measurements and areas. |
Health Check
Simple health check. No authentication required.
curl https://poly.cam/api/v1/health
Response 200
{
"status": "ok",
"service": "api-v1"
}