> ## 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 Service

> Upload agent files, share exact versions, and manage file retention.

The File Service stores content independently of messages and conversations. A file has a stable `id`, an exact
content `versionId`, an owner, and a storage retention policy. A download link is separate from the file.

## Agent files

Use `POST /files/agent-artifacts` for files an agent creates.
These files require authorized access or a read-grant link.

The route determines the file's purpose. Do not send `purpose`, storage placement, visibility, or owner parameters.
There is no general-purpose upload route or public organization-asset upload API.

Messaging distributions import inbound attachments as `MessagingMedia` and set their temporary retention policy.
Clients cannot upload messaging media through this API. SDK servers use `AgentArtifact` for generated output.
See [Media and Attachments](/docs/distributions/messaging/media-and-attachments) for provider support.

The [Python SDK's automatic file provider](/sdk/python/messaging/artifacts#file-retention) requests 30-day retention
for agent artifacts without a file action. An action with an omitted or `null` deadline requests indefinite storage.
That default belongs to the SDK. Direct File Service uploads without a deadline remain indefinite.

## Ownership and permissions

| File use | Owner |
| - | - |
| Media imported by a distribution | Its receiving Principal Identity |
| Agent output during that request | The same receiving identity, including downstream SDK output |
| Direct or autonomous deployment output | The deployment's Daemon Identity |

During a contextual SDK callback, the verified `agentPrincipal` determines the receiving owner. The
`executionPrincipal` supplies the permissions for the operation. These identities can differ.
For a direct callback, Aion resolves the deployment's current daemon and organization. The deployment organization pays.
Reported caller IDs describe attribution; they do not select a file owner or grant access.

For scheduled work, a Cron attachment's optional **Principal Identity** selects the receiving owner and agent usage
attribution. It can be reused across schedules. Without it, the target behavior's daemon supplies that attribution.
Cron still executes through the target behavior's daemon; selecting a principal does not grant executor permissions.

An identity can access its files across distributions that use that same receiving identity. Sharing an organization
alone does not give an agent access to another agent's files.

| Action | Required permission within the owner's scope |
| - | - |
| Upload | `FileCreate` |
| Read metadata or protected content | `FileRead` |
| Replace agent output, create a read grant, or renew retention | `FileUpdate` |
| Delete | `FileDelete` |

Agent callbacks need the executor's explicit action permission, even when the receiving owner has that permission.
Internal distribution imports also require the receiving principal's `FileCreate` permission.
The Project Agent role includes File create, read, and update; it does not include delete.
Workspace Owner and Workspace Admin receive File delete by default.

## Upload agent output

An SDK server authenticates with its **Version access token**. For a request routed by Aion, forward the signed
`Aion-Usage-Attribution` header unchanged. For direct or autonomous calls, use the
[runtime callback rules](/sdk/python/extensibility/runtime-callbacks). Do not send `Aion-Principal-Selector`.

Omit `organizationId` to use the verified callback payer. If supplied, it must match that payer.
Keep the same `operationId` when retrying the same upload.
Use a fresh operation ID for different content.

```bash theme={null}
curl --request POST \
  "$AION_API_URL/files/agent-artifacts?operationId=$OPERATION_ID&organizationId=$ORGANIZATION_ID&byteSize=$BYTE_SIZE" \
  --header "Authorization: Bearer $VERSION_ACCESS_TOKEN" \
  --header "Aion-Usage-Attribution: $USAGE_ATTRIBUTION" \
  --form "file=@report.pdf;type=application/pdf"
```

Set `BYTE_SIZE` to the file's byte count. The multipart field must be named `file`; include a filename and MIME type.
Omit the usage header for autonomous deployment work, rather than sending an empty value.

| Query parameter | Requirement |
| - | - |
| `operationId` | Required UUID for upload retries. |
| `organizationId` | Optional payer organization UUID. Defaults to the verified callback payer. |
| `byteSize` | Required content size in bytes. |
| `retentionExpiresAt` | Optional future absolute UTC timestamp for agent output. Omit for indefinite storage. |
| `associationKind`, `associationId` | Optional supported resource reference; supply both or neither. |

Associations do not establish arbitrary ownership. An artifact does not need a task or conversation association
to upload.

A successful response has this shape:

```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.pdf",
    "mediaType": "application/pdf",
    "byteSize": 1024,
    "contentSha256": "<sha256>",
    "createdAt": "2026-10-06T12:00:00Z",
    "retentionExpiresAt": null
  },
  "replayed": false
}
```

The returned protected `url` is not a recipient share link. Save both IDs, then create an exact-version read grant.
Never give recipients your Version bearer.

## Share an exact version

Create a read grant with `FileUpdate`:

```bash theme={null}
curl --request POST \
  "$AION_API_URL/files/$FILE_ID/versions/$VERSION_ID/grants?ttlSeconds=3600" \
  --header "Authorization: Bearer $VERSION_ACCESS_TOKEN" \
  --header "Aion-Usage-Attribution: $USAGE_ATTRIBUTION"
```

The default is **one hour**. Choose a whole number of minutes from **1 to 1,440**.
The HTTP parameter `ttlSeconds` expresses those minutes in seconds, from 60 to 86,400 in multiples of 60.
The SDK uses `ttl_minutes`. File retention can shorten the effective grant lifetime.

The response includes `id`, `versionId`, `url`, `accessExpiresAt`, and `retentionExpiresAt`.
The URL contains a bearer grant: anyone who possesses it can download that exact version without logging in.
Treat it as a secret and avoid logging it.

* `accessExpiresAt` is when that link expires.
* `retentionExpiresAt` is when file content expires, or `null` for no timed expiry.

When a link expires, an authorized server can request a fresh grant for the same IDs. The expired link is not
renewal authority. A fresh grant does not extend storage retention and does not select a newer file version.
Without a valid grant, protected downloads require authentication and `FileRead` within the owner's scope.

Chat clients do not manage these grants automatically. Retrieving old conversation history does not refresh its links.

## Inspect and retain a file

`GET /files/{id}` requires `FileRead`. It returns the stable ID, current version and revision, organization,
purpose, typed `owner` (`kind` and `id`), availability, and safe content metadata.
`retentionRenewalSupported` describes the file purpose; `canRenewRetention` describes the current caller's authority.
The effective deadline is `metadata.retentionExpiresAt`.

To extend an available artifact's retention deadline, use `FileUpdate`:

```bash theme={null}
curl --request PUT "$AION_API_URL/files/$FILE_ID/retention" \
  --header "Authorization: Bearer $VERSION_ACCESS_TOKEN" \
  --header "Aion-Usage-Attribution: $USAGE_ATTRIBUTION" \
  --header "Content-Type: application/json" \
  --data '{"retentionExpiresAt":"2027-01-01T00:00:00Z"}'
```

Send a future absolute UTC timestamp, with at most six fractional-second digits. Repeating the same deadline is safe.
An earlier deadline is rejected. There is no duration cap for agent-output retention.
To keep an unexpired artifact indefinitely, send:

```json theme={null}
{"retentionExpiresAt": null}
```

The field is required: omitting it is not equivalent to `null`. Indefinite retention cannot be changed to a finite
deadline through renewal. Renewal returns current metadata, does not change content or version IDs, and does not
extend existing grants. It cannot restore expired or deleted content. Downloads, metadata reads, and new grants
do not renew retention.

Expired content becomes unavailable and is removed by background cleanup. Conversation deletion does not delete
independently retained files. Indefinite storage continues to accrue storage charges. Conversation deletion and
download-link expiry do not end File retention or its storage billing.

## Replace or delete

* `PUT /files/{id}` replaces agent-output content with `FileUpdate`. Send multipart `file` and query parameters
  `operationId`, `expectedVersionId`, `expectedRevision`, and `byteSize`. Ownership and retention stay unchanged.
* `DELETE /files/{id}?operationId={uuid}` requires `FileDelete`. Referenced files are protected against deletion;
  remove the relevant references first.

An upload or grant permission failure is not solved by changing its reported caller or removing attribution headers.
Check the executing identity's permissions, receiving owner, payer organization, and deployment daemon assignment.

## Related pages

* [Identities](/docs/concepts/identities)
* [Runtime callbacks](/sdk/python/extensibility/runtime-callbacks)
* [Messaging Media extension](/a2a/extensions/aion/distribution/messaging/1.0.0#messaging-media)


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