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"?: [...] };issueslists 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, version.approved, version.changes_requested. 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
/api/v1/openapi.jsonpublicThis document
200OpenAPI 3.1 document
Projects
/api/v1/projectskey: readList projects
Every project of the organization, oldest first; a key pinned to a project sees only that project. Asset routes work in the active project: the dashboard's `glboost_project` cookie, a key's pinned project, or the `X-GLBoost-Project` header (404 when it names a project outside the organization), else the oldest project.
200Projects
/api/v1/projectsdashboard onlyCreate a project
Pro and up (`maxProjects` per plan). Owners and admins only. The project gets its own signing key and delivery settings.
{
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 100
}
},
"required": [
"name"
]
}201Created400Invalid body402Plan does not include this feature
/api/v1/projects/{projectId}key: readGet a project
200Project404Not found
/api/v1/projects/{projectId}key: writeUpdate delivery settings
`lodRatios` (Pro and up) lists up to three simplification ratios; every upload then gets one `gpu`-based variant per ratio for the viewer to switch between. An empty list turns the ladder off. `publishOnApproval` (Pro and up) holds replacement versions until a reviewer approves: `owner` needs an owner or admin, `any` accepts anyone the share link admits; the first version of an asset always publishes.
{
"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+)?$"
}
},
"lodRatios": {
"maxItems": 3,
"type": "array",
"items": {
"type": "number",
"minimum": 0.1,
"maximum": 0.9,
"multipleOf": 0.05
}
},
"autoUsdz": {
"type": "boolean"
},
"publishOnApproval": {
"type": "string",
"enum": [
"off",
"owner",
"any"
]
}
}
}200Project400Invalid body402Plan does not include this feature404Not found
/api/v1/projects/{projectId}dashboard onlyDelete an empty project
Owners and admins only. 409 with `reason: "not-empty"` while the project holds assets (the trash included), collections, or environments, and `reason: "last-project"` for the organization's only project.
200Deleted404Not found409Not empty, or the last project
Organizations
/api/v1/orgs/{orgId}key: writeUpdate organization branding
{
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"brandColor": {
"anyOf": [
{
"type": "string",
"pattern": "^#[0-9a-fA-F]{6}$"
},
{
"type": "null"
}
]
}
}
}200Organization400Invalid body404Not found
Uploads
/api/v1/uploadskey: writeStart 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`. Accepted files: `.glb`, self-contained `.gltf`, or a `.zip` holding one `.gltf` (or `.glb`) and the files it references; a pack job turns the archive into the original GLB while the variants are optimized from it directly.
{
"type": "object",
"properties": {
"projectId": {
"type": "string",
"minLength": 1
},
"filename": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Ends in .glb, .gltf, or .zip (a glTF with the files it references)"
},
"contentType": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"bytes": {
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
"assetId": {
"type": "string",
"minLength": 1
},
"note": {
"anyOf": [
{
"type": "string",
"maxLength": 2000
},
{
"type": "null"
}
]
}
},
"required": [
"projectId",
"filename",
"contentType",
"bytes"
]
}200Upload target400Invalid body413File too large415Unsupported format
/api/v1/uploads/{versionId}/completekey: writeFinish an upload and queue optimization
200Queued jobs404Not found409Object not yet on the CDN, or the upload was aborted
/api/v1/uploads/{versionId}/abortkey: writeGive 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.
200Version marked failed404Not found409The upload already completed
Assets
/api/v1/assetskey: readList or search assets
Query: `projectId` (defaults to the active project), `q` (case-insensitive substring of the name or description, or a prefix of the id), `tag` (exact), `deleted=true` (the trash instead of live assets). Items carry `tags`, `reviewState` (`approved`, `changes_requested`, `in_review`, or null for the newest version), `latestVersion`, and `deletedAt`.
200Assets400Invalid body
/api/v1/assets/{assetId}key: readGet an asset with versions, variants, and review state
Each version carries `note` and `review` (`approved` and `changesRequested` counts, each reviewer's current stance, and `awaitingApproval` when publish-on-approval is holding it).
200Asset detail404Not found
/api/v1/assets/{assetId}key: writeUpdate name, visibility, viewer settings, hotspots, tags; move between projects
`tags` replaces the labels (lower-case `a-z0-9_-`, up to 20; a bad tag answers 400 naming it). `projectId` moves the asset to another project of the organization: embeds keep working, but signed URLs minted with the old project's key stop verifying.
{
"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"
},
"variantPicker": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean"
},
"labels": {
"type": "object",
"propertyNames": {
"type": "string",
"maxLength": 64
},
"additionalProperties": {
"type": "string",
"maxLength": 64
}
},
"swatches": {
"type": "object",
"propertyNames": {
"type": "string",
"maxLength": 64
},
"additionalProperties": {
"type": "string",
"maxLength": 2000,
"pattern": "^(#[0-9a-fA-F]{3,8}|[a-zA-Z]+|rgba?\\([\\d\\s.,%]+\\)|https:\\/\\/.+)$"
}
}
},
"required": [
"enabled"
]
}
}
},
{
"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"
]
},
"variant": {
"type": "string",
"maxLength": 200
}
},
"required": [
"id",
"title",
"position",
"normal"
]
}
},
"tags": {
"maxItems": 20,
"type": "array",
"items": {
"type": "string",
"maxLength": 64
}
},
"projectId": {
"type": "string",
"minLength": 1
}
}
}200Asset400Invalid body404Not found
/api/v1/assets/{assetId}key: writeMove an asset to the trash, or purge it
Moves the asset to the trash, where it stays restorable for 30 days before the nightly purge. `?permanent=1` deletes a trashed asset and its files for good; a live asset answers 409, so deletion is always two steps.
200Deleted404Not found409Live asset with `permanent=1`
/api/v1/assets/{assetId}/restorekey: writeRestore an asset from the trash
200Asset404Not found
/api/v1/assets/{assetId}/thumbnail/renderkey: writeRender the poster and link preview now
Screenshots the current version in a headless browser (the `gpu` preset when ready, else the project default) into `thumb.png` and the 1200 × 630 `og.png`, replacing a captured poster. Renders also run on their own when a version goes live.
200Rendered404Not found409No live version yet502The browser could not render the page
/api/v1/assets/{assetId}/thumbnailkey: writeUpload a poster image
Raw `image/png` body up to 4 MiB, typically captured from the viewer.
{
"type": "string",
"format": "binary"
}200Stored404Not found413Too large415Not a PNG
Variants
/api/v1/assets/{assetId}/variantskey: readList variants per version
200Versions with variants; ready variants with stats carry a `score`404Not found
/api/v1/assets/{assetId}/variantskey: writeRequest 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. Spec keys include `palette` (merge texture-less materials into one palette texture) and `ntex` (a separate size cap for normal, occlusion, and metallic-roughness textures). Returns 201 when a job was queued, 200 when the variant already existed.
{
"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
},
"ntex": {
"anyOf": [
{
"type": "number",
"const": 256
},
{
"type": "number",
"const": 512
},
{
"type": "number",
"const": 1024
},
{
"type": "number",
"const": 2048
},
{
"type": "number",
"const": 4096
}
]
},
"palette": {
"type": "boolean"
}
},
"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"
]
}
}
}200Existing variant201Created400Invalid body404Not found409No uploaded version
/api/v1/assets/{assetId}/compatkey: readRuntime 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).
200Versions with variant verdicts404Not found
Versions
/api/v1/assets/{assetId}/versions/{versionId}key: writeSet or clear a version note
The note says what changed in this version; it shows on the dashboard and on share pages next to the change chips.
{
"type": "object",
"properties": {
"note": {
"anyOf": [
{
"type": "string",
"maxLength": 2000
},
{
"type": "null"
}
]
}
},
"required": [
"note"
]
}200Version400Invalid body404Not found
Reviews
/api/v1/assets/{assetId}/versions/{versionId}/reviewskey: readList review decisions on a version
Newest first. `summary` counts approvals and change requests by each reviewer's current stance (their latest decision); `awaitingApproval` is true while publish-on-approval holds this ready version.
200Reviews404Not found
/api/v1/assets/{assetId}/versions/{versionId}/reviewskey: writeApprove or request changes
Emits `version.approved` or `version.changes_requested`. When the project publishes on approval and the decision comes from someone the mode admits (`owner`: an owner or admin's dashboard session; `any`: anyone, API keys included), approving the newest ready version makes it live and emits `asset.ready`; `promoted` says whether that happened. Key-authored decisions show the key name as the reviewer.
{
"type": "object",
"properties": {
"decision": {
"type": "string",
"enum": [
"approved",
"changes_requested"
]
},
"note": {
"anyOf": [
{
"type": "string",
"maxLength": 2000
},
{
"type": "null"
}
]
}
},
"required": [
"decision"
]
}201Recorded400Invalid body404Not found409Version still uploading
Comments
/api/v1/assets/{assetId}/commentskey: readList review comments on an asset
Chronological; replies carry `parentId`. `anchor` holds the pinned surface point, normal, and camera.
200Comments404Not found
/api/v1/assets/{assetId}/commentskey: writePost a comment or reply
Key-authored comments show the key name as the author. Emits `comment.created` to webhooks.
{
"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"
]
},
"measure": {
"type": "object",
"properties": {
"a": {
"type": "array",
"prefixItems": [
{
"type": "number"
},
{
"type": "number"
},
{
"type": "number"
}
],
"items": false,
"minItems": 3,
"maxItems": 3
},
"b": {
"type": "array",
"prefixItems": [
{
"type": "number"
},
{
"type": "number"
},
{
"type": "number"
}
],
"items": false,
"minItems": 3,
"maxItems": 3
}
},
"required": [
"a",
"b"
]
}
},
"required": [
"position",
"normal"
]
},
{
"type": "null"
}
]
},
"versionId": {
"anyOf": [
{
"type": "string",
"minLength": 1,
"maxLength": 64
},
{
"type": "null"
}
]
}
},
"required": [
"body"
]
}201Created400Invalid body404Not found
/api/v1/comments/{commentId}key: writeResolve or reopen a comment
{
"type": "object",
"properties": {
"resolved": {
"type": "boolean"
}
},
"required": [
"resolved"
]
}200Updated400Invalid body404Not found
/api/v1/comments/{commentId}key: writeDelete a comment (and its replies)
204Deleted404Not found
Exports
/api/v1/assets/{assetId}/exportskey: readList exports
200Exports
/api/v1/assets/{assetId}/exportskey: writeExport a ready variant as an unpacked glTF zip, a C-array header, or a Quick Look USDZ
Served from the CDN at `/{assetId}.zip`, `/{assetId}.h`, or `/{assetId}.usdz` with the variant's query string once ready. `usdz` needs a variant with JPEG or PNG textures (400 for KTX2); projects with `autoUsdz` get it for every version's compat variant without asking.
{
"type": "object",
"properties": {
"variantId": {
"type": "string",
"minLength": 1
},
"format": {
"type": "string",
"enum": [
"gltf-zip",
"c-array",
"usdz"
]
}
},
"required": [
"variantId",
"format"
]
}200Existing export201Created400Invalid body, or usdz requested for a KTX2 variant404Not found409Variant not ready
Environments
/api/v1/environmentskey: readList project environments
200Environments
/api/v1/environmentskey: writeUpload 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=`.
{
"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"
]
}201Created400Invalid body413Too large415Unsupported image
/api/v1/environments/{envId}key: readGet an environment
200Environment404Not found
/api/v1/environments/{envId}key: writeDelete an environment
200Deleted404Not found
Delivery
/api/v1/assets/{assetId}/signkey: writeMint a signed CDN URL
{
"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
},
"ntex": {
"anyOf": [
{
"type": "number",
"const": 256
},
{
"type": "number",
"const": 512
},
{
"type": "number",
"const": 1024
},
{
"type": "number",
"const": 2048
},
{
"type": "number",
"const": 4096
}
]
},
"palette": {
"type": "boolean"
}
},
"required": [
"geo",
"tex",
"maxtex",
"q",
"anim"
]
},
"versionId": {
"type": "string"
},
"ttlSeconds": {
"type": "integer",
"minimum": 60,
"maximum": 604800
},
"dl": {
"type": "boolean"
}
}
}200Signed URL400Invalid body404Not found
Collections
/api/v1/collectionskey: readList collections
200Collections with asset counts
/api/v1/collectionskey: writeCreate a collection
{
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 120
},
"description": {
"anyOf": [
{
"type": "string",
"maxLength": 1000
},
{
"type": "null"
}
]
}
},
"required": [
"name"
]
}201Created400Invalid body
/api/v1/collections/{collectionId}key: readCollection with its assets in order
200Collection detail404Not found
/api/v1/collections/{collectionId}key: writeRename a collection or replace its membership
`assetIds` replaces the membership in the given order (max 50; ids outside the project are dropped).
{
"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
}
}
}
}200Updated400Invalid body404Not found
/api/v1/collections/{collectionId}key: writeDelete a collection (its share links stop resolving)
200Deleted404Not found
Billing
/api/v1/billing/checkoutdashboard onlyStart a hosted Stripe Checkout for a paid plan
Dashboard session with the owner role. Returns the Checkout URL to redirect to.
{
"type": "object",
"properties": {
"plan": {
"type": "string",
"enum": [
"pro",
"business"
]
},
"interval": {
"default": "month",
"type": "string",
"enum": [
"month",
"year"
]
}
},
"required": [
"plan"
]
}200Checkout URL400Invalid body403Not an owner409Billing not configured
/api/v1/billing/portaldashboard onlyOpen the Stripe Customer Portal
Dashboard session with the owner role; needs an existing subscription.
200Portal URL403Not an owner409No subscription or billing not configured
/api/v1/billing/switch-intervaldashboard onlySwitch the plan subscription between monthly and yearly billing
Dashboard session with the owner role; needs an active paid subscription. The cycle restarts now and is invoiced immediately with the unused remainder credited. Yearly billing keeps the metered overage prices on a second, monthly subscription.
{
"type": "object",
"properties": {
"interval": {
"type": "string",
"enum": [
"month",
"year"
]
}
},
"required": [
"interval"
]
}200The new interval and the plan400Invalid body403Not an owner409No subscription, already on that interval, or billing not configured
Account
/api/v1/mekey: readWho the key or session is
The org and its plan, the project requests work in (a key pinned to a project, the `X-GLBoost-Project` header, or the oldest project), the member role (null for keys), and the key name and scopes. The SDK defaults `projectId` from it; `glboost whoami` prints it.
200Caller identity
Usage
/api/v1/usagekey: readUsage 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`).
200Usage summary
Shares
/api/v1/shareskey: readList share links for an asset or collection
Query: `assetId` or `collectionId` (one is required).
200Shares400Invalid body
/api/v1/shareskey: writeCreate a share link
`allowComments` lets viewers leave review comments; `allowReview` (defaults to `allowComments`) lets them approve or request changes on the version they see.
{
"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"
},
"allowReview": {
"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"
]
}201Created400Invalid body404Not found
/api/v1/shares/{shareId}key: readGet a share link
200Share404Not found
/api/v1/shares/{shareId}key: writeUpdate a share link
{
"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"
},
"allowReview": {
"type": "boolean"
},
"expiresAt": {
"anyOf": [
{},
{
"type": "null"
}
]
},
"maxViews": {
"anyOf": [
{
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 1000000
},
{
"type": "null"
}
]
},
"notifyOnView": {
"type": "boolean"
},
"pinnedVersionId": {
"type": [
"string",
"null"
]
}
}
}200Share400Invalid body404Not found
/api/v1/shares/{shareId}key: writeRevoke a share link
200Revoked404Not found
/api/v1/shares/{shareId}/eventskey: readAudit log: sessions with their events
200Sessions404Not found
API keys
/api/v1/keysdashboard onlyList API keys
200Keys (hashes omitted)
/api/v1/keysdashboard onlyCreate an API key
The plain key is returned once in `key`; only its hash is stored.
{
"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"
]
}201Created400Invalid body404Not found
/api/v1/keys/{keyId}dashboard onlyRevoke an API key
200Revoked404Not found
Members
/api/v1/membersdashboard onlyList members and open invitations
Invitation links are included for owners and admins only.
200Members, invitations, and seats used against the plan
/api/v1/members/{userId}dashboard onlyChange a member's role
Owners and admins may set roles no higher than their own, never their own role; the last owner cannot be demoted.
{
"type": "object",
"properties": {
"role": {
"type": "string",
"enum": [
"owner",
"admin",
"member"
]
}
},
"required": [
"role"
]
}200Updated400Invalid body403Not allowed for your role404Not found409Would leave the organisation without an owner
/api/v1/members/{userId}dashboard onlyRemove a member (or leave)
Anyone may remove themselves except the last owner.
200Removed403Not allowed for your role404Not found409Would leave the organisation without an owner
/api/v1/members/invitesdashboard onlyInvite someone by email
Owners and admins only; admins cannot grant the owner role. Counts against the plan's seats (402 when full). An open invitation for the same address is replaced, so this doubles as "resend". The invitation is emailed when email is configured; `email.sent` says whether it was.
{
"type": "object",
"properties": {
"email": {
"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,}$"
},
"role": {
"default": "member",
"type": "string",
"enum": [
"owner",
"admin",
"member"
]
}
},
"required": [
"email"
]
}201Created400Invalid body402Plan has no free seat409Already a member
/api/v1/members/invites/{inviteId}dashboard onlyRevoke an invitation
200Revoked404Not found
Webhooks
/api/v1/webhooksdashboard onlyList webhooks
200Webhooks (secrets omitted)
/api/v1/webhooksdashboard onlyCreate a webhook
The signing secret is returned once in `secret`.
{
"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",
"version.approved",
"version.changes_requested",
"usage.threshold"
]
}
}
},
"required": [
"url",
"events"
]
}201Created400Invalid body
/api/v1/webhooks/{webhookId}dashboard onlyUpdate a webhook
{
"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",
"version.approved",
"version.changes_requested",
"usage.threshold"
]
}
}
}
}200Webhook400Invalid body404Not found
/api/v1/webhooks/{webhookId}dashboard onlyDelete a webhook
200Deleted404Not found
/api/v1/webhooks/{webhookId}/deliveriesdashboard onlyRecent deliveries
Query: `limit` (1–100, default 20).
200Deliveries404Not found