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

# Uploads and files

> How the File scalar is sent, what the server accepts, and how stored files are read back.

## Sending a file

Operations that take a `File` are sent as `multipart/form-data` following the [GraphQL multipart request spec](https://github.com/jaydenseric/graphql-multipart-request-spec). The server accepts this format natively.

The request body has three kinds of fields:

1. `operations`: a JSON string of the normal `{ query, variables }` payload, with `null` in place of each file.
2. `map`: a JSON string mapping each file field name to the dotted path(s) in `operations` it should fill, for example `{ "0": ["variables.file"] }`.
3. One form field per file, named as in `map`.

The server rejects a multipart body without an `operations` field, and requires both `operations` and `map` to be JSON strings.

Client libraries that implement the spec (for example `apollo-upload-client` for Apollo) produce this format automatically when a variable holds a `File` or `Blob`; plain JSON is used when no variable holds a file.

Operations that accept a `File` include `uploadChemicalSds`, `uploadEquipmentManual`, `uploadUserAvatar`, `uploadUserDocument`, `uploadIbcMeetingDocument`, `uploadMarkdownImage`, `processChemicalLabel`, `parseWasteManifest` (a list of files) and `CreateIncidentInput.photos`. The reference lists every field typed `File`.

## Validation

Every upload is validated before it is stored. A failed check is returned as a GraphQL error with `extensions.code` set as shown.

| Check | Rule | `extensions.code` |
| - | - | - |
| Size | At most 25 MB (25 × 1024 × 1024 bytes). Message: `File exceeds the 25 MB upload limit.` | `FILE_TOO_LARGE` |
| Blocked extensions | `.svg`, `.svgz`, `.html`, `.htm`, `.xml`, `.xhtml` are rejected regardless of MIME type. | `INVALID_FILE_TYPE` |
| MIME type | Must be in the allow-list for the operation's context (below). | `INVALID_FILE_TYPE` |

Each operation validates against one of five contexts:

<AccordionGroup>
  <Accordion title="image">
    `image/png`, `image/jpeg`, `image/gif`, `image/webp`. Used for incident photos.
  </Accordion>

  <Accordion title="pdf">
    `application/pdf`.
  </Accordion>

  <Accordion title="document">
    `application/pdf`, `application/msword`, `application/vnd.openxmlformats-officedocument.wordprocessingml.document`, `application/x-iwork-pages-sffpages`, `image/png`, `image/jpeg`.
  </Accordion>

  <Accordion title="embed">
    `image/png`, `image/jpeg`, `image/gif`, `image/webp`, `video/mp4`, `video/webm`. Used for the images and videos embedded in protocol steps.
  </Accordion>

  <Accordion title="protocol-file">
    `application/pdf`, `application/vnd.openxmlformats-officedocument.wordprocessingml.document`, `video/mp4`, `video/quicktime`, `video/webm`, `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`, `application/vnd.apple.numbers`, `text/csv`.
  </Accordion>
</AccordionGroup>

<Tip>
  The MIME check uses the `type` the browser reports for the file. A file with the right extension but an empty or unexpected `type` is rejected with a message listing the types the context expects.
</Tip>

## Reading a file back

Every uploaded file is identified by an opaque key that the API returns as a string. `Incident.photos`, for example, is declared as `[File!]!` on output as well as input, and on output it lists the keys of the incident's photos. Treat keys as identifiers: their format is not part of the API and may change.

A file is read back from `GET /files/<key>` on the app host, using the same authentication as the API:

| Condition | Response |
| - | - |
| The caller is not authenticated or has no active organization | `401` |
| The file belongs to another organization | `403` |
| No file matches the key | `404` |
| Otherwise | The file with its `Content-Type`, marked private and cacheable for one hour |

PNG, JPEG, GIF, WebP, PDF, MP4 and QuickTime files are served inline; every other type is served as a download.

The same pattern applies to every file the API hands back by key, such as protocol files and embedded images.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.