Gent API Documentation

Files & Folders

Structured storage where files are participants in workflows, not passive attachments. Every file traces back to the email or workflow write that created or updated it. Spreadsheets accumulate rows from extracted email data. Documents are revised by AI on each new trigger. Contacts become collaborators and viewers. The inbox is the hub — email is the interface.

Scopes: files:read for GET operations and signed download link generation. files:write permits creating files/folders, updating editable metadata/content, and appending rows. Destructive actions require owner or tenant-admin authority; collaborators can edit but cannot delete, move, or re-share. File download links are public endpoints authenticated by a signed token — no auth header required.
Files vs attachments: Message attachments are message-scoped upload/download objects. They are not Files records by default. Promote an attachment into Files when it needs owner, folder, version, viewer, or collaborator metadata.
File types: spreadsheet — row-level API, AI-populated from email context. document — text content, AI-revised per trigger. image — binary, optional AI description. video — binary, store/serve only. template — email template with variable rendering. skill — agent skill definition (JSON/YAML). other — anything else.
Versioning: Every content write (upload or workflow update) creates a new FileVersion. The file record always points to the current version. Previous versions remain accessible via GET /v1/files/{file_id}/versions/.
GET /v1/files/reference/ List file types and role capabilities  no auth required
200 OK
{ "version": "2026-06-06", "resource": "files", "payload": { "create_file": { "method": "POST", "path": "/v1/files/" }, "download_link": { "method": "POST", "path": "/v1/files/{file_id}/link/" } }, "fields": [...], "access_model": { "tenant_owned_default": "tenant_members_can_read", "collaborator": "edit_only_no_delete_move_reshare" }, "file_types": ["document", "image", "other", "skill", "spreadsheet", "template", "video"], "scopes": ["inbox", "tenant"], "roles": [ { "role": "collaborator", "can_read": true, "can_download": true, "can_edit": true, "can_delete": false, "can_share": false } ], "thread_share_roles": ["viewer", "collaborator"] }
This is the builder contract for Files clients and keeps legacy option arrays for compatibility. Tenant-scoped files are readable by tenant members by default. Collaborators can edit but cannot delete, move, or re-share.
Folders
GET /v1/files/folders/ List folders  scope: files:read
Query params
ParameterDescription
scopeInbox (default) | tenant.
inbox_idrequiredStringrequired// required.
scopestringoptionalInbox (default) | tenant.
inbox_idstringrequiredRequired for scope=inbox.
parent_idnulloptionalNest under another folder.
PATCH /v1/files/folders/{folder_id}/ Rename or reparent  scope: files:write
{ "name": "Renamed", "parent_id": "<folder-id>" }
Reparenting stays inside the same inbox or tenant folder tree. A folder cannot be moved under itself or one of its descendants.
DEL /v1/files/folders/{folder_id}/ Delete folder  scope: files:write
Request body (optional)
{ "contents": "unparent", "target_folder_id": null }
contents defaults to unparent, which keeps direct files and child folders by moving them to the root. Use delete to recursively delete child folders and files in the subtree, or move with target_folder_id to move direct files and child folders before deleting the folder. Move targets must be in the same tree and cannot be inside the deleted folder's subtree.
Files
GET /v1/files/ List files  scope: files:read
Query params
ParameterDescription
scopeInbox (default) | tenant.
inbox_idrequiredFilter to a specific folder.
POST /v1/files/ Upload a file → file_id  scope: files:write
Multipart upload (binary files)
# Content-Type: multipart/form-data # Fields: file (binary), name, file_type, scope, inbox_id, folder_id, instructions
JSON body (text content)
{ "name": "vendor-notes.txt", "file_type": "document", "content": "Plain text content...", "scope": "inbox", "inbox_id": "<inbox-id>", "folder_id": null, "instructions": "Track vendor communications and key decisions.", "thread_id": null }
201 Created
{ "file_id": "<file-id>", "name": "vendor-notes.txt", "file_type": "document", "scope": "inbox", "inbox_id": "<inbox-id>", "tenant_id": "<tenant-id>", "folder_id": null, "size": 1024, "content_type": "text/plain", "version": 1, "row_count": 0, "schema": null, "instructions": "Track vendor communications...", "thread_id": null, "source_message_id":null, "viewers": [], "collaborators": [], "created_at": "2026-05-31T00:00:00Z", "updated_at": "2026-05-31T00:00:00Z" }
Max upload size: 100 MB. Returns 413 if exceeded.
PATCH /v1/files/{file_id}/ Update file metadata  scope: files:write
{ "name": "renamed.csv", "folder_id": "<folder-id>", "instructions": "Updated LLM instructions for this file", "viewers": ["<contact-id>"], "collaborators": ["<contact-id>"] }
Move a file by setting folder_id; set it to null to unparent the file. Changing folder_id, viewers, or collaborators requires owner or tenant-admin authority. Content updates are uploaded to POST /v1/files/{file_id}/versions/; PATCH only affects metadata.
DEL /v1/files/{file_id}/ Delete file  scope: files:write
# 204 No Content. Deletes stored file versions, all FileVersion records, and all rows.
GET /v1/files/{file_id}/versions/ List all versions of a file, newest first  scope: files:read
200 OK
[{ "version_id": "<version-id>", "file_id": "<file-id>", "version_number": 3, "size": 204800, "source_message_id": "<message-id>", "created_at": "2026-05-20T08:14:00Z" }]
FieldTypeRequiredDescription
source_message_idstringoptionalSet when version came from an email attachment; null otherwise.
POST /v1/files/{file_id}/versions/ Upload a new current version  scope: files:write
Multipart upload
# Content-Type: multipart/form-data # Fields: file (binary), content_type, source_message_id
JSON text upload
{ "content": "Updated text content...", "content_type": "text/plain", "source_message_id": null }
Creates a new FileVersion, updates the file record to point at the new current content, and leaves older versions available from the versions list. Collaborators can upload content versions; viewers cannot.
Download
POST /v1/files/{file_id}/link/ Generate signed download link  scope: files:read
Request
{ "expires_seconds": 604800 }
FieldTypeRequiredDescription
expires_secondsnumberoptionalDefault 7 days, max 1 year.
200 OK
{ "url": "https://api.gent.mx/v1/files/<file-id>/download/?token=...&expires=...", "expires_at": "2026-06-07T00:00:00Z" }
The download URL requires no authentication — the signed token is sufficient. Embed it in emails, share with contacts, or use as a public link. The token is HMAC-signed and expires at the stated time. Max expiry is 1 year.
GET /v1/files/{file_id}/download/ Download file content  no auth — token-gated
Query params
ParameterDescription
tokenRequired — from POST /link/.
expiresrequiredDefault 100, max 1000.
offsetDefault 0.
Response
{ "schema": ["Vendor", "Amount", "Date", "Invoice #"], "rows": [{ "row_id": "<row-id>", "row_number": 1, "data": { "Vendor": "Acme", "Amount": "$1,200", "Date": "2026-05-30", "Invoice #": "INV-441" }, "source_message_id":"<message-id>", "created_at": "2026-05-30T14:00:00Z" }] }
POST /v1/files/{file_id}/rows/ Append rows  scope: files:write
Request
{ "rows": [ { "Vendor": "Acme", "Amount": "$1,200", "Date": "2026-05-30" }, { "Vendor": "Globex", "Amount": "$850", "Date": "2026-05-31", "Notes": "Net 30" } ], "source_message_id": "<message-id>" }
201 Created
{ "appended": 2, "rows": [{ "row_id": "row-1", "row_number": 1 }] }
New column names (e.g. Notes above) are automatically merged into the file's schema. Columns are additive — existing columns are never renamed or removed. Row numbers are assigned sequentially and are stable.
Workflow writes
SurfaceAdditional fieldsNotes
writes[]"source": {"from": "trigger.file|trigger.email|trigger.attachment|trigger.embedded_image|event.payload"}, "target": {"type": "file.spreadsheet.rows", "file_id": "<spreadsheet-id>"} or any target from /v1/workflows/reference/; "policy" is optionalPublic workflow path for extracted data and deterministic side effects. Gent reads the target contract, extracts values from the source, validates required fields, and either commits the result or holds it for review. Attachment and embedded-image sources use safe references and promote the selected blob into Files before extraction. Requires workflows:full.
source.extract"task": "extract_invoice", optional "engine", "ocr_engine", and "config"Optional preparation inside a write. Use it when the source needs OCR or structured extraction before the target can be committed.
source.mapOptional explicit field overrides such as {"Amount": "total_amount"}Most targets infer mappings from their contract. Use this only when a client needs controlled overrides.
Persistent instructions: Setting instructions on a file acts as a long-running prompt that shapes every AI interaction with that file. For spreadsheets: "Extract invoice number, vendor name, amount, and payment terms." For documents: "This is a vendor profile — track their pricing, key contacts, and any commitments made." Instructions set once apply to all future workflow writes targeting the file.
Provenance: Every row (source_message_id) and every version (source_message_id) traces back to the email that caused the write. Follow the chain: file → version → message to reconstruct why any piece of data exists.
Collaborators & events: The collaborators field is a list of contact IDs. Every file mutation (file.created, file.updated, file.version_added, file.rows_appended, file.deleted) fires a webhook event that includes the collaborator list. The inbox webhook consumer — or a workflow triggered by file_version_added — can read this list and email collaborators a fresh download link, giving them access without requiring accounts.
Skill files (file_type: "skill"): Markdown files (SKILL.md) containing structured instructions for how an agent should handle specific scenarios — persona definitions, domain-specific playbooks, tool usage guides, tone guidelines. An agent can read a skill file at runtime to load context-specific instructions. Store skills at tenant scope to share across all inboxes; store at inbox scope for per-inbox specialization.
Email templates: Email templates (GET/POST /v1/templates/) can be downloaded as files via this API. Set file_type: "template" when creating a file from template content. Editing a template file and re-uploading it does not automatically update the original template record — use the templates endpoint for active template management.
GDPR Subject Access Request: GET /v1/compliance/export/ returns a JSON data export and, when an inbox_id is available, also saves it as a file_type: "other" file and includes a _download key with a signed link valid for 7 days. The file is retrievable later via the Files API.
Labels

Create and manage labels, then apply them to messages or contacts. Labels carry a scope that controls what agents can do with them — set at creation time. Labels with type contact or any can also be used to broadcast a message to every contact in the group.

GET /v1/labels/reference/ List label scopes and type metadata  no auth required
200 OK
{ "version": "2026-06-06", "resource": "labels", "payload": { "create_label": { "method": "POST", "path": "/v1/labels/" }, "apply_message_label": { "method": "POST", "path": "/v1/messages/{message_id}/labels/" }, "apply_context_label": { "method": "POST", "path": "/v1/context/{label_id}/entities/" } }, "fields": [...], "label_scopes": [ { "scope": "read_apply", "can_apply": true, "can_remove": false } ], "label_types": [ { "type": "contact", "client_create_supported": false } ] }
Current create/update payloads expose label_scope. label_type is returned as metadata for system-created and inferred labels, including contact and calendar contexts.
GET /v1/labels/ List labels for an inbox  scope: labels:read
[ { "id": "<label-id>", "inbox_id": "<inbox-id>", "name": "priority", "label_scope": "read_write", "color": "#e8a045", "created_at": "2026-05-01T10:00:00Z", "updated_at": "2026-05-22T10:00:00Z" } ]
POST /v1/labels/ Create a label  scope: labels:write → id
Request
{ "inbox_id": "you@example.com", "name": "priority", "label_scope": "read_write", "color": "#e8a045" }
FieldTypeRequiredDescription
inbox_idstringrequired
namestringrequired
label_scopestringoptionalRead_only · read_apply · read_write (default: read_write)
colorstringoptional
201 Created
{ "id": "<label-id>", "inbox_id": "<inbox-id>", "name": "priority", "label_scope": "read_write", "color": "#e8a045", "created_at": "2026-05-22T10:00:00Z", "updated_at": "2026-05-22T10:00:00Z" }
POST /v1/messages/{id}/labels/ Apply a label to a message  scope: labels:write
Request
{ "label_id": "<label-id>" }
204 No Content
# Empty body — success indicated by 204 status code.
DEL /v1/messages/{id}/labels/{label_id}/ Remove a label  scope: labels:write
# 204 No Content — empty body
GET /v1/labels/{label_id}/ Get a label  scope: labels:read
{ "id": "<label-id>", "inbox_id": "<inbox-id>", "name": "priority", "label_scope": "read_write", "color": "#e8a045", "created_at": "2026-05-01T10:00:00Z", "updated_at": "2026-05-22T10:00:00Z" }
PATCH /v1/labels/{label_id}/ Update a label  scope: labels:write
Request — all fields optional
{ "name": "urgent", "label_scope": "read_apply", "color": "#c23b2a" }
FieldTypeRequiredDescription
namestringoptionalOmit fields to leave unchanged.
label_scopestringoptional
colorstringoptional
200 OK
{ "id": "<label-id>", "inbox_id": "<inbox-id>", "name": "urgent", "label_scope": "read_apply", "color": "#c23b2a", "created_at": "2026-05-22T10:00:00Z", "updated_at": "2026-05-22T10:00:00Z" }
DEL /v1/labels/{label_id}/ Delete a label  scope: labels:write
# 204 No Content — empty body
→ Label field reference
Note: For POST /v1/labels/ with token auth, inbox_id must still be included in the request body — this is the one write endpoint where it is not auto-derived.
Label CRUD and apply/remove accept agent token or session auth. Token calls require labels:write (or labels:read for GET). Apply and remove also require labels:write. The label's own scope then further gates what the agent can do: read_only blocks both apply and remove; read_apply allows apply only; read_write allows both.
Label broadcastPOST /v1/messages/broadcast/ sends a message to every contact tagged with a label.
  • Label label_type must be contact or any — broadcasting to a message-only label returns 400.
  • Asynchronous — returns 202 with a broadcast_id; poll GET /v1/messages/broadcasts/{broadcast_id}/ for aggregate delivery status.
  • The inbox's signature is appended to each message (if set).
  • Each recipient gets a unique unsubscribe link as a footer and via List-Unsubscribe / List-Unsubscribe-Post headers (RFC 2369 + RFC 8058).
  • Contacts who previously unsubscribed from this label, or whose address has hard-bounced, are skipped automatically (suppressed_count in the status response).