Drive reference
The Drive API is served at api.drive.destesi.io and is fully workspace-scoped. Every route is under /v1.
Authentication
Section titled “Authentication”Send one of the following with each request:
- API key —
Authorization: Bearer drive_<token>. Mint one in the Drive web app under API keys. The token addresses the workspace it was created in. - Personal access token —
Authorization: Bearer idn_pat_<token>plus anX-Destesi-Workspace: <workspace-slug>header to choose the workspace. - Browser session — the
drive_sessioncookie (set automatically after single sign-on). Used by the Drive web app.
An Authorization header with an unrecognized scheme or token prefix is rejected with 401 — Drive never silently falls back to the cookie when a bearer token is present.
Folders
Section titled “Folders”| Method & path | Purpose |
|---|---|
GET /v1/folders |
List folders. Optional ?parent=<id> to list one folder’s children (omit for root). |
POST /v1/folders |
Create a folder. Body: { "name": "...", "parent_id": "<id>"? }. |
GET /v1/folders/{id} |
Fetch one folder (used to walk the breadcrumb chain). |
PATCH /v1/folders/{id} |
Rename ({ "name": "..." }) and/or move ({ "parent_id": "<id>" }; null moves to root). |
DELETE /v1/folders/{id} |
Soft-delete the folder. |
A folder response looks like:
{ "id": "…", "parent_id": null, "name": "Reports", "created_at": "2026-05-31T12:00:00.000Z", "created_by": "…", "is_system": false}Folder names are 1–255 characters and unique (case-insensitive) among live siblings. A name collision returns 409 name_conflict; an unknown or cross-workspace parent_id returns 404.
| Method & path | Purpose |
|---|---|
GET /v1/files |
List files. Filters: ?folder=<id>, ?q=<name-prefix>, ?limit=<n>, ?cursor=<token>. |
POST /v1/files/upload |
Upload a file (multipart). See below. |
GET /v1/files/{id} |
File metadata. |
GET /v1/files/{id}/download |
Redirect (302) to a short-lived signed content URL. |
GET /v1/files/{id}/content |
Stream the file bytes inline. |
PATCH /v1/files/{id} |
Rename ({ "name": "..." }) and/or move ({ "folder_id": "<id>" }; null moves to root). |
DELETE /v1/files/{id} |
Soft-delete the file. |
A file response:
{ "id": "…", "folder_id": "…", "name": "report.pdf", "mime_type": "application/pdf", "size_bytes": 184320, "uploaded_by": "…", "uploaded_at": "2026-05-31T12:00:00Z", "created_at": "2026-05-31T12:00:00Z"}List responses are paginated: when more results exist, the response includes a next_cursor you pass back as ?cursor=.
{ "items": [ /* … */ ], "next_cursor": "…" }System folders and their files are excluded from listings by default. Pass ?include_system=true (on an authenticated request) to include them.
Uploading
Section titled “Uploading”POST /v1/files/upload takes a multipart/form-data body:
| Field | Required | Notes |
|---|---|---|
file |
yes | The file bytes. Streamed straight to storage. |
name |
no | Display name. Defaults to the uploaded filename. |
mime |
no | MIME type. Defaults to the file part’s Content-Type. |
folder_id |
no | Destination folder. Validated against your workspace. |
curl https://api.drive.destesi.io/v1/files/upload \ -H "Authorization: Bearer drive_xxxxxxxxxxxx..." \ -F "file=@report.pdf" \ -F "name=Q3 report.pdf" \ -F "folder_id=2f1c…"The response carries the new id and a signed URL to fetch the bytes back:
{ "drive_file_id": "…", "name": "Q3 report.pdf", "mime": "application/pdf", "size": 184320, "signed_content_url": "https://drive.destesi.io/v1/files/…/content?ws=…&exp=…&sig=…", "signed_content_expires_at": "2026-05-31T13:00:00Z"}The signed content URL is valid for one hour and is bound to your workspace — anyone with the link can fetch that one file until it expires, without a session.
API keys
Section titled “API keys”| Method & path | Purpose |
|---|---|
POST /v1/api-keys |
Create a key. Body: { "name": "..." }. Returns the plaintext once. |
GET /v1/api-keys |
List your workspace’s keys (prefix + metadata only; never the secret). |
DELETE /v1/api-keys/{id} |
Revoke a key. |
Creating a key requires a browser session (cookie auth) — a bearer token cannot mint a key. The create response is the only time the full token is returned:
{ "id": "…", "name": "ci-uploads", "display_prefix": "drive_ab12", "created_at": "2026-05-31T12:00:00Z", "token": "drive_ab12cd34…"}Store the token securely. Afterward only the display_prefix (the first 10 characters) is shown, so you can tell keys apart without revealing the secret.
Session & account
Section titled “Session & account”| Method & path | Purpose |
|---|---|
GET /v1/auth/me |
Current user (id, email, name). |
POST /v1/auth/logout |
Clear the session cookie. Always succeeds (idempotent). |
GET /v1/me/products |
The product list powering the 9-dot launcher. |
GET /healthz, GET /readyz |
Health checks. |
Limits
Section titled “Limits”| Limit | Value |
|---|---|
| Maximum upload size | 5 GiB per file |
| Folder / file name length | 1–255 characters |
| Signed content URL lifetime | 1 hour |
| API-key token format | drive_ + 43 base64url characters |
Error codes
Section titled “Error codes”Errors are returned as JSON { "error": "<code>" } with a matching HTTP status. Common codes:
| Status | Example codes |
|---|---|
400 |
invalid_json, missing_name, name_too_long, bad_limit, bad_cursor, folder_not_found |
401 |
missing_session, session_invalid, api_key_invalid, api_key_revoked, unknown_bearer_scheme |
403 |
cookie_required (minting a key over bearer auth) |
404 |
not_found, parent_not_found |
409 |
name_conflict |
503 |
store_unavailable (database briefly unreachable), storage_unavailable (file storage briefly unreachable) |
A concrete trigger for each family:
400name_too_long—POST /v1/folders(or aPATCHrename) with anameover 255 characters.missing_nameis the empty-name version of the same check;invalid_jsonis a malformed request body.400bad_limit/bad_cursor— listing files with a non-numeric or< 1?limit=(bad_limit), or replaying a?cursor=value that isn’t a token this API issued (bad_cursor).400folder_not_found— uploading with afolder_idthat doesn’t exist in your workspace (a cross-workspace id surfaces the same way, so one workspace can’t probe another’s folders).401unknown_bearer_scheme— sendingAuthorization: Basic …, or aBearertoken whose prefix is neitherdrive_noridn_pat_. Adrive_token in the wrong format givesapi_key_invalid; a revoked key givesapi_key_revoked; no credentials at all givesmissing_session.403cookie_required— callingPOST /v1/api-keyswith a bearer token instead of a browser session. A key can never mint another key.404parent_not_found— creating a folder under aparent_idthat doesn’t exist (or belongs to another workspace).not_foundis the equivalent for a missing file or folder you address by id.409name_conflict— uploading a file, or creating/renaming a folder, whose name already exists (case-insensitive) among the live siblings in that folder. Rename or move the existing item first, or pick a different name.503store_unavailable/storage_unavailable— the database (store_unavailable) or object storage (storage_unavailable) is briefly unreachable. Both are transient; retry with backoff.