> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aion.to/llms.txt
> Use this file to discover all available pages before exploring further.

# File REST API

> Upload, discover, edit, read, and retain agent and workspace artifacts.

All paths are relative to your Aion API origin, such as `https://api.aion.to`.
This reference covers the public `AgentArtifact` and `OrganizationArtifact` purposes.

## Authentication and ownership

Send `Authorization: Bearer <credential>`. File routes accept Aion user sessions, version credentials,
user API keys, and verified AuthKit Connect user tokens. Connect tokens use the same user permissions.
Keep credentials in headers.

For callbacks, forward the original signed `Aion-Usage-Attribution` unchanged.
Direct version callbacks can use `Aion-Caller-Id` under the
[runtime callback rules](https://docs.aion.to/sdk/python/extensibility/runtime-callbacks.md).
Those headers are mutually exclusive. A reported caller does not grant authority or select the owner.
Do not send `Aion-Principal-Selector`.

User creation and list requests specify `organizationId`.
Agent callbacks can omit it to use the verified payer workspace. A supplied workspace must match that payer.
For `AgentArtifact`, users can select an authorized owner with `agentIdentityId`;
omission selects the user's agent identity. Callbacks must retain their verified receiving owner.
Do not send `agentIdentityId` for `OrganizationArtifact`.

Every request checks the [owner's File permissions](https://docs.aion.to/docs/resources/file-service.md#ownership-and-permissions).
A cursor, upload descriptor, or previous authorization result does not grant access.

## Endpoints

| Method and path | Operation |
| - | - |
| `POST /files` | Create an artifact |
| `GET /files` | List retained artifacts of one purpose |
| `GET /files/{fileId}` | Read full current metadata |
| `PUT /files/{fileId}` | Replace bytes with a version precondition |
| `PATCH /files/{fileId}` | Edit metadata with a version precondition |
| `DELETE /files/{fileId}` | Delete through the file lifecycle |
| `GET` or `HEAD /files/{fileId}/content` | Read or inspect current content |
| `GET` or `HEAD /files/{fileId}/versions/{versionId}/content` | Read or inspect an exact version |
| `POST /files/{fileId}/versions/{versionId}/grants` | Create an explicit bearer read grant |
| `PUT /files/{fileId}/retention` | Extend retention |

## Upload limits

These limits apply to both `POST /files` and content replacement with `PUT /files/{fileId}`:

| Part | Default maximum |
| - | - |
| File content | **20 MiB (20,971,520 bytes)** |
| Optional JSON `metadata` part | **32 KiB (32,768 bytes)** |
| Complete multipart request body | File limit plus **64 KiB**; **21,037,056 bytes** with the default file limit |

The extra 64 KiB accommodates metadata, multipart headers, and boundary delimiters.
A full 20 MiB file can be uploaded with metadata; the allowance does not increase the file-content limit.
Set `byteSize` to the exact number of file bytes, excluding metadata and multipart framing.
The server streams artifact content to storage and buffers only the bounded metadata part.

A server can configure a different content limit. An MCP upload descriptor reports that server's limit as
`maximumBytes`. HTTP 413 means the file or request exceeds a size limit; reduce the payload before retrying.
See [MCP content reads](https://docs.aion.to/docs/resources/file-service/mcp.md#read-content) for the separate 128 KiB inline-read limit.

## Create an artifact

`POST /files` requires `FileCreate`. Send `multipart/form-data` with these query parameters:

| Parameter | Meaning |
| - | - |
| `purpose` | Required: `AgentArtifact` or `OrganizationArtifact` |
| `operationId` | Required UUID; reuse only for retries of the same operation |
| `byteSize` | Required exact content length in bytes |
| `organizationId` | Workspace UUID; required for users, optional for verified callbacks |
| `agentIdentityId` | Optional authorized agent owner for `AgentArtifact` |
| `retentionExpiresAt` | Optional future UTC timestamp; omission means retention until deletion |

Include exactly one `file` part with a safe leaf filename and MIME type.
If you include a JSON `metadata` part, put it **before** the file part.
It can contain only `description` and `tags`. The file part supplies the filename.
The server rejects unknown or duplicate parts.
The [upload limits](#upload-limits) apply to the file, metadata, and complete multipart request.

The example below uses a user token and creates a nine-byte CSV. Set `ACCESS_TOKEN` and `WORKSPACE_ID`
to your values. The user needs `FileCreate` in that workspace and `AgentIdentityRead` for their agent identity.
Run the setup once; reuse `OPERATION_ID` and the same file if you retry the request.

```bash theme={null}
export AION_API_URL="https://api.aion.to"
export ACCESS_TOKEN="<user-access-token>"
export WORKSPACE_ID="<workspace-uuid>"
OPERATION_ID="$(python3 -c 'import uuid; print(uuid.uuid4())')"
printf 'total,42\n' > report.csv
BYTE_SIZE="$(wc -c < report.csv | tr -d '[:space:]')"
```

Send the file with `curl`:

```bash theme={null}
curl --request POST \
  "$AION_API_URL/files?purpose=AgentArtifact&operationId=$OPERATION_ID&organizationId=$WORKSPACE_ID&byteSize=$BYTE_SIZE" \
  --header "Authorization: Bearer $ACCESS_TOKEN" \
  --form 'metadata={"description":"Weekly totals","tags":["reports"]};type=application/json' \
  --form 'file=@report.csv;type=text/csv'
```

For a callback, also send the original attribution header. For a workspace artifact, change `purpose` to
`OrganizationArtifact`. The response is HTTP 200 with the completed receipt:

```json theme={null}
{
  "id": "00000000-0000-0000-0000-000000000001",
  "versionId": "00000000-0000-0000-0000-000000000002",
  "revision": 1,
  "url": "https://api.aion.to/files/00000000-0000-0000-0000-000000000001/content",
  "purpose": "AgentArtifact",
  "metadata": {
    "fileName": "report.csv",
    "mediaType": "text/csv",
    "byteSize": 9,
    "contentSha256": "<sha256>",
    "createdAt": "2026-10-25T12:00:00Z",
    "retentionExpiresAt": null,
    "description": "Weekly totals",
    "tags": ["reports"]
  },
  "replayed": false
}
```

The receipt's `url` reads the current version and requires authorization. It is not a bearer link.
Save `versionId` to read that exact version later. A successful retry can return `replayed: true`.
A reused operation ID with different intent is a conflict.

### Metadata limits

| Field | Limit and behavior |
| - | - |
| `fileName` | Required for creation; 1–512 characters; no paths, control characters, or whitespace-only value |
| `description` | Optional; at most 2,000 characters; no NUL character |
| `tags` | At most 20 supplied tags; each normalized tag has 1–64 characters and no control characters |

Tags are trimmed, converted to lowercase, and deduplicated. The input count is checked before deduplication.
Metadata does not change owner, purpose, workspace, storage placement, or retention.

## List and search

`GET /files` requires `FileRead` and a single `purpose`.
Users specify a workspace and can select an authorized agent owner. Callback ownership stays fixed.
An organization-artifact list contains files owned by that workspace.

| Parameter | Default and behavior |
| - | - |
| `purpose` | Required: `AgentArtifact` or `OrganizationArtifact` |
| `organizationId` | Required for users; verified payer for callbacks |
| `agentIdentityId` | Optional authorized owner for agent artifacts |
| `fileNameRegex` | Optional filename pattern; 1–256 characters |
| `caseSensitive` | `false`; set to `true` for case-sensitive matching |
| `pageSize` | `25`; supplied integers are clamped to 1–50 |
| `pageToken` | Opaque continuation from the previous response |

```http theme={null}
GET /files?purpose=AgentArtifact&organizationId={workspaceId}&pageSize=25&fileNameRegex=report
```

The result contains `items` and `nextPageToken`. A null token means there is no next page.
Each item contains `id`, `organizationId`, `versionId`, `revision`, `purpose`, `owner`, `fileName`,
`mediaType`, `byteSize`, `createdAt`, and `retentionExpiresAt`.
Use `GET /files/{fileId}` for description and tags.

Results include current retained content, ordered by logical file creation time and ID, oldest first.
Edits do not reorder the file. Keep the workspace, owner, purpose, pattern, and case option unchanged when
using a page token. You can change page size. Authorization is checked again for each page.

### Filename patterns

Use this common regular-expression syntax. Matching is case-insensitive unless `caseSensitive=true`.
Patterns are search expressions, not shell wildcards. URL-encode them in query parameters.

| Syntax | Meaning | |
| - | - | - |
| `report` | Match the literal text anywhere in the filename | |
| `^report` and `csv$` | Match the start or end | |
| `.` and `.*` | One character, or zero or more characters | |
| `[0-9]` and `[^0-9]` | Character class or negated class | |
| \`(report | summary)\` | Group and alternatives |
| `?`, `+`, `*`, `{2,4}` | Optional, repeated, or bounded repeated input | |
| `\.` | Literal dot | |

For example, `^report-[0-9]+\.csv$` matches numbered CSV reports.
Do not rely on implementation-specific regex extensions.
Patterns are bound values, not executable query text. The service applies a bounded search timeout.
Invalid syntax returns HTTP 400 with `invalid_file_name_pattern`.
A slow search returns HTTP 503 with `file_search_timed_out`; use a narrower pattern.

## Inspect and edit metadata

`GET /files/{fileId}` returns current IDs, revision, purpose, owner, availability, metadata,
`retentionRenewalSupported`, and `canRenewRetention`.

`PATCH /files/{fileId}` requires `FileUpdate` and query parameters `operationId`, `expectedVersionId`,
and `expectedRevision`. Use `Content-Type: application/merge-patch+json`:

```json theme={null}
{
  "metadata": {
    "fileName": "revised-report.csv",
    "description": null,
    "tags": ["reviewed"]
  }
}
```

Omitted fields stay unchanged. Null clears the description or tags; an empty tag array also clears tags.
Null cannot clear the filename. Unknown fields are rejected. The body limit is 32 KiB.

A successful edit returns a new version receipt and shares the existing bytes, retention, and storage billing.
A stale version or revision returns HTTP 409. Re-read metadata before applying a new edit.
Retry the same operation with its original operation ID and preconditions after an uncertain response.

## Replace content

`PUT /files/{fileId}` requires `FileUpdate` and these query parameters:
`operationId`, `expectedVersionId`, `expectedRevision`, and `byteSize`.
Send optional JSON metadata first, then one file part, as for creation.
The same [20 MiB default content limit](#upload-limits) applies.

Omitted description and tags stay unchanged. The multipart filename becomes the new saved filename.
To rename an artifact without new content, use `metadata.fileName` in the metadata-only `PATCH` operation.
Ownership, purpose, workspace, and retention remain unchanged.
The response is a version receipt, with the same retry and conflict rules as a metadata edit.

## Read content or headers

Use the current-content path or the exact-version path from the endpoint table.
Both require `FileRead` and owner authorization. An exact-version path also accepts an explicit `grant`.
No read operation creates a grant or extends retention. Range requests return HTTP 416.

`HEAD` returns no body and does not read or charge content bytes.
It supplies `Content-Length`, `Content-Type`, `Content-Disposition`, and `Accept-Ranges: none`.
It does not return descriptions or tags. Use the JSON metadata endpoint for those fields.

## Create a read grant

`POST /files/{fileId}/versions/{versionId}/grants` requires `FileUpdate`.
Optional `ttlSeconds` defaults to the configured one hour. It must be a multiple of 60 from 60 to 86,400.

The response contains `id`, `versionId`, `url`, `accessExpiresAt`, and `retentionExpiresAt`.
The URL contains a bearer grant for that exact version. Anyone with it can read until the grant or content expires.
A grant does not renew retention, and a new version does not change an existing grant's version.

## Extend retention

`PUT /files/{fileId}/retention` requires `FileUpdate` and a JSON body:

```json theme={null}
{"retentionExpiresAt": "2027-01-01T00:00:00Z"}
```

The field is required. Explicit null requests indefinite retention.
Use an absolute UTC timestamp with at most six fractional-second digits.
Repeating the same deadline is safe. Shortening retention or renewing unavailable content is rejected.
Indefinite retention cannot be changed to a finite deadline through renewal.
The response contains current metadata. Existing grants retain their original expiration.

## Delete

`DELETE /files/{fileId}?operationId={uuid}` requires `FileDelete` and returns HTTP 204.
It follows the existing lifecycle, rejects protected references, and revokes grants.
Use the same operation ID for retries of the same deletion.

## Errors

File domain failures use a safe JSON envelope:

```json theme={null}
{"error": {"code": "file_conflict", "message": "Conflict"}}
```

Common codes include `invalid_file_request`, `file_payload_too_large`, `file_not_found`,
`file_conflict`, `file_expired`, `authentication_required`, and `file_service_unavailable`.
HTTP 401 indicates authentication is required; 403 indicates permission or owner-scope denial.
Some HTTP media-type or range rejections have a plain status response.
Do not remove attribution or change the reported caller to work around a denial.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.