Skip to content

Files

There are two ways to give us a document: upload it to us, or give us an HTTPS link to it. Uploading is the more reliable of the two.

POST /v1/files

Parameter Required Description
purpose Yes question_paper, marking_scheme or answer_script.
filename Yes The file’s name, for your reference. Up to 255 characters.
content_type Yes application/pdf, image/jpeg or image/png. See Accepted documents.
size_bytes Yes The exact size of the file in bytes.

Response 201:

{
"id": "file_As2Vb6Nc8T",
"object": "file",
"livemode": true,
"purpose": "answer_script",
"filename": "STU-0042-physics-paper-2.pdf",
"content_type": "application/pdf",
"size_bytes": 7340032,
"pages": null,
"status": "awaiting_upload",
"upload": {
"url": "https://...",
"method": "PUT",
"headers": { "Content-Type": "application/pdf", "If-None-Match": "*" },
"expires_at": "2026-10-06T10:14:02Z"
},
"created_at": "2026-10-06T09:14:02Z",
"expires_at": "2026-10-07T09:14:02Z"
}

Then send the file’s bytes to upload.url:

Terminal window
curl -X PUT "<upload.url>" \
-H "Content-Type: application/pdf" \
-H "If-None-Match: *" \
--data-binary @STU-0042-physics-paper-2.pdf
  • Use upload.method, and send exactly the headers in upload.headers. Don’t add your API key: the URL is already authorised.
  • Send exactly size_bytes bytes. The upload is refused if the size differs.
  • When the PUT returns 200, the file is uploaded and you can use it straight away.
  • An upload URL takes one upload. A second PUT gets 412: the first one is already stored. Retrieve the file to check that it is uploaded.
  • The upload URL works until upload.expires_at, one hour after creation. After that, create a new file.
  • The upload URL belongs to our storage service. Its errors are short XML documents, not this API’s error format; treat any non-200 as a failed upload and create a new file.
  • upload is returned only when you create the file. It is null when you retrieve the file later.
Field Type Description
id string Unique ID, prefix file_.
object string Always file.
livemode boolean true for live mode.
purpose string question_paper, marking_scheme or answer_script.
filename string The name you gave, or the last part of the link’s path.
content_type string The file’s format.
size_bytes integer The file’s size.
pages integer Pages in the file once we’ve read it, otherwise null. Each image counts as 1.
status string See below.
upload object Upload instructions. Only in the create response.
created_at timestamp When the file was created.
expires_at timestamp When the file is deleted if it isn’t used, or null while it is attached.
status Meaning
awaiting_upload Created, but we haven’t received the bytes yet.
uploaded We have the bytes. You can use the file in a paper or submission.
attached Used by a paper or submission. It is kept, and deleted, with that object.
expired Not used in time, so we deleted it.
  • A file can be used by one paper or one submission at a time, and only for the purpose it was created with.
  • If the paper or submission using a file fails or is cancelled, the file is released: it returns to uploaded, and you can use it again within 24 hours.
  • A file that is never used is deleted 24 hours after it was created.

GET /v1/files/{id}

Wherever a request takes a document, you can pass a link instead of a file ID:

{
"paper_id": "pap_3fT9qLx2Wd",
"student": { "external_id": "STU-0042" },
"files": [ { "url": "https://cdn.example.com/scripts/STU-0042.pdf?signature=..." } ]
}
  • The link must use HTTPS and work without cookies or extra headers. A pre-signed link is ideal.
  • Keep it valid for at least 1 hour after your request.
  • Your server must start responding within 30 seconds. We follow up to 3 redirects, and refuse links to private or internal network addresses.
  • The download must be an accepted format and within the size limit.

We download each link once, shortly after your request, and create a File from it. That File then appears in the paper’s question_paper or marking_scheme, or in the submission’s files. If a download fails, the paper or submission fails with file_download_failed, and nothing is charged.

Purpose Formats Per file Per paper or submission
question_paper PDF Up to 100 MB and 50 pages One file
marking_scheme PDF Up to 100 MB and 50 pages One file
answer_script PDF, JPEG or PNG Up to 100 MB One PDF, or 1 to 50 images (not both); 50 pages in total
  • We check each file’s actual contents, not its name or declared type.
  • Images must be at least 200 pixels on each side, and each image must show exactly one page.
  • We compress large documents before we read them; compression never changes the page count. To be read in full, a script must be under 12 MB after compression, and a paper’s question paper and marking scheme together under 15 MB. Anything larger fails with file_too_large, and nothing is charged.
  • Scan at 150 to 200 dpi rather than sending full-resolution phone photos: smaller files are read faster and stay within the size limits.
  • Scan or photograph each page flat, upright and in good light, with the whole page in view.
  • Put one student’s pages in one submission, in page order.
  • Leave out pages with nothing written on them. Every page you send is counted and charged.
  • Where you can, cover students’ names on the script itself. We don’t need them to mark.