Skip to content

Ask AI

Ask anything about Destesi — setup, products, APIs.

AI answers may be wrong — always verify against the docs.

Drive reference

The Drive API is served at api.drive.destesi.io and is fully workspace-scoped. Every route is under /v1.

Send one of the following with each request:

  • API key — Authorization: Bearer drive_<token>. Minted with POST /v1/api-keys from a browser session (the Drive web app has no API keys page); the token addresses the workspace it was created in. For scripts, a personal access token is the practical choice.
  • Personal access token — Authorization: Bearer idn_pat_<token> plus an X-Destesi-Workspace: <workspace-slug> header to choose the workspace.
  • Browser session — the drive_session cookie (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.

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). A folder cannot move into itself or its own subfolder (400 self_move).
DELETE /v1/folders/{id} Soft-delete the folder and every folder and file inside it.

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=<text> (case-insensitive match anywhere in the name), ?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.

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.
Terminal window
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.

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. POST and DELETE also require the workspace role owner or admin: a key acts for the whole workspace, so a member listing keys sees them and gets admin_required on mint and revoke. See Roles and permissions. 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.

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.
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

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 wrong_session_kind (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:

  • 400 name_too_long — POST /v1/folders (or a PATCH rename) with a name over 255 characters. missing_name is the empty-name version of the same check; invalid_json is a malformed request body.
  • 400 bad_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).
  • 400 folder_not_found — uploading with a folder_id that doesn’t exist in your workspace (a cross-workspace id surfaces the same way, so one workspace can’t probe another’s folders).
  • 401 unknown_bearer_scheme — sending Authorization: Basic …, or a Bearer token whose prefix is neither drive_ nor idn_pat_. A drive_ token in the wrong format gives api_key_invalid; a revoked key gives api_key_revoked; no credentials at all gives missing_session.
  • 403 wrong_session_kind — calling POST /v1/api-keys with a bearer token instead of a browser session. A key can never mint another key.
  • 404 parent_not_found — creating a folder under a parent_id that doesn’t exist (or belongs to another workspace). not_found is the equivalent for a missing file or folder you address by id.
  • 400 system_folder — renaming, moving or deleting a system-managed folder, or moving a folder into one. Those folders hold what other products save and are hidden from the web app.
  • 400 self_move — moving a folder into itself or into one of its own subfolders.
  • 409 name_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.
  • 503 store_unavailable / storage_unavailable — the database (store_unavailable) or object storage (storage_unavailable) is briefly unreachable. Both are transient; retry with backoff.