Write API: milestones, payments, contracts and files
Create and approve milestones, mark payment batches paid, record contract statuses, and upload and attach files from another system through the Backed API.
Last updated
This page is for your organisation's developers. It lists the Backed API routes that create or change milestones, payments and contracts, and that upload files, and what each one refuses. For registrations, applications, notes, tags and assessor assignments, see Write API: registrations, applications, notes, tags and assignments.
No write route reads or changes a bank account number. A bank_account_number in a request body is
refused with 422 unknown_field, and no write response carries one.
Before you start
A credential can only read until you give it a write scope and an Administrator to act as:
- Go to
Settings > Profile and termsand open theAPI Accesstab. - Select
Edit scopeson the credential's row, orNew Credentialfor a new one. - Tick the scopes the system needs. See API access.
- Choose the Administrator it acts as under
Acts as. This needsCan edit permissions. - Select
Save.
The six scopes
| Scope | Label on the tab | Opens |
|---|---|---|
read |
Read |
Every read route. Every credential has it. |
write:registrations |
Write registrations |
The registration routes, on the part one page. |
write:applications |
Write applications |
The transition, note and tag routes, on the part one page. |
write:assessments |
Write assessments |
The assignment route, on the part one page. |
write:post_award |
Write post-award records |
The milestone, payment and contract routes on this page. |
write:files |
Write files |
The file routes on this page. |
Who a write runs as
Every write runs with the permissions, data access rules and approval limit of the credential's
Acts as Administrator, so it can't do anything they couldn't do in Backed. It goes through the same
checks, sends the same messages and records the same audit entries as the action on the screen.
In the record's history a write shows the credential's name, then Acting as and the
Administrator's name.
If that Administrator is removed, disabled or no longer an Administrator, every write answers
403 run_as_disabled until you choose someone else. Reads carry on.
The Idempotency-Key header
Send your access token as a bearer token and this header on every POST: 1 to 255 characters your
system makes up, unique to that change.
- The same key and the same body within 24 hours answers the first response again, with the header
Idempotent-Replayed: true, and changes nothing. - The same key with a different body or path is refused with
422 idempotency_key_reused. - No key is refused with
400 idempotency_key_required. - A refused request stores nothing, so you can retry it with the same key.
No route on this page is a PATCH, so none takes If-Match. That header belongs to the PATCH route
on the part one page, where an out-of-date lock_version is refused with 412 precondition_failed.
Routes
Every path is on your organisation's host. Each route can also give the refusals in Refusals every write can give.
| Method | Path | Scope | Answers | Refusals of its own |
|---|---|---|---|---|
POST |
/api/v2/milestones |
write:post_award |
201 |
404 module_disabled |
POST |
/api/v2/milestones/:id/approve |
write:post_award |
200 |
404 module_disabled, 404 not_found, 403 approval_limit_exceeded, 409 invalid_transition, 409 invalid_state |
POST |
/api/v2/payments/:id/mark_paid |
write:post_award |
200 |
404 module_disabled, 404 not_found, 409 invalid_transition |
POST |
/api/v2/contracts/:id/status |
write:post_award |
200 |
404 module_disabled, 404 not_found, 409 invalid_transition |
POST |
/api/v2/files |
write:files |
201 |
404 not_found, 409 invalid_state, 413 payload_too_large, 415 unsupported_media_type |
POST |
/api/v2/files/:id/confirm |
write:files |
200 |
404 not_found, 409 invalid_state |
POST |
/api/v2/files/:id/attach |
write:files |
200 |
404 not_found, 409 invalid_state, 409 file_not_scanned, 422 file_infected |
404 module_disabled means the module is turned off for your organisation: Contracts for the milestone
and contract routes, and Payments for the payment route. The file routes have no module of their own.
Another organisation's id answers the same 404 not_found as an id that was never issued.
Milestones
POST /api/v2/milestones creates one milestone on an application, as Create Milestone does. Send
funding_application_id, title, due_date (YYYY-MM-DD), amount (such as "48500.00") and
form_template_id, which is the deliverables form. You can also send milestone_template_id,
description and reviewer_user_ids. The ids in this body are strings of digits. An application or
form template that doesn't exist or that the Administrator can't see is refused with
422 validation_failed on that field.
POST /api/v2/milestones/:id/approve does what Approve Milestone does and takes no body. Both
answer with the milestone as GET /api/v2/milestones/:id reads it.
- An amount above the Administrator's
Milestone Approval Limitis refused with403 approval_limit_exceeded. - A milestone that can't be approved from where it stands is refused with
409 invalid_transition, andcurrent_statussays where it stands. - A deliverable not yet approved, or a change made while it was being approved, is refused with
409 invalid_state. The text says which.
Payments
POST /api/v2/payments/:id/mark_paid does what Mark as Paid does, so :id is a payment batch, not
a single payment. Send paid_date (YYYY-MM-DD) and reference, both required. paid_date goes on
each payment marked, and reference on the batch's paid entry in its trail.
A batch that isn't approved, or that is already paid, is refused with 409 invalid_transition and
nothing changes. It answers id, status, marked_paid_at, paid_date, reference,
payment_count and amount_total.
Contracts
POST /api/v2/contracts/:id/status takes a status and answers with the contract as
GET /api/v2/contracts/:id reads it.
executedis the Administrator's own counter-signature, by typed name. Backed has no offline signing method, so the Administrator must be a signatory of this contract and it must be their turn to sign. It executes the contract when theirs is the last signature.terminatedneeds areason. The contract staysterminatingwhile the termination letter is sent in Backed, as it does on the screen.- Any other status,
completedincluded, is refused with409 invalid_transition. So is a move that isn't open from where the contract stands, andcurrent_statussays where that is.
Files
Uploading takes three steps. Send an Idempotency-Key on each, with no body on the last two.
- Reserve.
POST /api/v2/filestakesfilename,content_type,sizein bytes andtarget. It answers anidfor the next two steps, theuploadto make,expires_at,max_bytesandstate. The upload is valid for 15 minutes. - Upload, then confirm. If
upload.methodisPOST, send a multipart form toupload.urlwithupload.fieldsin order and the file last, in a part namedfile. If it isPUT, send the bytes withupload.headers. ThenPOST /api/v2/files/:id/confirmstarts the malware scan and answersstate: scanning. Confirming before the upload is stored is refused with409 invalid_state. - Attach.
POST /api/v2/files/:id/attachputs the clean file on its target and answersid,state: attachedandtarget. While the scan is running it is refused with409 file_not_scanned, so wait and retry. A file that failed the scan is refused with422 file_infectedand is never attached. A file never confirmed answers404 not_found. Attaching again answers the same and adds nothing.
Only the credential that reserved an upload can confirm or attach it. For anyone else the id answers
404 not_found.
target holds a type, the record's id, and for a deliverable the field:
target.type |
target.id |
Also needs | The Administrator needs |
|---|---|---|---|
milestone_deliverable |
The milestone | field: the form's File upload field |
The permission to complete on behalf |
registration_entity |
The registration | Nothing | The permission to edit applicants |
application_note |
The application note | Nothing | Always answers 404 not_found |
Only the applicant or verifier side of an application can attach to its notes, so a request naming an
application_note answers 404 not_found for an Administrator, as it does on the screen.
A file over 100 MB or over its target's own limit is refused with 413 payload_too_large, and a type
its target doesn't accept with 415 unsupported_media_type, both before anything is stored. A target
that is full or closed to files is refused with 409 invalid_state.
Refusals every write can give
A refusal's body is {"error": "<code>", "error_description": "<text>"}. Your system should switch
on error.
| Status | error |
Why |
|---|---|---|
400 |
invalid_request |
The body isn't a JSON object. |
400 |
idempotency_key_required |
There's no Idempotency-Key, or it's longer than 255 characters. |
403 |
insufficient_scope |
The credential doesn't hold the route's scope. The text names the scope. |
403 |
run_as_disabled |
The credential has no enabled Administrator to act as. |
403 |
forbidden |
The Administrator's permissions or data access rules don't allow this on this record. |
413 |
payload_too_large |
The request body is too large. |
415 |
unsupported_media_type |
The request body isn't application/json. |
422 |
idempotency_key_reused |
The key was already used for a different request. |
422 |
unknown_field |
The body has a field the route doesn't document. The text names the first one. |
422 |
validation_failed |
A value breaks the screen's own rules. fields names each failing field. |
See also
This page uses Backed's default terms, such as Funding Round and Application Template. An organisation may rename them, so read each as your organisation's own term if you've renamed it.
Was this helpful?