- POST /api/v2/files — Reserve a file upload.
- POST /api/v2/files/{id}/attach — Attach a scanned file to its target.
- POST /api/v2/files/{id}/confirm — Confirm a file upload.
POST /api/v2/files
Reserve a file upload.
Step 1 of 3. The target is checked and authorised as the product’s own action on it, the size and content type against its limits, all before anything is stored; then a 15-minute presigned upload is answered.
- Operation ID:
reserveFileUpload - Authentication: A Bearer token from a credential holding the
write:filesscope. Runs as the credential’s Administrator, and needs anIdempotency-Keyheader.
curl -X POST "https://your-org.backed.giving/api/v2/files" \
-H "Authorization: Bearer $BACKED_TOKEN" \
-H "Idempotency-Key: 7d1f0c2e-5b8a-4f3e-9c61-2a4b8e9d0f13" \
-H "Content-Type: application/json" \
-d '{"filename":"final-report.pdf","content_type":"application/pdf","size":482113,"target":{"type":"milestone_deliverable","id":2210,"field":950}}'Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
Idempotency-Key | header | Yes | string | A key of 1 to 255 characters, unique per write. A retry with the same key, method, path and body within 24 hours answers the first response again (with Idempotent-Replayed: true) and writes nothing. Example: 7d1f0c2e-5b8a-4f3e-9c61-2a4b8e9d0f13. |
Request body
application/json, the FileUploadRequest schema. The file to reserve and where it goes: a milestone deliverable (with its File upload field), an application note or a registration entity.
| Field | Type | Required | Notes |
|---|---|---|---|
filename | string | Yes | |
content_type | string | Yes | |
size | integer | Yes | |
target | object | Yes |
{
"filename": "final-report.pdf",
"content_type": "application/pdf",
"size": 482113,
"target": {
"type": "milestone_deliverable",
"id": 2210,
"field": 950
}
}201 response
The upload id and presigned upload.
Headers: Idempotent-Replayed.
| Field | Type | Required | Notes |
|---|---|---|---|
id | string | Yes | |
upload | object | Yes | |
expires_at | string | Yes | |
max_bytes | integer | Yes | |
state | string | Yes | Always awaiting_upload. |
{
"id": "upl_3Jq9xVb2LmT8sKp4Rz7Wn1Ye",
"upload": {
"url": "https://uploads.example.com/backed",
"method": "POST",
"fields": {
"key": "tmp/2026/11/09/3f0c9d1e",
"policy": "eyJleHBpcmF0aW9uIjoi...",
"x-amz-signature": "9c1f4e..."
},
"headers": {}
},
"expires_at": "2026-11-09T02:45:00.000Z",
"max_bytes": 104857600,
"state": "awaiting_upload"
}400 response
idempotency_key_required: The write has no Idempotency-Key header of 1 to 255 characters. invalid_request: A query parameter is invalid, or a write’s body is not a JSON object.
{
"error": "idempotency_key_required",
"error_description": "A write needs an Idempotency-Key header of 1 to 255 characters"
}{
"error": "invalid_request",
"error_description": "limit is invalid"
}401 response
The token is missing, invalid, expired or revoked, of another Organisation, or API access is not Full. Ask for a new token.
Headers: WWW-Authenticate.
{
"error": "invalid_token",
"error_description": "The access token is invalid or has expired"
}403 response
https_required: The request was sent over plain HTTP and was not processed. insufficient_scope: The credential does not hold the scope the route needs. run_as_disabled: The credential has no run-as Administrator, or that person is no longer one. forbidden: The run-as Administrator lacks the permission, or the record is outside their Data Access Rules.
Headers: WWW-Authenticate.
{
"error": "https_required"
}{
"error": "insufficient_scope",
"error_description": "This credential lacks the write:applications scope"
}{
"error": "run_as_disabled",
"error_description": "This credential has no enabled run-as Administrator"
}{
"error": "forbidden",
"error_description": "The run-as Administrator may not do this to this record"
}404 response
An unknown host or path, or an id that is unknown, deleted or of another Organisation.
{
"error": "not_found"
}409 response
idempotency_in_progress: The first request with this Idempotency-Key has not finished. Nothing was written; retry after the given seconds. invalid_state: The record is not in a state that admits this write.
Headers: Retry-After.
{
"error": "idempotency_in_progress",
"error_description": "The first request with this Idempotency-Key has not finished. If this persists it stopped part-way: check whether the record exists before writing again with a new key"
}{
"error": "invalid_state",
"error_description": "The target does not take files in its current state"
}413 response
The body, or the declared file, is larger than the limit that applies.
{
"error": "payload_too_large",
"error_description": "The request body is too large"
}415 response
The body is not application/json, or the target does not accept the file’s content type.
{
"error": "unsupported_media_type",
"error_description": "The request body must be application/json"
}422 response
idempotency_key_reused: The Idempotency-Key was used in the last 24 hours for a different method, path or body. unknown_field: The body has a property the route does not document; the first is named. validation_failed: Body values the product’s own form would refuse; fields names each.
{
"error": "idempotency_key_reused",
"error_description": "This Idempotency-Key was used for a different request"
}{
"error": "unknown_field",
"error_description": "bank_account_number is not a writable field"
}{
"error": "validation_failed",
"error_description": "Invalid fields: due_date",
"fields": [
"due_date"
]
}429 response
A rate limit was exceeded: 120 requests a minute per credential, 30 token requests a minute per client_id.
Headers: Retry-After.
{
"error": "rate_limited",
"error_description": "Too many requests"
}500 response
An unexpected server error.
{
"error": "server_error"
}503 response
The Organisation’s API access cannot be determined, or a dependency is unavailable. Retry after the given seconds.
Headers: Retry-After.
{
"error": "temporarily_unavailable"
}POST /api/v2/files/{id}/attach
Attach a scanned file to its target.
Step 3 of 3. Only a clean file is attached: still scanning is 409 file_not_scanned; rejected by the scan is 422 file_infected. No body. A repeat answers the same attachment.
- Operation ID:
attachFileUpload - Authentication: A Bearer token from a credential holding the
write:filesscope. Runs as the credential’s Administrator, and needs anIdempotency-Keyheader.
curl -X POST "https://your-org.backed.giving/api/v2/files/upl_3Jq9xVb2LmT8sKp4Rz7Wn1Ye/attach" \
-H "Authorization: Bearer $BACKED_TOKEN" \
-H "Idempotency-Key: 7d1f0c2e-5b8a-4f3e-9c61-2a4b8e9d0f13"Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
id | path | Yes | string | The opaque upload id POST /api/v2/files answered, for the credential that reserved it. Example: upl_3Jq9xVb2LmT8sKp4Rz7Wn1Ye. |
Idempotency-Key | header | Yes | string | A key of 1 to 255 characters, unique per write. A retry with the same key, method, path and body within 24 hours answers the first response again (with Idempotent-Replayed: true) and writes nothing. Example: 7d1f0c2e-5b8a-4f3e-9c61-2a4b8e9d0f13. |
200 response
The attachment.
Headers: Idempotent-Replayed.
| Field | Type | Required | Notes |
|---|---|---|---|
id | integer | Yes | |
state | string | Yes | Always attached. |
target | object | Yes |
{
"id": 99120,
"state": "attached",
"target": {
"type": "milestone_deliverable",
"id": 2210,
"field": 950
}
}400 response
The write has no Idempotency-Key header of 1 to 255 characters.
{
"error": "idempotency_key_required",
"error_description": "A write needs an Idempotency-Key header of 1 to 255 characters"
}401 response
The token is missing, invalid, expired or revoked, of another Organisation, or API access is not Full. Ask for a new token.
Headers: WWW-Authenticate.
{
"error": "invalid_token",
"error_description": "The access token is invalid or has expired"
}403 response
https_required: The request was sent over plain HTTP and was not processed. insufficient_scope: The credential does not hold the scope the route needs. run_as_disabled: The credential has no run-as Administrator, or that person is no longer one. forbidden: The run-as Administrator lacks the permission, or the record is outside their Data Access Rules.
Headers: WWW-Authenticate.
{
"error": "https_required"
}{
"error": "insufficient_scope",
"error_description": "This credential lacks the write:applications scope"
}{
"error": "run_as_disabled",
"error_description": "This credential has no enabled run-as Administrator"
}{
"error": "forbidden",
"error_description": "The run-as Administrator may not do this to this record"
}404 response
An unknown host or path, or an id that is unknown, deleted or of another Organisation.
{
"error": "not_found"
}409 response
idempotency_in_progress: The first request with this Idempotency-Key has not finished. Nothing was written; retry after the given seconds. file_not_scanned: The file has not finished its malware scan; try the attach again shortly. invalid_state: The record is not in a state that admits this write.
Headers: Retry-After.
{
"error": "idempotency_in_progress",
"error_description": "The first request with this Idempotency-Key has not finished. If this persists it stopped part-way: check whether the record exists before writing again with a new key"
}{
"error": "file_not_scanned",
"error_description": "The file has not finished its malware scan"
}{
"error": "invalid_state",
"error_description": "The target does not take files in its current state"
}413 response
The body, or the declared file, is larger than the limit that applies.
{
"error": "payload_too_large",
"error_description": "The request body is too large"
}415 response
The body is not application/json, or the target does not accept the file’s content type.
{
"error": "unsupported_media_type",
"error_description": "The request body must be application/json"
}422 response
idempotency_key_reused: The Idempotency-Key was used in the last 24 hours for a different method, path or body. file_infected: The file failed its malware scan and was not attached.
{
"error": "idempotency_key_reused",
"error_description": "This Idempotency-Key was used for a different request"
}{
"error": "file_infected",
"error_description": "The file failed its malware scan"
}429 response
A rate limit was exceeded: 120 requests a minute per credential, 30 token requests a minute per client_id.
Headers: Retry-After.
{
"error": "rate_limited",
"error_description": "Too many requests"
}500 response
An unexpected server error.
{
"error": "server_error"
}503 response
The Organisation’s API access cannot be determined, or a dependency is unavailable. Retry after the given seconds.
Headers: Retry-After.
{
"error": "temporarily_unavailable"
}POST /api/v2/files/{id}/confirm
Confirm a file upload.
Step 2 of 3, after the bytes are uploaded: the file is recorded and its malware scan begins. No body. Not yet uploaded: 409 invalid_state.
- Operation ID:
confirmFileUpload - Authentication: A Bearer token from a credential holding the
write:filesscope. Runs as the credential’s Administrator, and needs anIdempotency-Keyheader.
curl -X POST "https://your-org.backed.giving/api/v2/files/upl_3Jq9xVb2LmT8sKp4Rz7Wn1Ye/confirm" \
-H "Authorization: Bearer $BACKED_TOKEN" \
-H "Idempotency-Key: 7d1f0c2e-5b8a-4f3e-9c61-2a4b8e9d0f13"Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
id | path | Yes | string | The opaque upload id POST /api/v2/files answered, for the credential that reserved it. Example: upl_3Jq9xVb2LmT8sKp4Rz7Wn1Ye. |
Idempotency-Key | header | Yes | string | A key of 1 to 255 characters, unique per write. A retry with the same key, method, path and body within 24 hours answers the first response again (with Idempotent-Replayed: true) and writes nothing. Example: 7d1f0c2e-5b8a-4f3e-9c61-2a4b8e9d0f13. |
200 response
The scan has begun.
Headers: Idempotent-Replayed.
| Field | Type | Required | Notes |
|---|---|---|---|
id | string | Yes | |
state | string | Yes | Always scanning. |
{
"id": "upl_3Jq9xVb2LmT8sKp4Rz7Wn1Ye",
"state": "scanning"
}400 response
The write has no Idempotency-Key header of 1 to 255 characters.
{
"error": "idempotency_key_required",
"error_description": "A write needs an Idempotency-Key header of 1 to 255 characters"
}401 response
The token is missing, invalid, expired or revoked, of another Organisation, or API access is not Full. Ask for a new token.
Headers: WWW-Authenticate.
{
"error": "invalid_token",
"error_description": "The access token is invalid or has expired"
}403 response
https_required: The request was sent over plain HTTP and was not processed. insufficient_scope: The credential does not hold the scope the route needs. run_as_disabled: The credential has no run-as Administrator, or that person is no longer one. forbidden: The run-as Administrator lacks the permission, or the record is outside their Data Access Rules.
Headers: WWW-Authenticate.
{
"error": "https_required"
}{
"error": "insufficient_scope",
"error_description": "This credential lacks the write:applications scope"
}{
"error": "run_as_disabled",
"error_description": "This credential has no enabled run-as Administrator"
}{
"error": "forbidden",
"error_description": "The run-as Administrator may not do this to this record"
}404 response
An unknown host or path, or an id that is unknown, deleted or of another Organisation.
{
"error": "not_found"
}409 response
idempotency_in_progress: The first request with this Idempotency-Key has not finished. Nothing was written; retry after the given seconds. invalid_state: The record is not in a state that admits this write.
Headers: Retry-After.
{
"error": "idempotency_in_progress",
"error_description": "The first request with this Idempotency-Key has not finished. If this persists it stopped part-way: check whether the record exists before writing again with a new key"
}{
"error": "invalid_state",
"error_description": "The target does not take files in its current state"
}413 response
The body, or the declared file, is larger than the limit that applies.
{
"error": "payload_too_large",
"error_description": "The request body is too large"
}415 response
The body is not application/json, or the target does not accept the file’s content type.
{
"error": "unsupported_media_type",
"error_description": "The request body must be application/json"
}422 response
The Idempotency-Key was used in the last 24 hours for a different method, path or body.
{
"error": "idempotency_key_reused",
"error_description": "This Idempotency-Key was used for a different request"
}429 response
A rate limit was exceeded: 120 requests a minute per credential, 30 token requests a minute per client_id.
Headers: Retry-After.
{
"error": "rate_limited",
"error_description": "Too many requests"
}500 response
An unexpected server error.
{
"error": "server_error"
}503 response
The Organisation’s API access cannot be determined, or a dependency is unavailable. Retry after the given seconds.
Headers: Retry-After.
{
"error": "temporarily_unavailable"
}