# Files

Upload inputs and download outputs.

Source: https://scifig.ai/docs/api/files

## Upload

`POST /files` takes `multipart/form-data` and needs the `figures:generate` scope. It returns `201` with `file_id`, `purpose`, `content_type`, `size_bytes`, `expires_at` and `created_at`.

| Field | Type | Required | Default | Rules |
| --- | --- | --- | --- | --- |
| file | file | yes | — | See the purpose limits below |
| purpose | string | no | `image` | `image`, `mask` or `flowchart_attachment` |

| Purpose | Accepted | Used by |
| --- | --- | --- |
| image | PNG, JPEG or WebP up to 20 MB (checked by content, not by name) | Edits, text detections, upscales, conversions, reference images |
| mask | PNG up to 8 MB; white marks the area to redraw | `POST /images/edits` |
| flowchart_attachment | The FlowChart workspace formats, up to 32 MB | `POST /flowcharts/generations` |

Uploads can be used for 24 hours; after that the `file_id` is unknown (`400 INVALID_REQUEST`, `FILE_NOT_FOUND`). They are only inputs: they never appear in job responses, and they are not shared with anyone.

Uploads count against the same upload limits as the website. Beyond them the answer is `429 RATE_LIMITED` with `Retry-After` and `details.reason` `UPLOAD_RATE_LIMITED` (per minute) or `UPLOAD_DAILY_LIMIT_REACHED` (per day, flowchart attachments).

An upload may send an optional `Idempotency-Key` header (1–255 visible ASCII characters). The same account, key and `purpose` always map to one `file_id`: a repeat returns the first file with `201`, without uploading again or counting against the upload limits. The same key and `purpose` with different bytes returns `409 IDEMPOTENCY_CONFLICT`. Without the header every upload creates a new file.

## Download

Each job output has a `download_url` that works without credentials for one hour; read the job again for a fresh link. `GET /files/{file_id}` with your credential downloads any of your files. Files are always served as attachments.

A signed `download_url` returns `404 NOT_FOUND` once it has expired or if it was altered, and every link of an account that is banned or being deleted stops working the same way.

| Output | Can be passed to |
| --- | --- |
| PNG or JPEG | Every image endpoint (edits, text detections, upscales, conversions, reference images) |
| WebP | Edits, text detections, upscales, SVG vectorization and reference images; not PNG, PDF, TIFF or PPTX conversions |
| Flowchart SVG | `POST /images/conversions` to PNG, PDF, TIFF or PPTX |
| PDF, TIFF, EPS, PPTX, other SVG | Nothing: download only |

A conversion source larger than 20 MB (an 8K upscale, say) is refused with `400 INVALID_REQUEST` (`FILE_TOO_LARGE`).

## cURL

```bash
curl -X POST https://scifig.ai/api/v1/files \
  -H "Authorization: Bearer $SCIFIG_API_KEY" \
  -F "file=@figure.png" \
  -F "purpose=image"
```

## JavaScript

```javascript
import { readFile } from 'node:fs/promises';

const form = new FormData();
form.set('file', new Blob([await readFile('figure.png')]), 'figure.png');
form.set('purpose', 'image');

const response = await fetch('https://scifig.ai/api/v1/files', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SCIFIG_API_KEY}`,
  },
  body: form,
});
const result = await response.json();
console.log(response.status, result);
```

## Python

```python
import os

import requests

headers = {
    "Authorization": f"Bearer {os.environ['SCIFIG_API_KEY']}",
}
response = requests.post(
    "https://scifig.ai/api/v1/files",
    headers=headers,
    files={"file": open("figure.png", "rb")},
    data={"purpose": "image"},
    timeout=60,
)
print(response.status_code, response.json())
```
