Dashboard →

API reference

Everything the dashboard does is available over HTTPS. The machine-readable document is at https://glboost.com/api/v1/openapi.json.

Authentication

Create a key under Settings → API keys. Keys carry read and/or write scopes and may be pinned to one project.

curl https://glboost.com/api/v1/assets \
  -H "Authorization: Bearer glb_live_xxxxxxxx_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
  • Wrong or revoked key → 401. Missing scope → 403.
  • Rate limit: 120 requests per minute per key (token bucket, refills at 2/s). Exceeding it returns 429 with Retry-After: 1.
  • Errors are { "error": "...", "issues"?: [...] }; issues lists zod validation problems for 400s.

Upload in three calls

# 1. start
curl -X POST https://glboost.com/api/v1/uploads -H "Authorization: Bearer $KEY" -H "content-type: application/json" \
  -d '{"projectId":"<projectId>","filename":"helmet.glb","contentType":"model/gltf-binary","bytes":3773916}'
# → { assetId, versionId, uploadId, partSize, uploadUrl, token }

# 2. PUT each part (parts of partSize bytes; the last may be smaller), collect the etags
curl -X PUT "$UPLOAD_URL/part?n=1&token=$TOKEN" --data-binary @helmet.glb   # → { partNumber, etag }
curl -X POST "$UPLOAD_URL/complete?token=$TOKEN" -H "content-type: application/json" \
  -d '{"parts":[{"partNumber":1,"etag":"..."}]}'

# 3. finish: records the object and queues the project's default presets
curl -X POST https://glboost.com/api/v1/uploads/$VERSION_ID/complete -H "Authorization: Bearer $KEY"

Webhooks

Subscribe under Settings → Webhooks. Events: asset.ready, variant.ready, variant.failed, share.viewed, share.downloaded, comment.created. Each POST carries X-GLBoost-Event, X-GLBoost-Delivery, and X-GLBoost-Signature: t=<unix>,v1=<hex> where v1 is HMAC-SHA256 over `${t}.${body}` with the webhook secret. Non-2xx answers are retried after 1 min, 5 min, 30 min, 2 h, and 12 h. Reject timestamps older than five minutes.

import { createHmac, timingSafeEqual } from 'node:crypto';

export function verify(secret: string, header: string, body: string): boolean {
  const parts = Object.fromEntries(header.split(',').map((kv) => kv.split('=')));
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  const expected = createHmac('sha256', secret).update(`${parts.t}.${body}`).digest('hex');
  return expected.length === parts.v1?.length && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

Meta

get/api/v1/openapi.jsonpublic

This document

Responses
  • 200OpenAPI 3.1 document

Projects

get/api/v1/projectskey: read

List projects

A key pinned to a project sees only that project.

Responses
  • 200Projects
get/api/v1/projects/{projectId}key: read

Get a project

Responses
  • 200Project
  • 404Not found
patch/api/v1/projects/{projectId}key: write

Update delivery settings

Request body (application/json)
{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 100
    },
    "defaultPreset": {
      "type": "string",
      "enum": [
        "compat",
        "web",
        "gpu",
        "mobile",
        "embedded",
        "original"
      ]
    },
    "defaultVariantPresets": {
      "minItems": 1,
      "maxItems": 6,
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "compat",
          "web",
          "gpu",
          "mobile",
          "embedded",
          "original"
        ]
      }
    },
    "signingRequired": {
      "type": "boolean"
    },
    "allowedDomains": {
      "maxItems": 50,
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 200,
        "pattern": "^(\\*\\.)?[a-z0-9.-]+\\.[a-z]{2,}$|^localhost(:\\d+)?$"
      }
    }
  }
}
Responses
  • 200Project
  • 400Invalid body
  • 404Not found

Organizations

patch/api/v1/orgs/{orgId}key: write

Update organization branding

Request body (application/json)
{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 100
    },
    "brandColor": {
      "anyOf": [
        {
          "type": "string",
          "pattern": "^#[0-9a-fA-F]{6}$"
        },
        {
          "type": "null"
        }
      ]
    }
  }
}
Responses
  • 200Organization
  • 400Invalid body
  • 404Not found

Uploads

post/api/v1/uploadskey: write

Start an upload

Creates the asset (or a new version when `assetId` is set) and returns a multipart upload target on the CDN: PUT each part to `{uploadUrl}/part?n=<1-based>&token=<token>` (parts of `partSize` bytes, last part may be smaller), then POST `{uploadUrl}/complete?token=<token>` with `{ parts: [{ partNumber, etag }] }`, then call `POST /api/v1/uploads/{versionId}/complete`.

Request body (application/json)
{
  "type": "object",
  "properties": {
    "projectId": {
      "type": "string",
      "minLength": 1
    },
    "filename": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    },
    "contentType": {
      "type": "string",
      "minLength": 1,
      "maxLength": 100
    },
    "bytes": {
      "type": "integer",
      "exclusiveMinimum": 0,
      "maximum": 9007199254740991
    },
    "assetId": {
      "type": "string",
      "minLength": 1
    }
  },
  "required": [
    "projectId",
    "filename",
    "contentType",
    "bytes"
  ]
}
Responses
  • 200Upload target
  • 400Invalid body
  • 413File too large
  • 415Unsupported format
post/api/v1/uploads/{versionId}/completekey: write

Finish an upload and queue optimization

Responses
  • 200Queued jobs
  • 404Not found
  • 409Object not yet on the CDN, or the upload was aborted
post/api/v1/uploads/{versionId}/abortkey: write

Give up on an upload

Marks a version that is still `uploading` as `failed` so it no longer waits for parts. Idempotent. Uploads that are never completed or aborted are failed automatically once their token expires.

Responses
  • 200Version marked failed
  • 404Not found
  • 409The upload already completed

Assets

get/api/v1/assetskey: read

List assets

Responses
  • 200Assets
get/api/v1/assets/{assetId}key: read

Get an asset with versions and variants

Responses
  • 200Asset detail
  • 404Not found
patch/api/v1/assets/{assetId}key: write

Update name, visibility, viewer settings, hotspots

Request body (application/json)
{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    },
    "description": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 2000
        },
        {
          "type": "null"
        }
      ]
    },
    "visibility": {
      "type": "string",
      "enum": [
        "public",
        "private"
      ]
    },
    "viewerSettings": {
      "anyOf": [
        {
          "type": "object",
          "properties": {
            "camera": {
              "type": "object",
              "properties": {
                "yaw": {
                  "type": "number",
                  "minimum": -3600,
                  "maximum": 3600
                },
                "pitch": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 180
                },
                "distance": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1000000
                },
                "fov": {
                  "type": "number",
                  "minimum": 1,
                  "maximum": 179
                }
              },
              "required": [
                "yaw",
                "pitch",
                "distance"
              ]
            },
            "target": {
              "type": "array",
              "prefixItems": [
                {
                  "type": "number"
                },
                {
                  "type": "number"
                },
                {
                  "type": "number"
                }
              ],
              "items": false,
              "minItems": 3,
              "maxItems": 3
            },
            "exposure": {
              "type": "number",
              "minimum": 0,
              "maximum": 2
            },
            "shadowIntensity": {
              "type": "number",
              "minimum": 0,
              "maximum": 1
            },
            "environment": {
              "type": "string",
              "maxLength": 2000
            },
            "background": {
              "type": "string",
              "maxLength": 64,
              "pattern": "^(transparent|#[0-9a-fA-F]{3,8}|[a-zA-Z]+|rgba?\\([\\d\\s.,%]+\\))$"
            },
            "autoRotate": {
              "type": "boolean"
            },
            "animation": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 200
                },
                {
                  "type": "null"
                }
              ]
            },
            "variant": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 200
                },
                {
                  "type": "null"
                }
              ]
            },
            "ar": {
              "type": "boolean"
            }
          }
        },
        {
          "type": "null"
        }
      ]
    },
    "hotspots": {
      "maxItems": 50,
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "body": {
            "type": "string",
            "maxLength": 2000
          },
          "position": {
            "type": "array",
            "prefixItems": [
              {
                "type": "number"
              },
              {
                "type": "number"
              },
              {
                "type": "number"
              }
            ],
            "items": false,
            "minItems": 3,
            "maxItems": 3
          },
          "normal": {
            "type": "array",
            "prefixItems": [
              {
                "type": "number"
              },
              {
                "type": "number"
              },
              {
                "type": "number"
              }
            ],
            "items": false,
            "minItems": 3,
            "maxItems": 3
          },
          "camera": {
            "type": "object",
            "properties": {
              "yaw": {
                "type": "number",
                "minimum": -3600,
                "maximum": 3600
              },
              "pitch": {
                "type": "number",
                "minimum": 0,
                "maximum": 180
              },
              "distance": {
                "type": "number",
                "minimum": 0,
                "maximum": 1000000
              },
              "fov": {
                "type": "number",
                "minimum": 1,
                "maximum": 179
              }
            },
            "required": [
              "yaw",
              "pitch",
              "distance"
            ]
          }
        },
        "required": [
          "id",
          "title",
          "position",
          "normal"
        ]
      }
    }
  }
}
Responses
  • 200Asset
  • 400Invalid body
  • 404Not found
delete/api/v1/assets/{assetId}key: write

Delete an asset

Responses
  • 200Deleted
  • 404Not found
post/api/v1/assets/{assetId}/thumbnailkey: write

Upload a poster image

Raw `image/png` body up to 4 MiB, typically captured from the viewer.

Request body (image/png)
{
  "type": "string",
  "format": "binary"
}
Responses
  • 200Stored
  • 404Not found
  • 413Too large
  • 415Not a PNG

Variants

get/api/v1/assets/{assetId}/variantskey: read

List variants per version

Responses
  • 200Versions with variants
  • 404Not found
post/api/v1/assets/{assetId}/variantskey: write

Request a variant

Provide a named `preset`, an explicit `spec`, or budget mode (`budgetBytes` + `target`): the optimizer searches for the largest spec under the byte budget that the target runtime can load. Returns 201 when a job was queued, 200 when the variant already existed.

Request body (application/json)
{
  "type": "object",
  "properties": {
    "versionId": {
      "type": "string"
    },
    "preset": {
      "type": "string"
    },
    "spec": {
      "type": "object",
      "properties": {
        "geo": {
          "type": "string",
          "enum": [
            "meshopt",
            "draco",
            "none",
            "raw"
          ]
        },
        "tex": {
          "type": "string",
          "enum": [
            "ktx2",
            "webp",
            "avif",
            "jpeg",
            "png",
            "original"
          ]
        },
        "maxtex": {
          "anyOf": [
            {
              "type": "number",
              "const": 256
            },
            {
              "type": "number",
              "const": 512
            },
            {
              "type": "number",
              "const": 1024
            },
            {
              "type": "number",
              "const": 2048
            },
            {
              "type": "number",
              "const": 4096
            }
          ]
        },
        "q": {
          "type": "string",
          "enum": [
            "low",
            "med",
            "high"
          ]
        },
        "anim": {
          "type": "string",
          "enum": [
            "keep",
            "strip"
          ]
        },
        "simplify": {
          "type": "number",
          "minimum": 0.1,
          "maximum": 1
        }
      },
      "required": [
        "geo",
        "tex",
        "maxtex",
        "q",
        "anim"
      ]
    },
    "budgetBytes": {
      "type": "integer",
      "minimum": 10000,
      "maximum": 2147483648
    },
    "target": {
      "type": "string",
      "enum": [
        "threejs",
        "babylon",
        "model-viewer",
        "playcanvas",
        "unity-gltfast",
        "godot",
        "unreal",
        "filament",
        "lvgl",
        "quick-look"
      ]
    }
  }
}
Responses
  • 200Existing variant
  • 201Created
  • 400Invalid body
  • 404Not found
  • 409No uploaded version
get/api/v1/assets/{assetId}/compatkey: read

Runtime compatibility report per ready variant

Verdicts per runtime (three.js, Babylon.js, model-viewer, PlayCanvas, Unity glTFast, Godot, Unreal, Filament, LVGL, Quick Look): `ok`, `needs-decoder` (with the decoders), or `unsupported` (with the blocking extensions).

Responses
  • 200Versions with variant verdicts
  • 404Not found

Comments

get/api/v1/assets/{assetId}/commentskey: read

List review comments on an asset

Chronological; replies carry `parentId`. `anchor` holds the pinned surface point, normal, and camera.

Responses
  • 200Comments
  • 404Not found
post/api/v1/assets/{assetId}/commentskey: write

Post a comment or reply

Key-authored comments show the key name as the author. Emits `comment.created` to webhooks.

Request body (application/json)
{
  "type": "object",
  "properties": {
    "body": {
      "type": "string",
      "minLength": 1,
      "maxLength": 4000
    },
    "parentId": {
      "anyOf": [
        {
          "type": "string",
          "minLength": 1,
          "maxLength": 64
        },
        {
          "type": "null"
        }
      ]
    },
    "anchor": {
      "anyOf": [
        {
          "type": "object",
          "properties": {
            "position": {
              "type": "array",
              "prefixItems": [
                {
                  "type": "number"
                },
                {
                  "type": "number"
                },
                {
                  "type": "number"
                }
              ],
              "items": false,
              "minItems": 3,
              "maxItems": 3
            },
            "normal": {
              "type": "array",
              "prefixItems": [
                {
                  "type": "number"
                },
                {
                  "type": "number"
                },
                {
                  "type": "number"
                }
              ],
              "items": false,
              "minItems": 3,
              "maxItems": 3
            },
            "camera": {
              "type": "object",
              "properties": {
                "yaw": {
                  "type": "number",
                  "minimum": -3600,
                  "maximum": 3600
                },
                "pitch": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 180
                },
                "distance": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1000000
                },
                "fov": {
                  "type": "number",
                  "minimum": 1,
                  "maximum": 179
                }
              },
              "required": [
                "yaw",
                "pitch",
                "distance"
              ]
            }
          },
          "required": [
            "position",
            "normal"
          ]
        },
        {
          "type": "null"
        }
      ]
    },
    "versionId": {
      "anyOf": [
        {
          "type": "string",
          "minLength": 1,
          "maxLength": 64
        },
        {
          "type": "null"
        }
      ]
    }
  },
  "required": [
    "body"
  ]
}
Responses
  • 201Created
  • 400Invalid body
  • 404Not found
patch/api/v1/comments/{commentId}key: write

Resolve or reopen a comment

Request body (application/json)
{
  "type": "object",
  "properties": {
    "resolved": {
      "type": "boolean"
    }
  },
  "required": [
    "resolved"
  ]
}
Responses
  • 200Updated
  • 400Invalid body
  • 404Not found
delete/api/v1/comments/{commentId}key: write

Delete a comment (and its replies)

Responses
  • 204Deleted
  • 404Not found

Exports

get/api/v1/assets/{assetId}/exportskey: read

List exports

Responses
  • 200Exports
post/api/v1/assets/{assetId}/exportskey: write

Export a ready variant as an unpacked glTF zip or a C-array header

Served from the CDN at `/{assetId}.zip` or `/{assetId}.h` with the variant's query string once ready.

Request body (application/json)
{
  "type": "object",
  "properties": {
    "variantId": {
      "type": "string",
      "minLength": 1
    },
    "format": {
      "type": "string",
      "enum": [
        "gltf-zip",
        "c-array"
      ]
    }
  },
  "required": [
    "variantId",
    "format"
  ]
}
Responses
  • 200Existing export
  • 201Created
  • 400Invalid body
  • 404Not found
  • 409Variant not ready

Environments

get/api/v1/environmentskey: read

List project environments

Responses
  • 200Environments
post/api/v1/environmentskey: write

Upload an equirect environment (.hdr, .jpg, .png up to 8 MiB)

Multipart form with `name` and `file`. Renders 64/128/256/512/1024 px JPEG maps (plus HDR for HDR sources), served at `/env/{envId}.jpg?size=` and `/env/{envId}.hdr?size=`.

Request body (multipart/form-data)
{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 80
    },
    "file": {
      "type": "string",
      "description": "binary .hdr, .jpg, or .png up to 8 MiB"
    }
  },
  "required": [
    "name",
    "file"
  ]
}
Responses
  • 201Created
  • 400Invalid body
  • 413Too large
  • 415Unsupported image
get/api/v1/environments/{envId}key: read

Get an environment

Responses
  • 200Environment
  • 404Not found
delete/api/v1/environments/{envId}key: write

Delete an environment

Responses
  • 200Deleted
  • 404Not found

Delivery

post/api/v1/assets/{assetId}/signkey: write

Mint a signed CDN URL

Request body (application/json)
{
  "type": "object",
  "properties": {
    "preset": {
      "type": "string"
    },
    "spec": {
      "type": "object",
      "properties": {
        "geo": {
          "type": "string",
          "enum": [
            "meshopt",
            "draco",
            "none",
            "raw"
          ]
        },
        "tex": {
          "type": "string",
          "enum": [
            "ktx2",
            "webp",
            "avif",
            "jpeg",
            "png",
            "original"
          ]
        },
        "maxtex": {
          "anyOf": [
            {
              "type": "number",
              "const": 256
            },
            {
              "type": "number",
              "const": 512
            },
            {
              "type": "number",
              "const": 1024
            },
            {
              "type": "number",
              "const": 2048
            },
            {
              "type": "number",
              "const": 4096
            }
          ]
        },
        "q": {
          "type": "string",
          "enum": [
            "low",
            "med",
            "high"
          ]
        },
        "anim": {
          "type": "string",
          "enum": [
            "keep",
            "strip"
          ]
        },
        "simplify": {
          "type": "number",
          "minimum": 0.1,
          "maximum": 1
        }
      },
      "required": [
        "geo",
        "tex",
        "maxtex",
        "q",
        "anim"
      ]
    },
    "versionId": {
      "type": "string"
    },
    "ttlSeconds": {
      "type": "integer",
      "minimum": 60,
      "maximum": 604800
    },
    "dl": {
      "type": "boolean"
    }
  }
}
Responses
  • 200Signed URL
  • 400Invalid body
  • 404Not found

Collections

get/api/v1/collectionskey: read

List collections

Responses
  • 200Collections with asset counts
post/api/v1/collectionskey: write

Create a collection

Request body (application/json)
{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 120
    },
    "description": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 1000
        },
        {
          "type": "null"
        }
      ]
    }
  },
  "required": [
    "name"
  ]
}
Responses
  • 201Created
  • 400Invalid body
get/api/v1/collections/{collectionId}key: read

Collection with its assets in order

Responses
  • 200Collection detail
  • 404Not found
patch/api/v1/collections/{collectionId}key: write

Rename a collection or replace its membership

`assetIds` replaces the membership in the given order (max 50; ids outside the project are dropped).

Request body (application/json)
{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 120
    },
    "description": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 1000
        },
        {
          "type": "null"
        }
      ]
    },
    "assetIds": {
      "maxItems": 50,
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1,
        "maxLength": 64
      }
    }
  }
}
Responses
  • 200Updated
  • 400Invalid body
  • 404Not found
delete/api/v1/collections/{collectionId}key: write

Delete a collection (its share links stop resolving)

Responses
  • 200Deleted
  • 404Not found

Billing

post/api/v1/billing/checkoutdashboard only

Start a hosted Stripe Checkout for a paid plan

Dashboard session only. Returns the Checkout URL to redirect to.

Request body (application/json)
{
  "type": "object",
  "properties": {
    "plan": {
      "type": "string",
      "enum": [
        "pro",
        "business"
      ]
    }
  },
  "required": [
    "plan"
  ]
}
Responses
  • 200Checkout URL
  • 400Invalid body
  • 409Billing not configured
post/api/v1/billing/portaldashboard only

Open the Stripe Customer Portal

Dashboard session only; needs an existing subscription.

Responses
  • 200Portal URL
  • 409No subscription or billing not configured

Usage

get/api/v1/usagekey: read

Usage for the project

Query: `from`, `to` (YYYY-MM-DD, default last 30 days). Returns rolled-up days (`daily`), live delivery stats for the last 7 days (`delivery`, zeros when Analytics Engine is not configured), and share activity (`shares`).

Responses
  • 200Usage summary

Shares

get/api/v1/shareskey: read

List share links for an asset or collection

Query: `assetId` or `collectionId` (one is required).

Responses
  • 200Shares
  • 400Invalid body
post/api/v1/shareskey: write

Create a share link

Request body (application/json)
{
  "type": "object",
  "properties": {
    "accessMode": {
      "type": "string",
      "enum": [
        "link",
        "password",
        "email",
        "org"
      ]
    },
    "password": {
      "anyOf": [
        {
          "type": "string",
          "minLength": 4,
          "maxLength": 200
        },
        {
          "type": "null"
        }
      ]
    },
    "allowedEmails": {
      "maxItems": 200,
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 200,
        "format": "email",
        "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
      }
    },
    "allowedDomains": {
      "maxItems": 50,
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 200,
        "pattern": "^@?[a-z0-9.-]+\\.[a-z]{2,}$"
      }
    },
    "allowDownload": {
      "type": "string",
      "enum": [
        "none",
        "optimized",
        "all"
      ]
    },
    "watermark": {
      "type": "boolean"
    },
    "showStats": {
      "type": "boolean"
    },
    "allowComments": {
      "type": "boolean"
    },
    "expiresAt": {
      "anyOf": [
        {},
        {
          "type": "null"
        }
      ]
    },
    "maxViews": {
      "anyOf": [
        {
          "type": "integer",
          "exclusiveMinimum": 0,
          "maximum": 1000000
        },
        {
          "type": "null"
        }
      ]
    },
    "notifyOnView": {
      "type": "boolean"
    },
    "pinnedVersionId": {
      "type": [
        "string",
        "null"
      ]
    },
    "assetId": {
      "type": "string",
      "minLength": 1
    },
    "collectionId": {
      "type": "string",
      "minLength": 1
    }
  },
  "required": [
    "accessMode"
  ]
}
Responses
  • 201Created
  • 400Invalid body
  • 404Not found
get/api/v1/shares/{shareId}key: read

Get a share link

Responses
  • 200Share
  • 404Not found
patch/api/v1/shares/{shareId}key: write

Update a share link

Request body (application/json)
{
  "type": "object",
  "properties": {
    "accessMode": {
      "type": "string",
      "enum": [
        "link",
        "password",
        "email",
        "org"
      ]
    },
    "password": {
      "anyOf": [
        {
          "type": "string",
          "minLength": 4,
          "maxLength": 200
        },
        {
          "type": "null"
        }
      ]
    },
    "allowedEmails": {
      "maxItems": 200,
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 200,
        "format": "email",
        "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
      }
    },
    "allowedDomains": {
      "maxItems": 50,
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 200,
        "pattern": "^@?[a-z0-9.-]+\\.[a-z]{2,}$"
      }
    },
    "allowDownload": {
      "type": "string",
      "enum": [
        "none",
        "optimized",
        "all"
      ]
    },
    "watermark": {
      "type": "boolean"
    },
    "showStats": {
      "type": "boolean"
    },
    "allowComments": {
      "type": "boolean"
    },
    "expiresAt": {
      "anyOf": [
        {},
        {
          "type": "null"
        }
      ]
    },
    "maxViews": {
      "anyOf": [
        {
          "type": "integer",
          "exclusiveMinimum": 0,
          "maximum": 1000000
        },
        {
          "type": "null"
        }
      ]
    },
    "notifyOnView": {
      "type": "boolean"
    },
    "pinnedVersionId": {
      "type": [
        "string",
        "null"
      ]
    }
  }
}
Responses
  • 200Share
  • 400Invalid body
  • 404Not found
delete/api/v1/shares/{shareId}key: write

Revoke a share link

Responses
  • 200Revoked
  • 404Not found
get/api/v1/shares/{shareId}/eventskey: read

Audit log: sessions with their events

Responses
  • 200Sessions
  • 404Not found

API keys

get/api/v1/keysdashboard only

List API keys

Responses
  • 200Keys (hashes omitted)
post/api/v1/keysdashboard only

Create an API key

The plain key is returned once in `key`; only its hash is stored.

Request body (application/json)
{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 80
    },
    "scopes": {
      "minItems": 1,
      "maxItems": 2,
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "read",
          "write"
        ]
      }
    },
    "projectId": {
      "type": "string",
      "minLength": 1
    }
  },
  "required": [
    "name",
    "scopes"
  ]
}
Responses
  • 201Created
  • 400Invalid body
  • 404Not found
delete/api/v1/keys/{keyId}dashboard only

Revoke an API key

Responses
  • 200Revoked
  • 404Not found

Webhooks

get/api/v1/webhooksdashboard only

List webhooks

Responses
  • 200Webhooks (secrets omitted)
post/api/v1/webhooksdashboard only

Create a webhook

The signing secret is returned once in `secret`.

Request body (application/json)
{
  "type": "object",
  "properties": {
    "url": {
      "type": "string",
      "maxLength": 500
    },
    "events": {
      "minItems": 1,
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "asset.ready",
          "variant.ready",
          "variant.failed",
          "share.viewed",
          "share.downloaded",
          "comment.created"
        ]
      }
    }
  },
  "required": [
    "url",
    "events"
  ]
}
Responses
  • 201Created
  • 400Invalid body
patch/api/v1/webhooks/{webhookId}dashboard only

Update a webhook

Request body (application/json)
{
  "type": "object",
  "properties": {
    "active": {
      "type": "boolean"
    },
    "url": {
      "type": "string",
      "maxLength": 500
    },
    "events": {
      "minItems": 1,
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "asset.ready",
          "variant.ready",
          "variant.failed",
          "share.viewed",
          "share.downloaded",
          "comment.created"
        ]
      }
    }
  }
}
Responses
  • 200Webhook
  • 400Invalid body
  • 404Not found
delete/api/v1/webhooks/{webhookId}dashboard only

Delete a webhook

Responses
  • 200Deleted
  • 404Not found
get/api/v1/webhooks/{webhookId}/deliveriesdashboard only

Recent deliveries

Query: `limit` (1–100, default 20).

Responses
  • 200Deliveries
  • 404Not found