---
title: "Upload a file"
description: "Add a file as a resource by sending its bytes as the request body. Lookio reads PDF, Word, PowerPoint, Excel, CSV, images and more."
canonical: "https://lookio.app/docs/api/resources/upload-file"
updated: "2026-11-03"
---

# Upload a file

Requires `resources:write`.

Send the file's bytes as the request body, with a `Content-Length` header, and its name in the `file_name` query parameter. The body is the file itself, not multipart. The extension of the name decides how the file is read.

```bash
curl "https://api.lookio.app/v1/resources/file?file_name=handbook.pdf&tags=hr" \
  -H "Authorization: Bearer $LOOKIO_API_KEY" \
  -H "Content-Type: application/pdf" \
  --data-binary @handbook.pdf
```

Use `--data-binary`, not `-d`: `-d` alters the bytes. The body is streamed, never held in memory. `tags` is a comma-separated list.

Each format has its own size limit, from 5 MB for text to 50 MB for a PDF, and [`GET /v1/account`](/docs/api/account/get-account) lists the formats and their limits. An unsupported or legacy format is refused with `invalid_parameter` and advice on what to convert it to, and a file over its limit is refused with `request_too_large`. See [File formats](/docs/platform/file-formats). An upload that ends before all its bytes arrive is refused and creates nothing.

The resource comes back `queued`. Importing is free. Poll [Get a resource](/docs/api/resources/get-resource) until it is `ready` or `failed`.

A retried upload with the same `Idempotency-Key` is recognised by its name, size and query, not by its bytes.

```http
POST https://api.lookio.app/v1/resources/file
```

## Authorizations

- `Authorization` (bearer, in header, required)

## Query parameters

- `file_name` (`string`, required, length: `1–255 chars`): The file's name with its extension, which decides how it is read (PDF, Word, Excel, CSV, images, Markdown, HTML…).
- `title` (`string`, length: `1–200 chars`): Defaults to the file name without its extension.
- `tags` (`string`, length: `≤ 2000 chars`): Comma-separated tags.
- `source` (`string`, length: `≤ 500 chars`): Where this resource comes from, shown with every citation of it: a URL or a mention such as "Employee handbook, 2026 edition". Up to 500 characters; an empty string clears it.
- `wait` (`boolean | enum<string>`): Wait up to 15 seconds for reading and indexing to finish before returning. Short texts, files and pages usually come back `ready` (or `failed`) in one call; larger imports return their current status (`queued` or `processing`) when the window ends, never an error.

## Header parameters

- `Idempotency-Key` (`string`, length: `1–255 chars`): A unique key for this request, such as a UUID, 1 to 255 visible ASCII characters. Send the same key when you retry: the first response is returned (with `Idempotent-Replayed: true`) instead of running the request again, for 24 hours, per credential. The same key on a different request is a 409 `conflict`. A streamed answer is not recorded.

## Body

`application/octet-stream`, required

The file's bytes, unencoded. `Content-Length` is required.

The raw bytes of the file.

## Response

**201**: Add a file

- `data` (`object`, required)
- `data.id` (`string`, required, pattern: `^res_[abcdefghjkmnpqrstuvwxyz23456789]{12}$`)
- `data.title` (`string`, required)
- `data.type` (`enum<string>`, required, one of: `text, file, url, site`)
- `data.status` (`enum<string>`, required, one of: `queued, processing, ready, failed, stale, ready_simulated`): `queued` and `processing`: being read and indexed (pass `wait=true` or poll until it changes). `ready`: searchable. `failed`: see `status_detail`, then fix and call reindex.
- `data.status_detail` (`string,null`, required)
- `data.tags` (`string[]`, required)
- `data.source_url` (`string,null`, required): The page or site address, for `url` and `site` resources.
- `data.source` (`string,null`, required): Where it comes from, as shown with its citations.
- `data.site` (`object | null`, required): A site's page rules and schedule; null for other types.
- `data.site.rules` (`object`, required, default: `{"include":[],"exclude":[],"excluded_urls":[]}`)
- `data.site.rules.include` (`string[]`, required, default: `[]`, items: `≤ 50 items`): Path patterns to keep, such as `/docs/**`. `*` is one level, `**` everything below. Empty keeps the whole site.
- `data.site.rules.exclude` (`string[]`, required, default: `[]`, items: `≤ 50 items`): Path patterns to leave out, such as `/blog/**`.
- `data.site.rules.regex` (`string`, length: `≤ 500 chars`): Advanced: a regular expression on the path; matching pages are left out.
- `data.site.rules.excluded_urls` (`string[]`, required, default: `[]`, items: `≤ 5000 items`): Single pages to leave out, by full address.
- `data.site.schedule` (`string`, required, length: `≤ 40 chars`, pattern: `^(?:manual|daily|weekly|monthly|every_\d+_(?:day|days|week|weeks|month|months))$`): `manual` syncs only on request. Recurring schedules accept `daily`, `weekly`, `monthly`, or `every_N_days`, `every_N_weeks` and `every_N_months` (1-90 days, 1-52 weeks, 1-12 months). Recurring schedules require a paid plan.
- `data.site.last_synced_at` (`string,null`, required)
- `data.created_at` (`string`, required)
- `data.updated_at` (`string`, required)
- `request_id` (`string`, required)

```json
{
  "data": {
    "id": "res_thk6smb5nvts",
    "title": "Handbook",
    "type": "file",
    "status": "queued",
    "status_detail": null,
    "tags": [
      "hr"
    ],
    "source_url": null,
    "source": "Employee handbook, 2026 edition",
    "site": null,
    "created_at": "2026-10-12T09:30:00.000Z",
    "updated_at": "2026-10-12T09:30:00.000Z"
  },
  "request_id": "req_01K9X7M2Q4"
}
```

**400**: Possible codes: invalid_request, unknown_field, invalid_parameter, invalid_cursor

- `error` (`object`, required)
- `error.type` (`string`, required)
- `error.code` (`string`, required)
- `error.message` (`string`, required)
- `error.param` (`string`)
- `error.docs_url` (`string`, required)
- `error.action_url` (`string<uri>`)
- `request_id` (`string`, required)

```json
{
  "error": {
    "type": "<string>",
    "code": "<string>",
    "message": "<string>",
    "param": "<string>",
    "docs_url": "<string>",
    "action_url": "<string>"
  },
  "request_id": "<string>"
}
```

**401**: Possible codes: authentication_required, invalid_api_key, api_key_expired, api_key_revoked, invalid_access_key

- `error` (`object`, required)
- `error.type` (`string`, required)
- `error.code` (`string`, required)
- `error.message` (`string`, required)
- `error.param` (`string`)
- `error.docs_url` (`string`, required)
- `error.action_url` (`string<uri>`)
- `request_id` (`string`, required)

```json
{
  "error": {
    "type": "<string>",
    "code": "<string>",
    "message": "<string>",
    "param": "<string>",
    "docs_url": "<string>",
    "action_url": "<string>"
  },
  "request_id": "<string>"
}
```

**403**: Possible codes: insufficient_scope, feature_not_available, admin_required, permission_denied

- `error` (`object`, required)
- `error.type` (`string`, required)
- `error.code` (`string`, required)
- `error.message` (`string`, required)
- `error.param` (`string`)
- `error.docs_url` (`string`, required)
- `error.action_url` (`string<uri>`)
- `request_id` (`string`, required)

```json
{
  "error": {
    "type": "<string>",
    "code": "<string>",
    "message": "<string>",
    "param": "<string>",
    "docs_url": "<string>",
    "action_url": "<string>"
  },
  "request_id": "<string>"
}
```

**409**: Possible codes: conflict, assistant_has_no_resources

- `error` (`object`, required)
- `error.type` (`string`, required)
- `error.code` (`string`, required)
- `error.message` (`string`, required)
- `error.param` (`string`)
- `error.docs_url` (`string`, required)
- `error.action_url` (`string<uri>`)
- `request_id` (`string`, required)

```json
{
  "error": {
    "type": "<string>",
    "code": "<string>",
    "message": "<string>",
    "param": "<string>",
    "docs_url": "<string>",
    "action_url": "<string>"
  },
  "request_id": "<string>"
}
```

**413**: Possible codes: request_too_large

- `error` (`object`, required)
- `error.type` (`string`, required)
- `error.code` (`string`, required)
- `error.message` (`string`, required)
- `error.param` (`string`)
- `error.docs_url` (`string`, required)
- `error.action_url` (`string<uri>`)
- `request_id` (`string`, required)

```json
{
  "error": {
    "type": "<string>",
    "code": "<string>",
    "message": "<string>",
    "param": "<string>",
    "docs_url": "<string>",
    "action_url": "<string>"
  },
  "request_id": "<string>"
}
```

**422**: Possible codes: request_too_large_for_plan

- `error` (`object`, required)
- `error.type` (`string`, required)
- `error.code` (`string`, required)
- `error.message` (`string`, required)
- `error.param` (`string`)
- `error.docs_url` (`string`, required)
- `error.action_url` (`string<uri>`)
- `request_id` (`string`, required)

```json
{
  "error": {
    "type": "<string>",
    "code": "<string>",
    "message": "<string>",
    "param": "<string>",
    "docs_url": "<string>",
    "action_url": "<string>"
  },
  "request_id": "<string>"
}
```

**429**: Possible codes: rate_limited

- `error` (`object`, required)
- `error.type` (`string`, required)
- `error.code` (`string`, required)
- `error.message` (`string`, required)
- `error.param` (`string`)
- `error.docs_url` (`string`, required)
- `error.action_url` (`string<uri>`)
- `request_id` (`string`, required)

```json
{
  "error": {
    "type": "<string>",
    "code": "<string>",
    "message": "<string>",
    "param": "<string>",
    "docs_url": "<string>",
    "action_url": "<string>"
  },
  "request_id": "<string>"
}
```

**500**: Possible codes: internal_error

- `error` (`object`, required)
- `error.type` (`string`, required)
- `error.code` (`string`, required)
- `error.message` (`string`, required)
- `error.param` (`string`)
- `error.docs_url` (`string`, required)
- `error.action_url` (`string<uri>`)
- `request_id` (`string`, required)

```json
{
  "error": {
    "type": "<string>",
    "code": "<string>",
    "message": "<string>",
    "param": "<string>",
    "docs_url": "<string>",
    "action_url": "<string>"
  },
  "request_id": "<string>"
}
```

**503**: Possible codes: service_unavailable

- `error` (`object`, required)
- `error.type` (`string`, required)
- `error.code` (`string`, required)
- `error.message` (`string`, required)
- `error.param` (`string`)
- `error.docs_url` (`string`, required)
- `error.action_url` (`string<uri>`)
- `request_id` (`string`, required)

```json
{
  "error": {
    "type": "<string>",
    "code": "<string>",
    "message": "<string>",
    "param": "<string>",
    "docs_url": "<string>",
    "action_url": "<string>"
  },
  "request_id": "<string>"
}
```

**Example request**

```bash
curl --fail-with-body --request POST \
  --url 'https://api.lookio.app/v1/resources/file?file_name=<file_name>' \
  --header 'Authorization: <api-key>' \
  --header 'Content-Type: application/octet-stream' \
  --data-binary '@document.xlsx'
```
