# Files API

List, upload, download, share and organize files in your users' Google Drive and OneDrive with one set of /v1/files endpoints, or publish them as post media.

import { Tab, Tabs } from 'fumadocs-ui/components/tabs';

The Files API works on your users' own Google Drive and OneDrive. Connect an account once, then list, upload, download and share their files with the same `/v1/files` calls.

## What you can build

- **Save generated documents to the user's drive.** Upload a report or invoice with `POST /v1/files`, into a folder made with `POST /v1/files/folders`.
- **Let users attach files from their drive.** Send Google Drive users to a picker made with `POST /v1/files/picker-sessions`, then read the picked files with `GET /v1/files/{fileId}/download`.
- **Feed an AI agent the user's files.** Search by name with `GET /v1/files?q=...` and download the bytes for the model.
- **Publish from the drive.** Pass a file as `{ fileId, accountId }` in a post's `mediaItems`.
- **Share a file with a client.** Get an anyone-with-the-link URL with `GET /v1/files/{fileId}/share-link`.

## Providers

<ProductPlatforms product="files" />

The calls are the same on both. They differ in which files Zernio can see:

| Provider | Storage | Files Zernio can see |
|---|---|---|
| <PlatformName slug="google" page="files" /> | Google Drive | Only files created through Zernio or picked by the user |
| <PlatformName slug="microsoft" page="files" /> | OneDrive | Every file the user can reach in their OneDrive |

Google Drive is connected with Google's narrowest file permission, so the rest of the user's Drive stays private. IMAP mailboxes have no files.

## How it works

A drive is a connected account with its own `accountId`. Connect Google or Microsoft with OAuth and `files` in `scopes`. One account can grant `email`, `calendar` and `files` together.

Every call takes that `accountId`. File ids are the provider's own, and a folder is a file with `kind: "folder"`. Pass a folder's id as `folderId` to list it, or as `parentId` to upload into it.

Calls go live to the provider. Each account counts as one [connected account](/pricing/social), whatever areas it grants.

## Your first request

You need an API key and the `accountId` of an account connected with `files`. This searches the files by name.

<Tabs items={['Node.js', 'Python', 'curl']}>
<Tab value="Node.js">
```typescript
import Zernio from '@zernio/node';

const zernio = new Zernio();

const { data } = await zernio.files.listFiles({
  query: { accountId: '66b2e19d8c3f5a7e9d0b1c2d', q: 'invoice', limit: 20 },
});

for (const file of data.data) {
  console.log(file.id, file.name, file.mimeType);
}
```
</Tab>
<Tab value="Python">
```python
from zernio import Zernio

client = Zernio()

files = client.files.list_files(account_id="66b2e19d8c3f5a7e9d0b1c2d", q="invoice", limit=20)

for f in files["data"]:
    print(f["id"], f["name"], f["mimeType"])
```
</Tab>
<Tab value="curl">
```bash
curl "https://zernio.com/api/v1/files?accountId=66b2e19d8c3f5a7e9d0b1c2d&q=invoice&limit=20" \
  -H "Authorization: Bearer $ZERNIO_API_KEY"
```
</Tab>
</Tabs>

Response (`200`):

```json
{
  "data": [
    {
      "id": "01BYE5RZ6QN3ZWBTUFOFD3GSPGOHDJD36K",
      "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
      "provider": "microsoft",
      "name": "invoice-october.pdf",
      "kind": "file",
      "mimeType": "application/pdf",
      "size": 48213,
      "downloadable": true,
      "webUrl": "https://contoso-my.sharepoint.com/personal/ana/invoice-october.pdf"
    }
  ],
  "pagination": { "hasMore": false, "nextCursor": null }
}
```

Use `id` as `fileId` on every other call. On Google Drive, the search covers only the files Zernio can see.

## Examples

### Upload a file from a URL

Zernio fetches the file from a public https URL and stores it in the user's drive. Omit `parentId` to upload to the root.

<Tabs items={['Node.js', 'Python', 'curl']}>
<Tab value="Node.js">
```typescript
const { data: uploaded } = await zernio.files.uploadFile({
  body: {
    accountId: '66b2e19d8c3f5a7e9d0b1c2d',
    name: 'report-november.pdf',
    contentType: 'application/pdf',
    url: 'https://example.com/reports/november.pdf',
  },
});

console.log(uploaded.data.id, uploaded.data.webUrl);
```
</Tab>
<Tab value="Python">
```python
uploaded = client.files.upload_file(
    account_id="66b2e19d8c3f5a7e9d0b1c2d",
    name="report-november.pdf",
    content_type="application/pdf",
    url="https://example.com/reports/november.pdf",
)

print(uploaded["data"]["id"], uploaded["data"]["webUrl"])
```
</Tab>
<Tab value="curl">
```bash
curl -X POST "https://zernio.com/api/v1/files" \
  -H "Authorization: Bearer $ZERNIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
    "name": "report-november.pdf",
    "contentType": "application/pdf",
    "url": "https://example.com/reports/november.pdf"
  }'
```
</Tab>
</Tabs>

The response (`201`) is the stored file. On Google Drive, `convertTo` imports a text or Office file as an editable Docs, Sheets or Slides file.

### Let a user pick Drive files

To open an existing Drive file, the user picks it first. Create a session and send the user's browser to its `url`, a Google Picker page hosted by Zernio.

```bash
curl -X POST "https://zernio.com/api/v1/files/picker-sessions" \
  -H "Authorization: Bearer $ZERNIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "accountId": "66b2e19d8c3f5a7e9d0b1c2d", "redirectUrl": "https://example.com/files/done", "mimeTypes": ["application/pdf"] }'
```

```json
{ "data": { "id": "ps_…", "url": "https://zernio.com/files/picker/…", "expiresAt": "2026-10-26T16:10:00.000Z" } }
```

The user returns to `redirectUrl` with the picked `fileIds`, and [`file.picked`](/webhooks/inbox/file-picked) fires too. The picked files stay readable from then on. See [the picker](/platforms/google/files#let-the-user-pick-drive-files).

### Publish a drive file in a post

A post's `mediaItems` take `{ fileId, accountId }` next to the usual `url` items. Zernio copies the file when the post is created.

```bash
curl -X POST "https://zernio.com/api/v1/posts" \
  -H "Authorization: Bearer $ZERNIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Our November report is out.",
    "mediaItems": [{ "fileId": "01BYE5RZ6QN3ZWBTUFOFD3GSPGOHDJD36K", "accountId": "66b2e19d8c3f5a7e9d0b1c2d" }],
    "publishNow": true,
    "platforms": [{ "platform": "linkedin", "accountId": "66b2e19d8c3f5a7e9d0b1c2e" }]
  }'
```

The post publishes even if the file changes or the drive disconnects later. A folder is refused as media.

## How it behaves

- **Google formats.** Docs and Slides download as PDF, Sheets as XLSX and Drawings as PNG. Forms and Sites have `downloadable: false`.
- **Upload size.** Uploads by URL accept up to 100 MB over https. Multipart uploads stop at 4.5 MB, so send larger files by URL.
- **Share links.** A share link lets anyone with it read the file until the grant is removed in the drive.
- **Webhooks.** `file.created`, `file.updated` and `file.deleted` fire for changes to files Zernio can see.

## If it fails

| Error | Cause | Fix |
|---|---|---|
| `404` with `reason: "file_not_granted"` | A Drive file the user never picked | Send the user to a picker session |
| `412` `account_scope_missing` | The account was connected without `files` | Connect it again with `files` in `scopes` |
| `400` on `url` | Not https, redirects, or over 100 MB | Use a direct https link to a smaller file |
| `400` on `convertTo` | `convertTo` on a OneDrive account | Leave it out: only Google Drive converts |

## Related

- [Google Drive files](/platforms/google/files)
- [OneDrive files](/platforms/microsoft/files)
- [Upload a file](/files/upload-file)
- [Create a picker session](/files/create-file-picker-session)
- [File picked webhook](/webhooks/inbox/file-picked)
- [Connected account pricing](/pricing/social)