Files
Short-lived input staging handles for chat attachments. Files are consumer-facing pre-run objects; managed artifacts are run-scoped agent/tool transfer handles.
Do not paste bulk content into the prompt. To give an agent a large or binary input (a CSV, a log, a config blob, an image, a base64 payload), upload it as a file and attach it, rather than embedding it in the message text. Content in the prompt is retranscribed by the model on every tool call it feeds; large opaque blobs drift when copied verbatim. Uploading keeps the bytes on a durable handle the agent reads directly, so they never pass through the model's output.
Methods
| Method | Route |
|---|---|
| novo.files.create({...}) | POST /v1/files |
| novo.files.upload({...}) | POST /v1/files + signed PUT + complete |
| novo.files.complete(fileId) | POST /v1/files/:id/complete |
| novo.files.get(fileId) | GET /v1/files/:id |
| novo.files.delete(fileId) | DELETE /v1/files/:id |
URL Input
Use this when your app already has a short-lived signed URL, such as a Drive or object-store download URL:
const file = await novo.files.create({
source: { type: 'url', url: signedUrl },
filename: 'contract.pdf',
});
await novo.runs.start({
threadId,
input: {
text: 'Review this contract.',
attachments: [{ fileId: file.id }],
},
});
Novo fetches, validates, and stores the bytes immediately. The signed URL is not shown to the agent.
Upload Input
Use this when the user selects a local file and your server wants Novo to receive the bytes directly:
const file = await novo.files.create({
source: { type: 'upload' },
filename: 'screenshot.png',
mimeType: 'image/png',
sizeBytes: bytes.size,
});
await fetch(file.upload.url, {
method: 'PUT',
headers: file.upload.headers,
body: bytes,
});
const ready = await novo.files.complete(file.id);
await novo.threads.stream(threadId, {
input: {
text: 'What is happening here?',
attachments: [{ fileId: ready.id }],
},
});
Upload files must be completed before they can be attached to a run.
Upload in One Call
novo.files.upload(...) does the create → signed PUT → complete sequence for
you and resolves to the ready file:
const ready = await novo.files.upload({
data: bytes, // Uint8Array | ArrayBuffer | string
filename: 'transactions.csv',
mimeType: 'text/csv',
});
await novo.runs.start({
threadId,
input: {
text: 'Summarize these transactions.',
attachments: [{ fileId: ready.id }],
},
});
The bytes are PUT directly to storage with the credentials from the create
step; your Novo API key is never sent to the storage host.
Seed a File Into the Workspace
For an agent with a managed workspace, add workspacePath to an attachment to
have Novo write the file into the workspace at that path before the run's first
step. The agent then reads the file directly, with no workspace_import_artifact
call, and the content never touches the prompt or the model's output:
const ready = await novo.files.upload({
data: reportBytes,
filename: 'report.md',
});
await novo.runs.start({
threadId,
input: {
text: 'Revise the report at inputs/report.md.',
attachments: [{ fileId: ready.id, workspacePath: 'inputs/report.md' }],
},
});
workspacePath is workspace-relative (no leading /, no ..), overwrites any
existing file at the path, and requires a managed workspace with filesystem or
shell access. The first seeded file provisions the workspace, so the run pays a
one-time cold start it would otherwise defer.
Agent Access
At run start, each fileId becomes a run-scoped managed artifact. The agent sees
a compact manifest with an artifactId and a tool hint:
- seeded (
workspacePathset): read the file at that workspace path directly - media:
view_media({ artifactId }) - documents/PDFs:
document_read({ artifact_id }) - generic files:
workspace_import_artifact({ artifactId, path })
File attachments require managed artifact storage. Media files use the baseline
view_media tool. Document/generic files also require a managed workspace with
file access; documents require the documents capability and the managed document
processor.