# Files

List, search, upload, download, organise and share files in a user's Google Drive or OneDrive, let users pick Drive files, and use them as post media.

Work with files in your users' Google Drive or OneDrive. Save a generated report into their drive, fetch a contract they picked, share a link, or attach a drive file to a social post. Every call takes the `accountId` of an account connected with the `files` area.

## What Pozy can see

| Provider | Access |
| --- | --- |
| Google Drive | Only files created through Pozy and files the user picked in Google Picker. Pozy never asks for full Drive access |
| OneDrive | Every file the user can see in their OneDrive. No picker step |

On Google, a file outside those two groups returns `404` with `details.reason: "file_not_granted"`. Picker sessions, which let the user grant such a file, are not available yet (see below), so for now work with files created through Pozy.

## Ask the user to choose Drive files

> **Not available yet:**
> 
> Picker sessions are not available yet: `POST /v1/files/picker-sessions` answers `403` with `code: "feature_not_available"`. This section describes how they will work once Pozy hosts the picker page.

Start a picker session and open its `url` in the user's browser. Pozy hosts the page, where Google Picker lets them select the files to share.

```bash cURL
curl -X POST https://api.pozy.ai/v1/files/picker-sessions \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "accountId": "6710c501e4b0d2f1a8c95e77",
    "redirectUrl": "https://app.example.com/files/picked",
    "mimeTypes": ["application/pdf", "image/jpeg", "image/png"]
  }'
```
```ts Node
const res = await fetch("https://api.pozy.ai/v1/files/picker-sessions", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.POZY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    accountId: "6710c501e4b0d2f1a8c95e77",
    redirectUrl: "https://app.example.com/files/picked",
    mimeTypes: ["application/pdf", "image/jpeg", "image/png"],
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
// Redirect the user to data.data.url
```
```python Python
import os, requests

session = requests.post(
    "https://api.pozy.ai/v1/files/picker-sessions",
    headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
    json={"accountId": "6710c501e4b0d2f1a8c95e77", "redirectUrl": "https://app.example.com/files/picked"},
).json()["data"]
```

| Field | What it does |
| --- | --- |
| `redirectUrl` | Where the user goes afterwards. Optional if you handle picks on your server |
| `fileIds` | Files already ticked when the picker opens, 50 at most |
| `mimeTypes` | Only show these types, 30 at most |
| `multiselect` | Allow several files. Default `true` |

Once the user is done, Pozy sends them to `redirectUrl` with `pickerSessionId` and either `status=picked` plus the chosen `fileIds` (comma-separated) or `status=cancelled`. The same files also arrive in a `file.picked` webhook. Each session works once and lapses after 30 minutes, so make a new one for every pick. Access to picked files does not lapse: Pozy can read them until the user removes the app in their Google account. Pickers exist for Google only; on a Microsoft account the call fails with `400 platform_not_supported`.

## List and search

```bash cURL
curl "https://api.pozy.ai/v1/files?accountId=6710c501e4b0d2f1a8c95e77&q=invoice&limit=20" \
  -H "Authorization: Bearer $POZY_API_KEY"
```
```ts Node
const res = await fetch("https://api.pozy.ai/v1/files?accountId=6710c501e4b0d2f1a8c95e77&q=invoice&limit=20", {
  headers: { Authorization: `Bearer ${process.env.POZY_API_KEY}` },
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
for (const f of data.data) console.log(f.kind, f.name, f.size, f.webUrl);
```
```python Python
files = requests.get(
    "https://api.pozy.ai/v1/files",
    headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
    params={"accountId": "6710c501e4b0d2f1a8c95e77", "q": "invoice", "limit": 20},
).json()["data"]
```

Without `folderId` you get the root (OneDrive) or the granted files (Drive). `q` searches names (and content on OneDrive), up to 200 characters. Pages hold up to 100 items, 50 by default. Each item has `id`, `name`, `kind` (`file` or `folder`), `mimeType`, `size`, `parentId`, `webUrl`, `downloadable`, `createdAt`, `modifiedAt` and `trashed`. `GET /v1/files/{fileId}?accountId=...` returns one item.

## Download

`GET /v1/files/{fileId}/download?accountId=...` returns the file's content, with its filename and content type. Files in Google's own formats are not stored as bytes, so Pozy converts them as it downloads:

| Google file | You receive |
| --- | --- |
| Docs | PDF |
| Slides | PDF |
| Sheets | XLSX |
| Drawings | PNG |

Folders, Forms and Sites cannot be downloaded and show `downloadable: false`.

## Upload

Upload from a URL as JSON, or send the file as `multipart/form-data`:

```bash cURL
curl -X POST https://api.pozy.ai/v1/files \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "accountId": "6710c501e4b0d2f1a8c95e77",
    "name": "October report.pdf",
    "contentType": "application/pdf",
    "url": "https://files.example.com/reports/october.pdf",
    "parentId": "1qFolderAbC"
  }'
```
```ts Node
const res = await fetch("https://api.pozy.ai/v1/files", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.POZY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    accountId: "6710c501e4b0d2f1a8c95e77",
    name: "October report.pdf",
    contentType: "application/pdf",
    url: "https://files.example.com/reports/october.pdf",
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
console.log(data.data.id, data.data.webUrl);
```
```python Python
resp = requests.post(
    "https://api.pozy.ai/v1/files",
    headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
    json={
        "accountId": "6710c501e4b0d2f1a8c95e77",
        "name": "October report.pdf",
        "contentType": "application/pdf",
        "url": "https://files.example.com/reports/october.pdf",
    },
)
```

| Limit | Value |
| --- | --- |
| Upload by URL | Up to 100 MB, HTTPS only, no redirects, must download within 20 seconds |
| Multipart upload | Bounded by the 4.5 MB request size. Send larger files by URL |
| Names | Up to 255 characters, no `/` or `\` |
| `convertTo` | Google only: import as a Google Doc, Sheet or Slides. OneDrive refuses it |

A file uploaded through Pozy is granted from then on, so you can read it back on Drive.

## Organise and share

| Job | Call |
| --- | --- |
| Create a folder | `POST /v1/files/folders` with `name` and optional `parentId` |
| Rename or move | `PATCH /v1/files/{fileId}` with `name` or `parentId` |
| Trash | `DELETE /v1/files/{fileId}?accountId=...` (Drive trash, OneDrive recycle bin) |
| Share link | `GET /v1/files/{fileId}/share-link?accountId=...` opens the file to anyone who has the link, and gives you that link |

If a company has disabled anonymous links in OneDrive, the share-link call fails for its files.

## Post a file from a drive

In a post's `mediaItems`, a drive file can stand in for a URL:

```json
{
  "mediaItems": [{ "type": "image", "fileId": "1AbCdEfGhIjK", "accountId": "6710c501e4b0d2f1a8c95e77" }]
}
```

Pozy takes its own copy as soon as the post is created. Later edits to the file, or a disconnected account, do not affect the post. A folder, or any file with nothing to download, makes that media item fail with `400`. See [Media](https://pozy.ai/docs/publishing/media).

## Webhook events

`file.created`, `file.updated` and `file.deleted` fire for changes to files Pozy can see. The payload carries the file `id` and `changeType`; fetch the file for its current state. `file.picked` lists the files chosen in a picker session.

| Event | When it fires |
| --- | --- |
| [`file.created`](https://pozy.ai/docs/webhooks/events#file-created) | File created. |
| [`file.updated`](https://pozy.ai/docs/webhooks/events#file-updated) | File updated. |
| [`file.picked`](https://pozy.ai/docs/webhooks/events#file-picked) | Files picked. |
| [`file.deleted`](https://pozy.ai/docs/webhooks/events#file-deleted) | File deleted. |

## Errors

| Error | Cause | Fix |
| --- | --- | --- |
| `404`, `reason: "file_not_granted"` | Pozy has no access to that Drive file: it was not made through Pozy and the user never picked it | Use a file created through Pozy. Picker sessions are not available yet |
| `400` on `url` | The link is not HTTPS, redirects, or points to more than 100 MB | Link straight to the file over HTTPS, and keep it under 100 MB |
| `400 platform_not_supported` | Picker sessions and `convertTo` on a Microsoft account | Both only exist for Google |
| `412 account_scope_missing` | No `files` area on the account | Reconnect with `files` in `scopes` |

## Endpoints

| Endpoint | What it does |
| --- | --- |
| [`GET /v1/files`](https://pozy.ai/docs/api/listFiles) | List files in a folder or search the drive |
| [`POST /v1/files`](https://pozy.ai/docs/api/uploadFile) | Upload a file |
| [`POST /v1/files/folders`](https://pozy.ai/docs/api/createFolder) | Create a folder |
| [`POST /v1/files/picker-sessions`](https://pozy.ai/docs/api/createFilePickerSession) | Create a hosted Google Picker session (not available yet) |
| [`GET /v1/files/{fileId}`](https://pozy.ai/docs/api/getFile) | Get a file or folder |
| [`PATCH /v1/files/{fileId}`](https://pozy.ai/docs/api/updateFile) | Rename or move a file |
| [`DELETE /v1/files/{fileId}`](https://pozy.ai/docs/api/trashFile) | Move a file to the trash |
| [`GET /v1/files/{fileId}/download`](https://pozy.ai/docs/api/downloadFile) | Download a file |
| [`GET /v1/files/{fileId}/share-link`](https://pozy.ai/docs/api/getFileShareLink) | Get an anyone-with-the-link share link |
