Files API
Every Files action (14) and data model (3): endpoints, scopes, input, preconditions, events and examples.
Every file you upload, create or receive, in folders with previews.
Module code files · 14 actions · 3 data models · 16 events. A key or token never does more here than the person behind it can; row-level security applies exactly as in the app. Scopes: files (everything), files:read (read only) or files:<capability>.
Resources
| Resource | Table | List and read | POST | PATCH | DELETE |
|---|---|---|---|---|---|
file_links | file_link | /api/v1/file_links | |||
file_upload_links | file_upload_link | /api/v1/file_upload_links | |||
files | file | /api/v1/files | files.update |
Lists take limit (up to 200), cursor, order (e.g. -created_at), fields and q; every other query parameter is a column filter: ?status=paid, ?total[gte]=100, ?id[in]=a,b, ?x=null.
Actions
| Action | Needs | What it does |
|---|---|---|
files.attach | files:read | Attach a file to a record |
files.create_folder | files:write | Create a folder |
files.create_link | files:manage | Create an upload link |
files.detach | files:read | Remove a file from a record |
files.publish | files:manage | Publish a file |
files.remove | files:delete | Delete a file or folder |
files.renew_link | files:manage | Extend an upload link |
files.replace | files:write | Save new contents |
files.revoke_link | files:manage | Close an upload link |
files.sign_url | files:read | Get a download link |
files.sweep | files:manage | Clean up files |
files.unpublish | files:manage | Make a file private |
files.update | files:write | Edit file details |
files.upload | files:write | Record an uploaded file |
files.attach
Attach a file to a record — Puts a file from Files on a record’s Files tab. Needs permission to edit that record.
Needs files:read · changes data
https://app.getera.io/api/v1/file_links/actions/attach- Canonical address:
POST /api/v1/actions/files.attach
Input
| Field | Type | Required | Notes |
|---|---|---|---|
fileId | uuid | yes | File · id of file |
recordType | string | yes | Record type · max 60 |
recordId | uuid | yes | Record |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
unknown_record_type | The record type must be one a module owns. |
cannot_write_record | Attaching needs write on that record’s module. |
file_not_found | The file must exist and not be a folder. |
record_not_found | The record must exist. |
Events it publishes: files.attached
What it writes: file_link (insert) — Links the file to the record
curl -X POST https://app.getera.io/api/v1/file_links/actions/attach \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"fileId":"FILE_ID","recordType":"Record type","recordId":"RECORD_ID"}'{
"ok": true,
"result": "…",
"events": [
"files.attached"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.fileLinks.attach({
fileId: "FILE_ID",
recordType: "Record type",
recordId: "RECORD_ID"
})era file_links attach \
--file-id "FILE_ID" \
--record-type "Record type" \
--record-id "RECORD_ID"{
"name": "era__files__attach",
"arguments": {
"fileId": "FILE_ID",
"recordType": "Record type",
"recordId": "RECORD_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold files:read · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 unknown_record_type, cannot_write_record, file_not_found, record_not_found · 429 slow down
files.create_folder
Create a folder — Adds a folder under an existing path, refusing a name that is already taken there.
Needs files:write · changes data
https://app.getera.io/api/v1/files/actions/create_folder- Canonical address:
POST /api/v1/actions/files.create_folder
Input
| Field | Type | Required | Notes |
|---|---|---|---|
parentPath | string | Inside · max 500 | |
name | string | yes | Name · max 255 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
name_required | A folder needs a name. |
name_has_separator | A folder name cannot contain a slash. |
folder_exists | No sibling folder may already carry that name. |
Events it publishes: files.folder_created
What it writes: file (insert) — Adds the folder row
curl -X POST https://app.getera.io/api/v1/files/actions/create_folder \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"name":"Name"}'{
"ok": true,
"result": "…",
"events": [
"files.folder_created"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.files.createFolder({
name: "Name"
})era files create_folder \
--name "Name"{
"name": "era__files__create_folder",
"arguments": {
"name": "Name"
}
}Errors: 401 no or expired credential · 403 the credential does not hold files:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 name_required, name_has_separator, folder_exists · 429 slow down
files.create_link
Create an upload link — Opens a tokenised door for someone outside the workspace to upload into one folder.
Needs files:manage · changes data
https://app.getera.io/api/v1/file_upload_links/actions/create_link- Canonical address:
POST /api/v1/actions/files.create_link
Input
| Field | Type | Required | Notes |
|---|---|---|---|
label | string | Label · max 255 | |
description | string | Description · max 2000 | |
targetFolder | string | Uploads land in · max 500 | |
maxFiles | number | Maximum files | |
maxFileSizeMb | number | Maximum size (MB) | |
allowedFileTypes | string | Accepted types · Comma-separated extensions or mime prefixes. Empty accepts everything. · max 500 | |
expiresAt | date-time | Expires |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
invalid_limits | The link must accept at least one file of at least one megabyte. |
expiry_in_past | The expiry date must be in the future. |
Events it publishes: files.link_created
What it writes: file_upload_link (insert) — Creates the link and its token
curl -X POST https://app.getera.io/api/v1/file_upload_links/actions/create_link \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"label":"Label"}'{
"ok": true,
"result": "…",
"events": [
"files.link_created"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.fileUploadLinks.createLink({
label: "Label"
})era file_upload_links create_link \
--label "Label"{
"name": "era__files__create_link",
"arguments": {
"label": "Label"
}
}Errors: 401 no or expired credential · 403 the credential does not hold files:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 invalid_limits, expiry_in_past · 429 slow down
files.detach
Remove a file from a record — Takes a file off a record without deleting the file.
Needs files:read · removes or cannot be undone
https://app.getera.io/api/v1/file_links/actions/detach- Canonical address:
POST /api/v1/actions/files.detach
Input
| Field | Type | Required | Notes |
|---|---|---|---|
fileId | uuid | yes | File · id of file |
recordType | string | yes | Record type · max 60 |
recordId | uuid | yes | Record |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
unknown_record_type | The record type must be one a module owns. |
cannot_write_record | Removing needs write on that record’s module. |
Events it publishes: files.detached
What it writes: file_link (delete) — Unlinks the file from the record
curl -X POST https://app.getera.io/api/v1/file_links/actions/detach \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"fileId":"FILE_ID","recordType":"Record type","recordId":"RECORD_ID"}'{
"ok": true,
"result": "…",
"events": [
"files.detached"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.fileLinks.detach({
fileId: "FILE_ID",
recordType: "Record type",
recordId: "RECORD_ID"
})era file_links detach \
--file-id "FILE_ID" \
--record-type "Record type" \
--record-id "RECORD_ID"{
"name": "era__files__detach",
"arguments": {
"fileId": "FILE_ID",
"recordType": "Record type",
"recordId": "RECORD_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold files:read · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 unknown_record_type, cannot_write_record · 429 slow down
files.publish
Publish a file — Makes a file a public asset, served to anybody at its public address.
Needs files:manage · reaches outside (sends, publishes or charges) · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/files/{id}/actions/publish- Canonical address:
POST /api/v1/actions/files.publish
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · File · id of file |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The file must still exist in this workspace. |
is_folder | A folder cannot be published. |
already_public | The file must be private. |
uploaded_through_link | A file sent in by a stranger is re-uploaded before it is published. |
no_object | The entry must have a stored file behind it. |
Events it publishes: files.published
What it writes: file (update) — Sets is_public
curl -X POST https://app.getera.io/api/v1/files/FILE_ID/actions/publish \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'{
"ok": true,
"result": "…",
"events": [
"files.published"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.files.publish("FILE_ID")era files publish FILE_ID{
"name": "era__files__publish",
"arguments": {
"id": "FILE_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold files:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, is_folder, already_public, uploaded_through_link, no_object · 429 slow down
files.remove
Delete a file or folder — Tombstones the entry — and, for a folder, everything beneath it — and returns the objects to purge.
Needs files:delete · removes or cannot be undone · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/files/{id}/actions/remove- Canonical address:
POST /api/v1/actions/files.remove
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · File · id of file |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The file must still exist in this workspace. |
Events it publishes: files.deleted, files.folder_deleted
What it writes: file (update) — Marks the row, and its descendants, deleted
curl -X POST https://app.getera.io/api/v1/files/FILE_ID/actions/remove \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'{
"ok": true,
"result": "…",
"events": [
"files.deleted",
"files.folder_deleted"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.files.remove("FILE_ID")era files remove FILE_ID{
"name": "era__files__remove",
"arguments": {
"id": "FILE_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold files:delete · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found · 429 slow down
files.renew_link
Extend an upload link — Moves an open link’s expiry date without changing the address already sent.
Needs files:manage · changes data
https://app.getera.io/api/v1/file_upload_links/{id}/actions/renew_link- Canonical address:
POST /api/v1/actions/files.renew_link
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Link · id of file_upload_link |
expiresAt | date-time | yes | Open until |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The link must exist in this workspace. |
already_revoked | A closed link is not reopened; a new one is made instead. |
expiry_in_past | The new expiry date must be in the future. |
Events it publishes: files.link_renewed
What it writes: file_upload_link (update) — Moves the expiry date
curl -X POST https://app.getera.io/api/v1/file_upload_links/FILE_UPLOAD_LINK_ID/actions/renew_link \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"expiresAt":"2026-11-02T09:00:00Z"}'{
"ok": true,
"result": "…",
"events": [
"files.link_renewed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.fileUploadLinks.renewLink("FILE_UPLOAD_LINK_ID", {
expiresAt: "2026-11-02T09:00:00Z"
})era file_upload_links renew_link FILE_UPLOAD_LINK_ID \
--expires-at "2026-11-02T09:00:00Z"{
"name": "era__files__renew_link",
"arguments": {
"id": "FILE_UPLOAD_LINK_ID",
"expiresAt": "2026-11-02T09:00:00Z"
}
}Errors: 401 no or expired credential · 403 the credential does not hold files:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, already_revoked, expiry_in_past · 429 slow down
files.replace
Save new contents — Authorises overwriting a file’s bytes and records the new size as a new version.
Needs files:write · changes data
https://app.getera.io/api/v1/files/{id}/actions/replace- Canonical address:
POST /api/v1/actions/files.replace
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · File · id of file |
fileSize | number | yes | New size in bytes · min 0 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The file must still exist in this workspace. |
is_folder | A folder has no contents to save. |
no_object | The entry must have a stored file behind it. |
file_too_large | The new contents must be within the size limit. |
storage_quota_exceeded | The organization must have room left on its plan for the growth. |
Events it publishes: files.replaced
What it writes: file (update) — Records the new size and version
curl -X POST https://app.getera.io/api/v1/files/FILE_ID/actions/replace \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"fileSize":1}'{
"ok": true,
"result": "…",
"events": [
"files.replaced"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.files.replace("FILE_ID", {
fileSize: 1
})era files replace FILE_ID \
--file-size 1{
"name": "era__files__replace",
"arguments": {
"id": "FILE_ID",
"fileSize": 1
}
}Errors: 401 no or expired credential · 403 the credential does not hold files:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, is_folder, no_object, file_too_large, storage_quota_exceeded · 429 slow down
files.revoke_link
Close an upload link — Stops a token being accepted, without touching the files that already came through it.
Needs files:manage · removes or cannot be undone · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/file_upload_links/{id}/actions/revoke_link- Canonical address:
POST /api/v1/actions/files.revoke_link
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Link · id of file_upload_link |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The link must exist in this workspace. |
already_revoked | The link must still be open. |
Events it publishes: files.link_revoked
What it writes: file_upload_link (update) — Closes the link
curl -X POST https://app.getera.io/api/v1/file_upload_links/FILE_UPLOAD_LINK_ID/actions/revoke_link \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'{
"ok": true,
"result": "…",
"events": [
"files.link_revoked"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.fileUploadLinks.revokeLink("FILE_UPLOAD_LINK_ID")era file_upload_links revoke_link FILE_UPLOAD_LINK_ID{
"name": "era__files__revoke_link",
"arguments": {
"id": "FILE_UPLOAD_LINK_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold files:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, already_revoked · 429 slow down
files.sign_url
Get a download link — Decides whether a private object may be handed out, and for how long.
Needs files:read · changes data
https://app.getera.io/api/v1/files/{id}/actions/sign_url- Canonical address:
POST /api/v1/actions/files.sign_url
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · File · id of file |
ttlSeconds | number | Valid for (seconds) |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The file must still exist in this workspace. |
is_folder | A folder cannot be downloaded. |
no_object | The entry must have a stored file behind it. |
ttl_too_long | A download link can last at most 24 hours. |
Events it publishes: files.url_signed
What it writes: file (update) — Counts the download
curl -X POST https://app.getera.io/api/v1/files/FILE_ID/actions/sign_url \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"ttlSeconds":1}'{
"ok": true,
"result": "…",
"events": [
"files.url_signed"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.files.signUrl("FILE_ID", {
ttlSeconds: 1
})era files sign_url FILE_ID \
--ttl-seconds 1{
"name": "era__files__sign_url",
"arguments": {
"id": "FILE_ID",
"ttlSeconds": 1
}
}Errors: 401 no or expired credential · 403 the credential does not hold files:read · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, is_folder, no_object, ttl_too_long · 429 slow down
files.sweep
Clean up files — Closes expired or full upload links and queues long-deleted files’ stored objects for removal.
Needs files:manage · changes data
https://app.getera.io/api/v1/file_upload_links/actions/sweep- Canonical address:
POST /api/v1/actions/files.sweep
Input
This action takes no input.
Events it publishes: files.link_expired, files.swept
What it writes: file_upload_link (update) — Closes links that expired or filled up
curl -X POST https://app.getera.io/api/v1/file_upload_links/actions/sweep \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'{
"ok": true,
"result": "…",
"events": [
"files.link_expired",
"files.swept"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.fileUploadLinks.sweep()era file_upload_links sweep{
"name": "era__files__sweep",
"arguments": {}
}Errors: 401 no or expired credential · 403 the credential does not hold files:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 429 slow down
files.unpublish
Make a file private — Stops serving a file publicly; it can then only leave through a signed link.
Needs files:manage · reaches outside (sends, publishes or charges)
https://app.getera.io/api/v1/files/{id}/actions/unpublish- Canonical address:
POST /api/v1/actions/files.unpublish
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · File · id of file |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The file must still exist in this workspace. |
is_folder | A folder cannot be published. |
already_private | The file must be public. |
Events it publishes: files.unpublished
What it writes: file (update) — Clears is_public
curl -X POST https://app.getera.io/api/v1/files/FILE_ID/actions/unpublish \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'{
"ok": true,
"result": "…",
"events": [
"files.unpublished"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.files.unpublish("FILE_ID")era files unpublish FILE_ID{
"name": "era__files__unpublish",
"arguments": {
"id": "FILE_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold files:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, is_folder, already_private · 429 slow down
files.update
Edit file details — Changes the display name, alt text, description or category of a file.
Needs files:write · changes data
https://app.getera.io/api/v1/files/{id}/actions/update- REST alias:
PATCH /api/v1/files/{id} - Canonical address:
POST /api/v1/actions/files.update
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · File · id of file |
originalName | string | Name · max 500 | |
altText | string | Alt text · max 2000 | |
description | string | Description · max 4000 | |
category | string | Category · max 255 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_found | The file must still exist in this workspace. |
Events it publishes: files.updated
What it writes: file (update) — Updates the editable metadata
curl -X POST https://app.getera.io/api/v1/files/FILE_ID/actions/update \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"originalName":"Name"}'{
"ok": true,
"result": "…",
"events": [
"files.updated"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.files.update("FILE_ID", {
originalName: "Name"
})era files update FILE_ID \
--original-name "Name"{
"name": "era__files__update",
"arguments": {
"id": "FILE_ID",
"originalName": "Name"
}
}Errors: 401 no or expired credential · 403 the credential does not hold files:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found · 429 slow down
files.upload
Record an uploaded file — Registers a stored object against a folder, enforcing the size, type and link limits.
Needs files:write · changes data
https://app.getera.io/api/v1/files/actions/upload- Canonical address:
POST /api/v1/actions/files.upload
Input
| Field | Type | Required | Notes |
|---|---|---|---|
originalName | string | yes | File name · max 500 |
storedName | string | yes | Stored name · max 500 |
storageKey | string | yes | Object key · max 1024 |
fileUrl | string | Reference URL · max 2048 | |
mimeType | string | Content type · max 255 | |
fileType | string (enum) | Kind · Allowed values: image, document, video, audio, other | |
fileSize | number | yes | Size in bytes · min 0 |
folderPath | string | Folder · max 500 | |
category | string | Category · max 255 | |
agentId | string | Produced by agent · max 64 | |
sourceConnector | string | Source connector · max 64 | |
artifactKind | string | Artifact kind · max 24 | |
uploadToken | string | Upload link token · max 64 | |
context | string (enum) | Stored by · Allowed values: file, workspace, knowledge, execution, chat, copilot | |
executionId | string | Workflow run · max 128 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
file_too_large | The file must be within the workspace or link size limit. |
type_not_allowed | The link must accept this kind of file. |
link_unknown | The upload link must exist. |
link_revoked | The upload link must not have been closed. |
link_expired | The upload link must not have expired. |
link_full | The upload link must not have reached its file limit. |
storage_quota_exceeded | The organization must have room left on its plan. |
Events it publishes: files.uploaded
What it writes: file (insert) — Records the file and any folder it needed; file_upload_link (update) — Counts the upload against the link
curl -X POST https://app.getera.io/api/v1/files/actions/upload \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"originalName":"File name","storedName":"Stored name","storageKey":"Object key","fileSize":1}'{
"ok": true,
"result": "…",
"events": [
"files.uploaded"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.files.upload({
originalName: "File name",
storedName: "Stored name",
storageKey: "Object key",
fileSize: 1
})era files upload \
--original-name "File name" \
--stored-name "Stored name" \
--storage-key "Object key" \
--file-size 1{
"name": "era__files__upload",
"arguments": {
"originalName": "File name",
"storedName": "Stored name",
"storageKey": "Object key",
"fileSize": 1
}
}Errors: 401 no or expired credential · 403 the credential does not hold files:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 file_too_large, type_not_allowed, link_unknown, link_revoked, link_expired, link_full, storage_quota_exceeded · 429 slow down
Data models
FileLink
Table file_link · Resource file_links
GET /api/v1/file_linksGET /api/v1/file_links/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
file_id | string (uuid) · uuid | file | ||
record_type | string · text | |||
record_id | string (uuid) · uuid | |||
linked_by_user_id | string (uuid) · uuid | yes | app_user | |
created_at | string (date-time) · timestamptz |
Primary key: id
Unique: file_id, record_type, record_id
FileUploadLink
Table file_upload_link · Resource file_upload_links
GET /api/v1/file_upload_linksGET /api/v1/file_upload_links/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
label | string · text | yes | ||
description | string · text | yes | ||
target_folder | string · text | |||
max_files | integer · int4 | |||
max_file_size_mb | integer · int4 | |||
allowed_file_types | string[] · text[] | |||
expires_at | string (date-time) · timestamptz | yes | ||
is_active | boolean · bool | |||
upload_count | integer · int4 | |||
owner_user_id | string (uuid) · uuid | yes | app_user | |
created_at | string (date-time) · timestamptz | |||
updated_at | string (date-time) · timestamptz |
Primary key: id
Unique: token
Checks:
CHECK ((max_file_size_mb > 0))CHECK ((max_files > 0))CHECK ((upload_count >= 0))
File
Table file · Resource files
GET /api/v1/filesGET /api/v1/files/{id}PATCH /api/v1/files/{id}→files.update
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
name | string · text | |||
original_name | string · text | |||
storage_key | string · text | yes | ||
file_url | string · text | yes | ||
file_type | string · text | image, document, video, audio, other | ||
mime_type | string · text | yes | ||
file_size | string (int64) · int8 | yes | ||
folder_path | string · text | |||
parent_id | string (uuid) · uuid | yes | file | |
category | string · text | yes | ||
alt_text | string · text | yes | ||
description | string · text | yes | ||
is_folder | boolean · bool | |||
is_public | boolean · bool | |||
sort_order | integer · int4 | |||
download_count | integer · int4 | |||
owner_user_id | string (uuid) · uuid | yes | app_user | |
agent_id | string · text | yes | ||
source_connector | string · text | yes | ||
artifact_kind | string · text | yes | ||
version | integer · int4 | |||
upload_link_id | string (uuid) · uuid | yes | file_upload_link | |
deleted_at | string (date-time) · timestamptz | yes | ||
created_at | string (date-time) · timestamptz | |||
updated_at | string (date-time) · timestamptz | |||
context | string · text | file, workspace, knowledge, execution, chat, copilot | ||
execution_id | string · text | yes |
Primary key: id
Checks:
CHECK (((file_size IS NULL) OR (file_size >= 0)))
Events
Workflows and webhooks react to these events.
| Event | What happens | Payload |
|---|---|---|
files.attached | A file was attached to a record. | id, recordType, recordId |
files.deleted | A file was deleted. | id, name |
files.detached | A file was taken off a record. | id, recordType, recordId |
files.folder_created | A folder was created. | id, name, folderPath |
files.folder_deleted | A folder and its contents were deleted. | id, name, count |
files.link_created | An upload link was opened. | id, label |
files.link_expired | An upload link was closed because it expired or filled up. | id |
files.link_renewed | An upload link was given more time. | id, expiresAt |
files.link_revoked | An upload link was closed. | id |
files.published | A file became a public asset. | id, name |
files.replaced | A file’s contents were overwritten. | id, name, size |
files.swept | The daily file clean-up ran. | linksClosed, objectsQueued, days |
files.unpublished | A public file was made private. | id, name |
files.updated | A file’s details were edited. | id, name |
files.uploaded | A file was stored. | id, name, folderPath, size |
files.url_signed | A download link was handed out for a private file. | id, name, ttlSeconds |