# Files — artifacts in a conversation

Artifacts include both files the user attached and files your agent delivered. Browse their metadata with ONBF Tools, then use short-lived HTTPS links for the bytes.

## What artifacts are

An **artifact** is a file stored in the current conversation. `direction: "from_user"` means the user attached it; `direction: "from_agent"` means your agent delivered it. Artifacts persist across runs in that conversation.

- **Reading a file the user sent** — the webhook already carries `input.files[]`, and your agent can also find it later with `list_artifacts` + `get_artifact`.
- **Delivering a file that's already online** — your agent calls `upload_artifact_from_url` with a public HTTPS URL and ONBF fetches it.
- **Delivering a file it produced itself** — your agent calls `get_artifact_upload_info` and sends the bytes to the link it gets back.

## List and download files

| `list_artifacts` argument | Meaning |
| --- | --- |
| `direction` | Optional `from_user` or `from_agent`; omit for both. |
| `kind` | Optional `image`, `document`, `data`, `archive` or `other`. |
| `query` | Optional case-insensitive filename substring. |
| `limit` | Defaults to 50; maximum 100; newest first. |

The list returns `artifactId`, `filename`, `kind`, `mimeType`, `sizeBytes`, `direction` and `createdAt`, without download URLs. Passing one `artifactId` to `get_artifact` returns its metadata plus a fresh signed `downloadUrl`. That URL is short-lived and purpose-bound: it's fetched over plain HTTPS, and a new one is requested once it expires.

## Deliver a file from a URL

When the file is already available at a public `https://` URL, `upload_artifact_from_url` is the whole delivery: ONBF fetches it server-side, blocks private and internal destinations, validates its real type and size, and returns an `artifactId` once it's in the conversation.

## Deliver a file your agent created

For a file that only exists inside your agent's runtime — a PDF it just generated, a chart it rendered — ONBF hands out an upload link instead of accepting the bytes through a tool call. Your agent calls `get_artifact_upload_info`, then its runtime sends the bytes straight to the returned `uploadUrl`.

> **Why the bytes travel separately:** Tool calls carry structured JSON, not megabytes of binary. Splitting it keeps the file transfer on plain HTTPS, so a large delivery can't stall or bloat a tool call. The upload link is single-purpose and expiring, and once it accepts a file that file is already visible in the conversation — there's no finalize step.

### For developers: upload raw bytes by hand

Only needed if you're implementing the upload yourself rather than letting your agent's platform do it. POST `multipart/form-data` with a single field named `file`. Don't send an Authorization header — the credential is already inside the URL — and don't set the multipart `Content-Type` boundary manually. The same link accepts multiple files until it expires or hits a quota.

_Complete raw-byte upload_

```bash
export ONBF_BASE="https://onbf.ai"
export ONBF_TOKEN="onbf_sess_FROM_WEBHOOK"

UPLOAD_URL=$(curl -s "$ONBF_BASE/api/passport/v1/tools/get_artifact_upload_info" \
  -H "Authorization: Bearer $ONBF_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}' | node -p 'JSON.parse(require("fs").readFileSync(0,"utf8")).uploadUrl')

curl "$UPLOAD_URL" -F "file=@./report.pdf"
```

## Limits & access

- Maximum **25 MB per file** and **50 files per conversation**.
- Supported types include common images, PDF, text/CSV/Markdown, JSON, ZIP and Office documents.
- All artifact tools are **run-session-only**. Deliver tools require `conversation:write`; read tools require `conversation:read`.
- The run credential selects the user and conversation, so your agent can't reach another conversation's files — a foreign `artifactId` returns `found: false`.

## Who calls the file tools

### MCP

**Nothing for you to build.** Once your platform is pointed at ONBF's MCP server, your agent already has the file tools in its tool list and does the calling itself, mid-run, whenever it decides to. There is no per-tool setup and no code on your side — connecting happens once on the **[MCP transport](/docs/mcp)** page.

The one thing MCP never carries is the file itself: upload and download URLs are fetched over plain HTTPS by whatever is holding the bytes.

### HTTP API

**Your backend makes this call**, at the moment your agent decides it needs the file tools. The run credential from the webhook is the bearer token.

POST the canonical tool arguments to `/api/passport/v1/tools/{tool_name}`. Returned upload and download URLs are then used directly over HTTPS — bytes never pass through the tool endpoint.
