# Cubster Assets API > Cubster is an agent-first developer platform on Netlify. This is the P1 > platform backbone: a REST API for uploading, managing, and serving assets > (images and generic files), scoped to a workspace. Base URL: > https://app.cubster.dev All responses are JSON unless serving file bytes. Errors are `{ "error": "..." }` with a matching HTTP status. All data is workspace-scoped; you can never read or mutate assets outside the workspace your credential resolves to. ## Authentication Three credential types: - Session (first-party): a Netlify Identity cookie (`nf_jwt`). Access is invite-only — your email must be on the approved safelist. A non-invited login is recorded on the waitlist and rejected. First approved login auto-creates your profile and a personal workspace. - API key (programmatic): send `Authorization: Bearer csk_...`. A key is bound to one workspace, which scopes every request it makes. Keys are shown in full exactly once, at creation; only a prefix is shown afterward. - Upload grant (third-party browser upload): a short-lived signed token that authorizes a single direct upload. See "Client uploads" below. ## Endpoints ### Current user (session or key for GET; session only for PATCH) - `GET /api/v1/me` — session-or-key bootstrap. With an API key, returns `{ profile: { id, email, name, avatarUrl }, workspace: }` — no `isApproved` (a key only ever exists for an already-approved account, and it can never see another workspace). With a session, an approved session returns `{ isApproved: true, profile, workspace: }`; an authenticated-but-not-yet-approved session returns `200 { isApproved: false, email }` (its login is recorded on the waitlist) so a client can show a waitlist screen. An unauthenticated request is `401`. - `PATCH /api/v1/me` — update your profile (session only). Body: `{ "name": "..." }` (the only editable field; avatar is synced from Google). Returns `{ profile }`. ### Waitlist (public, unauthenticated) - `POST /api/v1/waitlist` — join the private-beta waitlist. Body: `{ "email": "..." }`. Always responds `200 { "ok": true }` for a well-formed email (whether or not it was already on the list — the response never reveals which, to avoid enumeration); `400 { "error": "..." }` for a malformed or missing email. CORS-scoped to the cubster.dev marketing site. ### Manage API keys (session only) - `POST /api/v1/api-keys` — create a key. Body (optional): `{ "label": "..." }`. Returns the full secret `key` once. Add `?workspace=` to target a specific owned workspace (default: your personal workspace). - `GET /api/v1/api-keys` — list keys (prefixes only, no secrets). - `DELETE /api/v1/api-keys/:id` — revoke a key. ### Manage workspaces (session only) - `GET /api/v1/workspaces` — list your workspaces. Each includes `usage: { bytes, cap, pct }` — current stored bytes, the per-workspace cap, and usage as a rounded percentage of the cap. - `POST /api/v1/workspaces` — create one. Body: `{ "name": "..." }`. - `PATCH /api/v1/workspaces/:id` — rename a workspace you own. Body: `{ "name": "..." }`. A workspace you don't own resolves to `404`. - `DELETE /api/v1/workspaces/:id` — delete a workspace you own (its assets' blobs are purged, then the row — cascading its assets + keys). You cannot delete your only workspace (`400`); a workspace you don't own resolves to `404`. ### Upload - `POST /api/v1/uploads` — direct upload (session or key). `multipart/form-data` with a `file` field. Optional fields: `name` (display name), `tags` (comma-separated or JSON array), `width`, `height`. Returns the created asset. - `POST /api/v1/uploads/grant` — mint a client-upload grant (key only). JSON body (all optional): `{ "maxSize": , "mimeTypes": ["image/png", ...], "ttlSeconds": <=600 }`. A grant may narrow but never widen the limits. Returns `{ token, uploadUrl, grantHeader, maxSize, mimeTypes, expiresAt }`. - `POST /api/v1/uploads/client` — the grant target (grant auth). `multipart/form-data` with a `file` field, plus the grant in the `X-Cubster-Grant` header (header only — it is verified before the body is read). Single-use: the grant is consumed on success. ### Assets (session or key) - `GET /api/v1/assets` — list. Query: `type` (`image`|`file`), `search`, `tags` (comma-separated, matches assets containing all), `limit` (max 100). - `GET /api/v1/assets/:id` — detail. - `PATCH /api/v1/assets/:id` — update. JSON body: `{ "displayName"?, "tags"? }`. - `DELETE /api/v1/assets/:id` — delete the asset (blob + row). ### Serve (public, by opaque key) - `GET /img/:key` — raster image via the Netlify Image CDN. Transform with a positional cascade `/img/:key/:width/:height/:fit/:quality/:format` or a named preset: `/img/thumb/:key` (150x150), `/img/avatar/:key` (256x256), `/img/hero/:key` (1200x675), `/img/small|medium|large/:key` (w=300|600|1200). - `GET /f/:key` — generic file or SVG as a forced download (Content-Disposition: attachment). SVGs are always served this way, never inline. An asset's response includes `url` (the serve URL), `kind` (`image`|`file`), and for images `rawUrl` plus a `presets` map. ## Limits - Max file size: 25 MB. - Allowed image types: png, jpeg, webp, gif, avif, svg+xml. - Allowed file types: pdf, text/plain, csv, markdown, json, zip. - Per-workspace storage cap: 1 GB (uploads over the cap are rejected). - SVG is accepted but only ever served as a download (never inline / via the CDN).