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

# surface_file

> Save generated content and return a time-limited download URL.

# surface\_file

Saves a generated or computed file and returns a time-limited download card in the chat. Use when the agent (or your code) has produced a file at runtime — reports, exports, CSVs, processed images, generated PDFs.

Exactly one of `content`, `content_base64`, or `source_path` must be provided.

## Parameters

| Name             | Type   | Required     | Description                                                                                 |
| ---------------- | ------ | ------------ | ------------------------------------------------------------------------------------------- |
| `filename`       | string | yes          | Display name including extension (e.g. `report.pdf`, `data.csv`).                           |
| `content`        | string | one of three | UTF-8 string content. Use for text files (CSV, JSON, Markdown, plain text).                 |
| `content_base64` | string | one of three | Base64-encoded binary content, no `data:` prefix. Use for binary files (PDF, images, XLSX). |
| `source_path`    | string | one of three | Absolute server path to an existing file to copy. Used by skills that write to disk.        |
| `content_type`   | string | no           | MIME type override. Inferred from extension if omitted.                                     |
| `description`    | string | no           | Short description shown on the download card.                                               |
| `expires_hours`  | number | no           | Hours until the link expires. Default 24.                                                   |

## Returns

```json theme={null}
{
  "action": "download",
  "filename": "report.csv",
  "description": "Monthly sales report",
  "size": 4821,
  "downloadUrl": "/api/download/dl_8fk2mx..."
}
```

On failure: `{ "error": "...", "details": { "hint": "..." } }`.

***

## Examples

### Text content — CSV report

```js theme={null}
export async function handle(input, ctx) {
  const rows = input.data.map((row) =>
    Object.values(row)
      .map((v) => `"${String(v).replace(/"/g, '""')}"`)
      .join(',')
  );
  const csv = [Object.keys(input.data[0]).join(','), ...rows].join('\n');

  return await ctx.tools.surface_file({
    filename:      `${input.report_name}.csv`,
    content:       csv,
    description:   `${input.report_name} — ${input.data.length} rows`,
    expires_hours: 48,
  });
}
```

### Text content — Markdown document

```js theme={null}
export async function handle(input, ctx) {
  const md = `# ${input.title}\n\n${input.body}`;
  return await ctx.tools.surface_file({
    filename:     'summary.md',
    content:      md,
    content_type: 'text/markdown',
    description:  input.title,
  });
}
```

### Binary content — PDF (JavaScript)

```js theme={null}
export async function handle(input, ctx) {
  const pdfBase64 = generatePdfBase64(input);  // your PDF library
  return await ctx.tools.surface_file({
    filename:       'invoice.pdf',
    content_base64: pdfBase64,
    content_type:   'application/pdf',
    description:    `Invoice #${input.invoice_number}`,
    expires_hours:  72,
  });
}
```

### Binary content — PDF (Python)

```python theme={null}
async def handle(input, ctx):
    import base64
    pdf_bytes = generate_pdf(input)           # your PDF library
    b64       = base64.b64encode(pdf_bytes).decode()
    return await ctx.tools.surface_file({
        'filename':       'invoice.pdf',
        'content_base64': b64,
        'content_type':   'application/pdf',
        'description':    f"Invoice #{input['invoice_number']}",
        'expires_hours':  72,
    })
```

### Short-lived link for a sensitive file

```js theme={null}
return await ctx.tools.surface_file({
  filename:       'salary-bands.xlsx',
  content_base64: xlsxBase64,
  content_type:   'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  description:    'Salary band analysis — confidential',
  expires_hours:  1,
});
```

### Pair with render\_ui to show a preview card alongside the download

```js theme={null}
export async function handle(input, ctx) {
  const csv  = buildCsv(input.data);
  const file = await ctx.tools.surface_file({
    filename:    'results.csv',
    content:     csv,
    description: `${input.data.length} rows`,
  });

  // Show a summary card with the download URL embedded
  return await ctx.tools.render_ui({
    interactive: false,
    spec: {
      root: 'card',
      elements: {
        card: { type: 'card', props: { title: 'Export ready', subtitle: null }, children: ['kv'] },
        kv: {
          type: 'key_value',
          props: {
            items: [
              { key: 'Rows',      value: String(input.data.length) },
              { key: 'Format',    value: 'CSV' },
              { key: 'Expires',   value: '24 hours' },
              { key: 'Download',  value: file.downloadUrl },
            ],
          },
          children: [],
        },
      },
      state: {},
    },
  });
}
```

***

## Notes

* Always registered.
* Generated files are stored securely; the download link expires after `expires_hours` (default 24).
* For files that already exist in `data_public` (not generated at runtime), use [present\_public\_file](/builtin-tools/present_public_file) instead.
* For binary output, always base64-encode and pass via `content_base64` — do not embed raw bytes in `content`.
