Skip to main content

Developers · Reference

Milestones

Milestones with their deliverables. 4 operations: parameters, request body and answers, with examples.

GET /api/v2/milestones

List milestones.

Every milestone of the Organisation (application, contract and standalone) in ascending id order, at most 50 a page, each with its deliverables entry. Every filter is ANDed; any other filters[…] key is 400.

  • Operation ID: listMilestones
  • Authentication: A Bearer token from a credential holding the read scope.
  • Also served at /funding_system/api/v2/milestones, the documented product’s alias path.
Example request
curl "https://your-org.backed.giving/api/v2/milestones" \
  -H "Authorization: Bearer $BACKED_TOKEN"

Parameters

Parameters of GET /api/v2/milestones
NameInRequiredTypeDescription
limitqueryNointegerResults per page (default 50, at most 50; a larger value is read as 50). Not an integer, or below 1: 400. Example: 50.
gtqueryNointegerReturn records with an id greater than this value. Page forward by passing the last id you received. Example: 1287.
ltqueryNointegerReturn records with an id less than this value.
filters[deliverables][value]queryNostringMatch deliverable input values; % is a wildcard for partial matching. Example: %Smith%.
filters[deliverables][tags][]queryNoarray of stringFilter by the internal_identifier of deliverable fields. Example: artist_name, song_title.
filters[deliverables][tag]queryNostringCombine a tag with filters[deliverables][value] to search within one field. Example: artist_name.
filters[ids][]queryNoarray of integerFilter by milestone ids. Example: 2210, 2211.

200 response

A JSON array of milestones.

Fields of Milestone
FieldTypeRequiredNotes
idintegerYes
created_atstringYes
updated_atstringYes
titlestringYes
contract_idinteger | nullYes
funding_amountstringYes
descriptionstring | nullYes
statestringYesOne of not_submitted, pending_review, submitted_to_verifier, submitted, resubmission_requested, approved, finance_approved.
paid_atstring | nullYes
decline_reasonstring | nullYes
due_datestringYes
contractobject | nullYes
lock_versionintegerYes
deliverables_template_entryobjectYes
Example 200 response
[
  {
    "id": 2210,
    "created_at": "2026-03-02T21:15:04.000Z",
    "updated_at": "2026-06-18T03:40:11.000Z",
    "title": "Final report",
    "contract_id": 640,
    "funding_amount": "4500.0",
    "description": "A report on the outcomes the project achieved.",
    "state": "submitted",
    "paid_at": null,
    "decline_reason": null,
    "due_date": "2027-06-30",
    "contract": {
      "id": 640,
      "title": "Funding agreement: Te Awa river restoration"
    },
    "lock_version": 2,
    "deliverables_template_entry": {
      "id": 9310,
      "created_at": "2026-03-02T21:15:04.000Z",
      "updated_at": "2026-06-18T03:40:11.000Z",
      "inputs": [
        {
          "id": 77001,
          "created_at": "2026-03-02T21:15:04.000Z",
          "updated_at": "2026-06-18T03:40:11.000Z",
          "value": "final-report.pdf",
          "file": {
            "filename": "final-report.pdf",
            "content_type": "application/pdf",
            "byte_size": 482113
          },
          "tahua_file_url": "https://your-org.backed.giving/api/v2/file_inputs/77001",
          "field": {
            "id": 950,
            "created_at": "2026-03-02T21:15:04.000Z",
            "updated_at": "2026-03-02T21:15:04.000Z",
            "internal_identifier": "report"
          }
        }
      ]
    }
  }
]

400 response

A query parameter is invalid, or a write’s body is not a JSON object.

Example 400 response
{
  "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.

Example 401 response
{
  "error": "invalid_token",
  "error_description": "The access token is invalid or has expired"
}

403 response

The request was sent over plain HTTP and was not processed.

Example 403 response
{
  "error": "https_required"
}

404 response

An unknown host or path, or an id that is unknown, deleted or of another Organisation.

Example 404 response
{
  "error": "not_found"
}

429 response

A rate limit was exceeded: 120 requests a minute per credential, 30 token requests a minute per client_id.

Headers: Retry-After.

Example 429 response
{
  "error": "rate_limited",
  "error_description": "Too many requests"
}

500 response

An unexpected server error.

Example 500 response
{
  "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.

Example 503 response
{
  "error": "temporarily_unavailable"
}

POST /api/v2/milestones

Create a milestone on a funding application.

Create Singular as the run-as Administrator (milestones.create). Needs the Contracts module.

  • Operation ID: createMilestone
  • Authentication: A Bearer token from a credential holding the write:post_award scope. Runs as the credential’s Administrator, and needs an Idempotency-Key header.
Example request
curl -X POST "https://your-org.backed.giving/api/v2/milestones" \
  -H "Authorization: Bearer $BACKED_TOKEN" \
  -H "Idempotency-Key: 7d1f0c2e-5b8a-4f3e-9c61-2a4b8e9d0f13" \
  -H "Content-Type: application/json" \
  -d '{"funding_application_id":"1287","title":"Progress report","due_date":"2026-12-15","amount":"5000.00","form_template_id":"61","description":"A six-month progress report."}'

Parameters

Parameters of POST /api/v2/milestones
NameInRequiredTypeDescription
Idempotency-KeyheaderYesstringA 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 MilestoneCreate schema. A milestone on one funding application, with its deliverables form. Ids are decimal strings and amount has two decimal places, as the product’s form takes them.

Fields of the MilestoneCreate body
FieldTypeRequiredNotes
funding_application_idstringYes
titlestringYes
due_datestringYes
amountstringYes
form_template_idstringYes
milestone_template_idstring | nullNo
descriptionstring | nullNo
reviewer_user_idsarray of stringNo
Example request body
{
  "funding_application_id": "1287",
  "title": "Progress report",
  "due_date": "2026-12-15",
  "amount": "5000.00",
  "form_template_id": "61",
  "description": "A six-month progress report."
}

201 response

The milestone as the single read answers it.

Headers: Idempotent-Replayed.

Fields of Milestone
FieldTypeRequiredNotes
idintegerYes
created_atstringYes
updated_atstringYes
titlestringYes
contract_idinteger | nullYes
funding_amountstringYes
descriptionstring | nullYes
statestringYesOne of not_submitted, pending_review, submitted_to_verifier, submitted, resubmission_requested, approved, finance_approved.
paid_atstring | nullYes
decline_reasonstring | nullYes
due_datestringYes
contractobject | nullYes
lock_versionintegerYes
deliverables_template_entryobjectYes
Example 201 response
{
  "id": 2210,
  "created_at": "2026-03-02T21:15:04.000Z",
  "updated_at": "2026-06-18T03:40:11.000Z",
  "title": "Final report",
  "contract_id": 640,
  "funding_amount": "4500.0",
  "description": "A report on the outcomes the project achieved.",
  "state": "submitted",
  "paid_at": null,
  "decline_reason": null,
  "due_date": "2027-06-30",
  "contract": {
    "id": 640,
    "title": "Funding agreement: Te Awa river restoration"
  },
  "lock_version": 2,
  "deliverables_template_entry": {
    "id": 9310,
    "created_at": "2026-03-02T21:15:04.000Z",
    "updated_at": "2026-06-18T03:40:11.000Z",
    "inputs": [
      {
        "id": 77001,
        "created_at": "2026-03-02T21:15:04.000Z",
        "updated_at": "2026-06-18T03:40:11.000Z",
        "value": "final-report.pdf",
        "file": {
          "filename": "final-report.pdf",
          "content_type": "application/pdf",
          "byte_size": 482113
        },
        "tahua_file_url": "https://your-org.backed.giving/api/v2/file_inputs/77001",
        "field": {
          "id": 950,
          "created_at": "2026-03-02T21:15:04.000Z",
          "updated_at": "2026-03-02T21:15:04.000Z",
          "internal_identifier": "report"
        }
      }
    ]
  }
}

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.

Example 400 response: idempotency_key_required
{
  "error": "idempotency_key_required",
  "error_description": "A write needs an Idempotency-Key header of 1 to 255 characters"
}
Example 400 response: invalid_request
{
  "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.

Example 401 response
{
  "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.

Example 403 response: https_required
{
  "error": "https_required"
}
Example 403 response: insufficient_scope
{
  "error": "insufficient_scope",
  "error_description": "This credential lacks the write:applications scope"
}
Example 403 response: run_as_disabled
{
  "error": "run_as_disabled",
  "error_description": "This credential has no enabled run-as Administrator"
}
Example 403 response: forbidden
{
  "error": "forbidden",
  "error_description": "The run-as Administrator may not do this to this record"
}

404 response

not_found: An unknown host or path, or an id that is unknown, deleted or of another Organisation. module_disabled: The Organisation has the module this route belongs to turned off.

Example 404 response: not_found
{
  "error": "not_found"
}
Example 404 response: module_disabled
{
  "error": "module_disabled",
  "error_description": "This module is not enabled for the Organisation"
}

409 response

The first request with this Idempotency-Key has not finished. Nothing was written; retry after the given seconds.

Headers: Retry-After.

Example 409 response
{
  "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"
}

413 response

The body, or the declared file, is larger than the limit that applies.

Example 413 response
{
  "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.

Example 415 response
{
  "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.

Example 422 response: idempotency_key_reused
{
  "error": "idempotency_key_reused",
  "error_description": "This Idempotency-Key was used for a different request"
}
Example 422 response: unknown_field
{
  "error": "unknown_field",
  "error_description": "bank_account_number is not a writable field"
}
Example 422 response: validation_failed
{
  "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.

Example 429 response
{
  "error": "rate_limited",
  "error_description": "Too many requests"
}

500 response

An unexpected server error.

Example 500 response
{
  "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.

Example 503 response
{
  "error": "temporarily_unavailable"
}

GET /api/v2/milestones/{id}

Get one milestone.

Exactly the list item, its deliverables entry included. Filters are ignored.

  • Operation ID: getMilestone
  • Authentication: A Bearer token from a credential holding the read scope.
  • Also served at /funding_system/api/v2/milestones/{id}, the documented product’s alias path.
Example request
curl "https://your-org.backed.giving/api/v2/milestones/42" \
  -H "Authorization: Bearer $BACKED_TOKEN"

Parameters

Parameters of GET /api/v2/milestones/{id}
NameInRequiredTypeDescription
idpathYesintegerThe record id. Anything but a positive integer is 404. Example: 42.

200 response

The milestone.

Fields of Milestone
FieldTypeRequiredNotes
idintegerYes
created_atstringYes
updated_atstringYes
titlestringYes
contract_idinteger | nullYes
funding_amountstringYes
descriptionstring | nullYes
statestringYesOne of not_submitted, pending_review, submitted_to_verifier, submitted, resubmission_requested, approved, finance_approved.
paid_atstring | nullYes
decline_reasonstring | nullYes
due_datestringYes
contractobject | nullYes
lock_versionintegerYes
deliverables_template_entryobjectYes
Example 200 response
{
  "id": 2210,
  "created_at": "2026-03-02T21:15:04.000Z",
  "updated_at": "2026-06-18T03:40:11.000Z",
  "title": "Final report",
  "contract_id": 640,
  "funding_amount": "4500.0",
  "description": "A report on the outcomes the project achieved.",
  "state": "submitted",
  "paid_at": null,
  "decline_reason": null,
  "due_date": "2027-06-30",
  "contract": {
    "id": 640,
    "title": "Funding agreement: Te Awa river restoration"
  },
  "lock_version": 2,
  "deliverables_template_entry": {
    "id": 9310,
    "created_at": "2026-03-02T21:15:04.000Z",
    "updated_at": "2026-06-18T03:40:11.000Z",
    "inputs": [
      {
        "id": 77001,
        "created_at": "2026-03-02T21:15:04.000Z",
        "updated_at": "2026-06-18T03:40:11.000Z",
        "value": "final-report.pdf",
        "file": {
          "filename": "final-report.pdf",
          "content_type": "application/pdf",
          "byte_size": 482113
        },
        "tahua_file_url": "https://your-org.backed.giving/api/v2/file_inputs/77001",
        "field": {
          "id": 950,
          "created_at": "2026-03-02T21:15:04.000Z",
          "updated_at": "2026-03-02T21:15:04.000Z",
          "internal_identifier": "report"
        }
      }
    ]
  }
}

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.

Example 401 response
{
  "error": "invalid_token",
  "error_description": "The access token is invalid or has expired"
}

403 response

The request was sent over plain HTTP and was not processed.

Example 403 response
{
  "error": "https_required"
}

404 response

An unknown host or path, or an id that is unknown, deleted or of another Organisation.

Example 404 response
{
  "error": "not_found"
}

429 response

A rate limit was exceeded: 120 requests a minute per credential, 30 token requests a minute per client_id.

Headers: Retry-After.

Example 429 response
{
  "error": "rate_limited",
  "error_description": "Too many requests"
}

500 response

An unexpected server error.

Example 500 response
{
  "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.

Example 503 response
{
  "error": "temporarily_unavailable"
}

POST /api/v2/milestones/{id}/approve

Approve a milestone.

Approve Milestone at its current version (milestones.approve), within the run-as Administrator’s Milestone Approval Limit. No body. Needs the Contracts module.

  • Operation ID: approveMilestone
  • Authentication: A Bearer token from a credential holding the write:post_award scope. Runs as the credential’s Administrator, and needs an Idempotency-Key header.
Example request
curl -X POST "https://your-org.backed.giving/api/v2/milestones/42/approve" \
  -H "Authorization: Bearer $BACKED_TOKEN" \
  -H "Idempotency-Key: 7d1f0c2e-5b8a-4f3e-9c61-2a4b8e9d0f13"

Parameters

Parameters of POST /api/v2/milestones/{id}/approve
NameInRequiredTypeDescription
idpathYesintegerThe record id. Anything but a positive integer is 404. Example: 42.
Idempotency-KeyheaderYesstringA 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 milestone as the single read answers it.

Headers: Idempotent-Replayed.

Fields of Milestone
FieldTypeRequiredNotes
idintegerYes
created_atstringYes
updated_atstringYes
titlestringYes
contract_idinteger | nullYes
funding_amountstringYes
descriptionstring | nullYes
statestringYesOne of not_submitted, pending_review, submitted_to_verifier, submitted, resubmission_requested, approved, finance_approved.
paid_atstring | nullYes
decline_reasonstring | nullYes
due_datestringYes
contractobject | nullYes
lock_versionintegerYes
deliverables_template_entryobjectYes
Example 200 response
{
  "id": 2210,
  "created_at": "2026-03-02T21:15:04.000Z",
  "updated_at": "2026-06-18T03:40:11.000Z",
  "title": "Final report",
  "contract_id": 640,
  "funding_amount": "4500.0",
  "description": "A report on the outcomes the project achieved.",
  "state": "submitted",
  "paid_at": null,
  "decline_reason": null,
  "due_date": "2027-06-30",
  "contract": {
    "id": 640,
    "title": "Funding agreement: Te Awa river restoration"
  },
  "lock_version": 2,
  "deliverables_template_entry": {
    "id": 9310,
    "created_at": "2026-03-02T21:15:04.000Z",
    "updated_at": "2026-06-18T03:40:11.000Z",
    "inputs": [
      {
        "id": 77001,
        "created_at": "2026-03-02T21:15:04.000Z",
        "updated_at": "2026-06-18T03:40:11.000Z",
        "value": "final-report.pdf",
        "file": {
          "filename": "final-report.pdf",
          "content_type": "application/pdf",
          "byte_size": 482113
        },
        "tahua_file_url": "https://your-org.backed.giving/api/v2/file_inputs/77001",
        "field": {
          "id": 950,
          "created_at": "2026-03-02T21:15:04.000Z",
          "updated_at": "2026-03-02T21:15:04.000Z",
          "internal_identifier": "report"
        }
      }
    ]
  }
}

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.

Example 400 response: idempotency_key_required
{
  "error": "idempotency_key_required",
  "error_description": "A write needs an Idempotency-Key header of 1 to 255 characters"
}
Example 400 response: invalid_request
{
  "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.

Example 401 response
{
  "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. approval_limit_exceeded: The amount is above the run-as Administrator’s approval limit.

Headers: WWW-Authenticate.

Example 403 response: https_required
{
  "error": "https_required"
}
Example 403 response: insufficient_scope
{
  "error": "insufficient_scope",
  "error_description": "This credential lacks the write:applications scope"
}
Example 403 response: run_as_disabled
{
  "error": "run_as_disabled",
  "error_description": "This credential has no enabled run-as Administrator"
}
Example 403 response: forbidden
{
  "error": "forbidden",
  "error_description": "The run-as Administrator may not do this to this record"
}
Example 403 response: approval_limit_exceeded
{
  "error": "approval_limit_exceeded",
  "error_description": "The amount is above the run-as Administrator’s approval limit"
}

404 response

not_found: An unknown host or path, or an id that is unknown, deleted or of another Organisation. module_disabled: The Organisation has the module this route belongs to turned off.

Example 404 response: not_found
{
  "error": "not_found"
}
Example 404 response: module_disabled
{
  "error": "module_disabled",
  "error_description": "This module is not enabled for the Organisation"
}

409 response

idempotency_in_progress: The first request with this Idempotency-Key has not finished. Nothing was written; retry after the given seconds. invalid_transition: The move is not open from where the record stands; current_status says where that is. invalid_state: The record is not in a state that admits this write.

Headers: Retry-After.

Example 409 response: idempotency_in_progress
{
  "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"
}
Example 409 response: invalid_transition
{
  "error": "invalid_transition",
  "error_description": "The record cannot make this move from approved",
  "current_status": "approved"
}
Example 409 response: invalid_state
{
  "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.

Example 413 response
{
  "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.

Example 415 response
{
  "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.

Example 422 response: idempotency_key_reused
{
  "error": "idempotency_key_reused",
  "error_description": "This Idempotency-Key was used for a different request"
}
Example 422 response: unknown_field
{
  "error": "unknown_field",
  "error_description": "bank_account_number is not a writable field"
}

429 response

A rate limit was exceeded: 120 requests a minute per credential, 30 token requests a minute per client_id.

Headers: Retry-After.

Example 429 response
{
  "error": "rate_limited",
  "error_description": "Too many requests"
}

500 response

An unexpected server error.

Example 500 response
{
  "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.

Example 503 response
{
  "error": "temporarily_unavailable"
}