# Backed developer documentation > The Backed API: a REST API through which an organisation’s own systems read and change its records in Backed, the grants platform, with signed webhooks and an OData reporting feed for Power BI and Tableau. Every page below is Markdown; each is also published as HTML at the same address without `.md`. The OpenAPI 3.1 document: https://backed.giving/developers/openapi.json. Each organisation's host serves the same document at /api/v2/openapi.json. --- Source: https://backed.giving/developers.md # Backed developer documentation What the Backed API is, a quick start from credential to first request, and a link to every guide, reference page, the event catalogue, the changelog and the OpenAPI document. The Backed API lets your organisation’s own systems, such as a finance, CRM or reporting system, read and change its records in Backed: funding rounds, applications, registrations, assessments, contracts, milestones, payments and files. It is a REST API with JSON bodies, served on your organisation’s own address, such as `https://your-org.backed.giving`. Signed webhooks tell your systems when something happens, and an OData feed loads your records into Power BI, Tableau and data warehouses. This site holds no organisation’s data: every address and record on it is an example. You need no account to read it. ## Quick start 1. An Administrator creates an API credential under `Settings > Profile and terms`, on the `API Access` tab, and gives your system its Client ID and Client Secret. 2. Your system exchanges the pair for a Bearer token at `POST /oauth/token`. 3. It sends the token on every request, in the `Authorization` header. Step 1: get a token: ```bash curl -X POST "https://your-org.backed.giving/oauth/token" \ -d grant_type=client_credentials \ -d client_id=your-client-id \ -d client_secret=your-client-secret ``` Step 2: make your first request: ```bash curl "https://your-org.backed.giving/api/v2/funding_rounds?limit=50" \ -H "Authorization: Bearer $BACKED_TOKEN" ``` The answer is a JSON array of your funding rounds, oldest first. Read the next page with `gt`: see [Pagination](https://backed.giving/developers/guides/pagination.md). Every write also needs an `Idempotency-Key`: see [Idempotency](https://backed.giving/developers/guides/idempotency.md). ## The OpenAPI document The whole API is described by one OpenAPI 3.1 document, which code generators and API tools such as Postman or Insomnia read. [Download openapi.json](https://backed.giving/developers/openapi.json). Each organisation’s own host serves the same document at `/api/v2/openapi.json`, with its own address as the server, such as `https://your-org.backed.giving/api/v2/openapi.json`. Neither needs a credential. ## Guides - [Authentication](https://backed.giving/developers/guides/authentication.md): Get a 5-day Bearer token from a credential with the OAuth 2.0 client-credentials grant, send it on every request, and choose the scopes a credential holds. - [Pagination](https://backed.giving/developers/guides/pagination.md): Read a long list 50 records at a time with the limit, gt and lt parameters, in ascending id order. - [Errors](https://backed.giving/developers/guides/errors.md): What every error code means, the status it comes with, and which ones are safe to retry. - [Idempotency](https://backed.giving/developers/guides/idempotency.md): Make every write safe to retry with an Idempotency-Key, and update a record at the version you read with If-Match. - [Webhook signature verification](https://backed.giving/developers/guides/webhook-signatures.md): Check the Backed-Signature header on every webhook delivery with HMAC-SHA256, in Node.js or Python, and answer in time. - [Connect Power BI](https://backed.giving/developers/guides/power-bi.md): Load the OData reporting feed into Power BI Desktop, with Basic authentication or a token query, and refresh it on a schedule. - [Connect Tableau](https://backed.giving/developers/guides/tableau.md): Connect Tableau Desktop to the OData reporting feed with the credential as a user name and password, and load it into other tools and warehouses. - [Versions and deprecation](https://backed.giving/developers/guides/versioning.md): Which version is current, why a breaking change only ever comes under a new version path, and how long a superseded version stays. ## Reference - [API reference](https://backed.giving/developers/reference.md): every operation, by resource. - [Authentication](https://backed.giving/developers/reference/authentication.md): Exchange a credential for an access token. 1 operation: parameters, request body and answers, with examples. - [Funding Rounds](https://backed.giving/developers/reference/funding-rounds.md): The Organisation’s funding rounds. 2 operations: parameters, request body and answers, with examples. - [Funding Applications](https://backed.giving/developers/reference/funding-applications.md): Applications, their entries, moves, notes and tags. 5 operations: parameters, request body and answers, with examples. - [Registration Entities](https://backed.giving/developers/reference/registration-entities.md): Applicant organisations and individuals. 5 operations: parameters, request body and answers, with examples. - [Assessment Rounds](https://backed.giving/developers/reference/assessment-rounds.md): Assessor assignments. 1 operation: parameters, request body and answers, with examples. - [Contracts](https://backed.giving/developers/reference/contracts.md): Contracts and their recorded statuses. 3 operations: parameters, request body and answers, with examples. - [Milestones](https://backed.giving/developers/reference/milestones.md): Milestones with their deliverables. 4 operations: parameters, request body and answers, with examples. - [Payments](https://backed.giving/developers/reference/payments.md): Payment batches. 1 operation: parameters, request body and answers, with examples. - [File Inputs](https://backed.giving/developers/reference/file-inputs.md): Downloads of files held by application and deliverable inputs. 1 operation: parameters, request body and answers, with examples. - [Files](https://backed.giving/developers/reference/files.md): The three-step file upload. 3 operations: parameters, request body and answers, with examples. - [Reference](https://backed.giving/developers/reference/reference.md): This document. 1 operation: parameters, request body and answers, with examples. ## Events and changelog - [Event catalogue](https://backed.giving/developers/events.md): every webhook event type and the payload. - [Changelog](https://backed.giving/developers/changelog.md): every API change, with its date. ## For AI assistants The site publishes itself for AI assistants too: [llms.txt](https://backed.giving/developers/llms.txt) lists every page with a line on what it holds, [llms-full.txt](https://backed.giving/developers/llms-full.txt) is every page in one file, and every page is also served as Markdown at its own address plus `.md`. --- Source: https://backed.giving/developers/guides/authentication.md # Authentication Get a 5-day Bearer token from a credential with the OAuth 2.0 client-credentials grant, send it on every request, and choose the scopes a credential holds. Every request to the API is made with an access token, and every token comes from an API credential. The API uses the OAuth 2.0 client-credentials grant: your system sends the credential’s Client ID and Client Secret to the token endpoint and gets back a Bearer token. ## Get a credential An Administrator of your organisation creates the credential under `Settings > Profile and terms`, on the `API Access` tab, and chooses its scopes. The Client Secret is shown once. See [API access](https://admin.backed.giving/help/integrations/api-access) in the Help Centre. ## Ask for a token Send the pair to `POST /oauth/token` on your organisation’s own address, form-encoded, with `grant_type=client_credentials`. You may send the Client ID and Client Secret in the form body, or with HTTP Basic authentication instead. Request: ```bash curl -X POST "https://your-org.backed.giving/oauth/token" \ -d grant_type=client_credentials \ -d client_id=your-client-id \ -d client_secret=your-client-secret # or, with HTTP Basic authentication: curl -X POST "https://your-org.backed.giving/oauth/token" \ -u your-client-id:your-client-secret \ -d grant_type=client_credentials ``` Response 200: ```json { "access_token": "q3Vx9LmT2sKp4Rz7Wn1YeB8cD5fG6hJ0kN_aQ-uIoPr", "token_type": "Bearer", "expires_in": 432000, "created_at": 1762650000 } ``` - `expires_in` is 432000 seconds: a token lasts 5 days. - There is no refresh token. Each successful request issues a new token; when a request answers `401 invalid_token`, ask for a new one. - The answer carries `Cache-Control: no-store`. Keep the token as you keep the secret. - The token endpoint takes 30 requests a minute per `client_id`, counted before the secret is checked. Ask for a token when you need one, not before every call. ## Send the token Send the token on every request in the `Authorization` header: ```bash curl "https://your-org.backed.giving/api/v2/funding_rounds" \ -H "Authorization: Bearer $BACKED_TOKEN" ``` Node.js 18 or later: ```js const host = 'https://your-org.backed.giving'; async function token() { const response = await fetch(`${host}/oauth/token`, { method: 'POST', body: new URLSearchParams({ grant_type: 'client_credentials', client_id: process.env.BACKED_CLIENT_ID, client_secret: process.env.BACKED_CLIENT_SECRET, }), }); if (!response.ok) throw new Error(`token: ${response.status}`); return (await response.json()).access_token; } const rounds = await fetch(`${host}/api/v2/funding_rounds`, { headers: { Authorization: `Bearer ${await token()}` }, }).then((response) => response.json()); ``` A missing, invalid, expired or revoked token answers `401` with `invalid_token` and `WWW-Authenticate: Bearer error="invalid_token"`. Every request must use HTTPS: one sent over plain HTTP answers `403 https_required` and is not processed. See [Errors](https://backed.giving/developers/guides/errors.md). ## Scopes A credential holds the scopes an Administrator ticked when they created or edited it. Every credential holds `read`. A write needs its own write scope, and a credential without it is answered `403 insufficient_scope`. Scopes: | Scope | What it allows | | --- | --- | | `read` | Read every /api/v2 resource. Every credential holds it. | | `read:reporting_feed` | Read the OData v4 reporting feed at /odata/v4. | | `write:registrations` | Create and edit registration entities and invite their users. | | `write:applications` | Move funding applications, add notes and replace tags. | | `write:assessments` | Assign Assessors to applications in an assessment round. | | `write:post_award` | Create and approve milestones, mark payment batches paid, record contracts. | | `write:files` | Upload files to deliverables, application notes and registration entities. | ## Who a write acts as A credential with a write scope acts as one Administrator, chosen under `Acts as`. Every write runs with that person’s permissions, Data Access Rules and approval limits, so it can do nothing they could not, and the audit log records the credential and the Administrator together. A credential with no enabled Administrator to act as is answered `403 run_as_disabled` on every write. The write routes are described in [Write API: registrations, applications, notes, tags and assignments](https://admin.backed.giving/help/integrations/api-c02-write-api-part-one-registrations-applications-no) and [Write API: milestones, payments, contracts and files](https://admin.backed.giving/help/integrations/api-c03-write-api-part-two-milestones-payments-contracts). ## Rate limits A credential may make 120 requests a minute, the API and the OData feed together. Past that, the API answers `429 rate_limited` with a `Retry-After` header: wait that many seconds, then carry on. --- Source: https://backed.giving/developers/guides/pagination.md # Pagination Read a long list 50 records at a time with the limit, gt and lt parameters, in ascending id order. Every list (`GET /api/v2/funding_rounds`, `/funding_applications`, `/contracts`, `/registration_entities`, `/milestones`) answers a bare JSON array of records in ascending `id` order, at most 50 at a time. There is no page number and no cursor: you page by id. ## The parameters Paging parameters: | Parameter | What it does | | --- | --- | | `limit` | Records per page. Default 50, at most 50: a larger value is read as 50. Not an integer, or below 1, answers `400 invalid_request` with `limit is invalid`. | | `gt` | Only records whose id is greater than this. Pass the last id you received to read the next page. | | `lt` | Only records whose id is less than this. | Each parameter is an integer, sent once. A parameter that is not an integer, or is sent twice, answers `400 invalid_request` naming it. A `gt` or `lt` that no id can meet is an empty page, not an error. A parameter a route does not document is ignored, except a `filters[…]` key the milestones list does not know, which is `400`. ## Read every page 1. Ask for the first page with no `gt`. 2. Keep the `id` of the last record on the page. 3. Ask again with `gt` set to that id. 4. Stop when a page holds fewer records than the `limit`: there are no more. The first two pages: ```bash curl "https://your-org.backed.giving/api/v2/funding_applications?limit=50" -H "Authorization: Bearer $BACKED_TOKEN" curl "https://your-org.backed.giving/api/v2/funding_applications?limit=50>=1287" -H "Authorization: Bearer $BACKED_TOKEN" ``` Node.js: ```js async function* everyRecord(path, token) { let after = null; for (;;) { const query = new URLSearchParams({ limit: '50' }); if (after !== null) query.set('gt', String(after)); const response = await fetch(`https://your-org.backed.giving${path}?${query}`, { headers: { Authorization: `Bearer ${token}` }, }); if (!response.ok) throw new Error(`${path}: ${response.status}`); const page = await response.json(); yield* page; if (page.length < 50) return; after = page[page.length - 1].id; } } for await (const application of everyRecord('/api/v2/funding_applications', token)) { console.log(application.id, application.state); } ``` Filters, such as `submitted=1` on funding applications or `filters[ids][]` on milestones, narrow the list before it is paged: keep sending them with every page. > The OData reporting feed pages differently: up to 1000 rows a page, each ending with \`@odata.nextLink\` while more remain. See [Power BI](https://backed.giving/developers/guides/power-bi.md). --- Source: https://backed.giving/developers/guides/errors.md # Errors What every error code means, the status it comes with, and which ones are safe to retry. A refused request answers a 4xx or 5xx status with a small JSON body. `error` is a machine code your system switches on; `error_description`, where present, is a fixed English sentence for the person reading a log. A refusal: ```json { "error": "invalid_request", "error_description": "limit is invalid" } ``` - `invalid_transition` also carries `current_status`, the state the record is in. - `validation_failed` also carries `fields`, the name of each field the product’s own form would refuse. - A write refused for any reason has changed nothing. ## Every code Error codes: | Status | Code | When | | --- | --- | --- | | 400 | `idempotency_key_required` | The write has no Idempotency-Key header of 1 to 255 characters. | | 400 | `invalid_request` | A query parameter is invalid, or a write’s body is not a JSON object. | | 400 | `unsupported_grant_type` | The grant type is not client\_credentials. | | 401 | `invalid_client` | Client authentication failed: an unknown client\_id, a wrong secret, a revoked credential or another Organisation’s, or API access is not Full. | | 401 | `invalid_token` | The token is missing, invalid, expired or revoked, of another Organisation, or API access is not Full. Ask for a new token. | | 403 | `approval_limit_exceeded` | The amount is above the run-as Administrator’s approval limit. | | 403 | `forbidden` | The run-as Administrator lacks the permission, or the record is outside their Data Access Rules. | | 403 | `https_required` | The request was sent over plain HTTP and was not processed. | | 403 | `insufficient_scope` | The credential does not hold the scope the route needs. | | 403 | `run_as_disabled` | The credential has no run-as Administrator, or that person is no longer one. | | 404 | `module_disabled` | The Organisation has the module this route belongs to turned off. | | 404 | `not_found` | An unknown host or path, or an id that is unknown, deleted or of another Organisation. | | 405 | `method_not_allowed` | The method is not served on this path. `Allow` lists what is: `GET, HEAD` on a read path, `POST` on /oauth/token, the write’s own method on a write path. | | 409 | `conflict_declared` | The Assessor has declared a conflict of interest with an application. | | 409 | `file_not_scanned` | The file has not finished its malware scan; try the attach again shortly. | | 409 | `idempotency_in_progress` | The first request with this Idempotency-Key has not finished. Nothing was written; retry after the given seconds. | | 409 | `invalid_state` | The record is not in a state that admits this write. | | 409 | `invalid_transition` | The move is not open from where the record stands; `current_status` says where that is. | | 412 | `precondition_failed` | If-Match is missing, malformed or not the record’s current lock\_version, or the record changed under the write. Nothing was written. | | 413 | `payload_too_large` | The body, or the declared file, is larger than the limit that applies. | | 415 | `unsupported_media_type` | The body is not application/json, or the target does not accept the file’s content type. | | 422 | `file_infected` | The file failed its malware scan and was not attached. | | 422 | `idempotency_key_reused` | The Idempotency-Key was used in the last 24 hours for a different method, path or body. | | 422 | `unknown_field` | The body has a property the route does not document; the first is named. | | 422 | `unknown_tag` | A tag is not one of the Organisation’s tags. None is created. | | 422 | `validation_failed` | Body values the product’s own form would refuse; `fields` names each. | | 429 | `rate_limited` | A rate limit was exceeded: 120 requests a minute per credential, 30 token requests a minute per client\_id. | | 500 | `server_error` | An unexpected server error. | | 503 | `temporarily_unavailable` | The Organisation’s API access cannot be determined, or a dependency is unavailable. Retry after the given seconds. | ## Which to retry - `429 rate_limited`, `503 temporarily_unavailable` and `409 idempotency_in_progress` carry `Retry-After`: wait that many seconds and send the same request again, with the same `Idempotency-Key` for a write. - `500 server_error`: retry a read later. Retry a write with the same `Idempotency-Key`, so that a write that did happen is answered again rather than made twice. See the idempotency guide. - `401 invalid_token`: ask for a new token, then retry. - Anything else is about the request itself: change it before you send it again. The OData reporting feed answers its own query errors in the OData error body: `400 bad_request`, `404 not_found`, `410 delta_token_expired`, `501 not_implemented`. See [Power BI](https://backed.giving/developers/guides/power-bi.md) and [Idempotency](https://backed.giving/developers/guides/idempotency.md). --- Source: https://backed.giving/developers/guides/idempotency.md # Idempotency Make every write safe to retry with an Idempotency-Key, and update a record at the version you read with If-Match. A connection can drop after the API has made a change but before your system hears the answer. An idempotency key lets your system send the request again without making the change twice. ## Send a key with every write Every request that creates or changes something (`POST`, `PUT` or `PATCH`) must carry an `Idempotency-Key` header: a value your system makes up, 1 to 255 characters, unique to that one change. A UUID is a good key. A write with no key, an empty one or a longer one answers `400 idempotency_key_required` and changes nothing. A write with its key: ```bash curl -X POST "https://your-org.backed.giving/api/v2/funding_applications/1287/notes" \ -H "Authorization: Bearer $BACKED_TOKEN" \ -H "Idempotency-Key: 7d1f0c2e-5b8a-4f3e-9c61-2a4b8e9d0f13" \ -H "Content-Type: application/json" \ -d '{"body":"Budget checked against the supplier quote."}' ``` ## What a retry gets What the API does with a key: | You send | The API answers | | --- | --- | | The same key, method, path and body within 24 hours | The first request’s status and body again, with `Idempotent-Replayed: true`. Nothing is written a second time. | | The same key with a different method, path or body | `422 idempotency_key_reused`. Nothing is written. | | The same key while the first request is still running | `409 idempotency_in_progress` with `Retry-After`. Nothing is written: retry after that many seconds. | - The path includes its query string. The body is compared as JSON, so the order of its properties does not matter. - A key belongs to the credential that sent it: two credentials never share one. - A refused write keeps nothing against its key, so you can send the same key again once you have fixed the request. - After 24 hours the key is forgotten, and the same key is a new request. > If a key keeps answering `409 idempotency_in_progress`, its first request stopped part-way. Check whether the record exists before you write again with a new key. Retrying a write safely (Node.js): ```js import { randomUUID } from 'node:crypto'; async function write(method, path, body, token) { const key = randomUUID(); // one key per change, reused on every retry of it for (let attempt = 1; ; attempt += 1) { const response = await fetch(`https://your-org.backed.giving${path}`, { method, headers: { Authorization: `Bearer ${token}`, 'Idempotency-Key': key, 'Content-Type': 'application/json', }, body: JSON.stringify(body), }).catch(() => null); // a dropped connection: retry with the same key const retryable = response === null || [409, 429, 500, 503].includes(response.status); if (!retryable || attempt === 5) return response; const wait = Number(response?.headers.get('Retry-After') ?? 2 ** attempt); await new Promise((resolve) => setTimeout(resolve, wait * 1000)); } } ``` ## If-Match: write at the version you read A `PATCH` that updates a record must carry an `If-Match` header holding the record’s `lock_version` as your system last read it, bare or quoted (`4` or `"4"`). If someone changed the record since, the update answers `412 precondition_failed` and nothing changes: read the record again, then retry with its new `lock_version` and a new key. ```bash curl -X PATCH "https://your-org.backed.giving/api/v2/registration_entities/311" \ -H "Authorization: Bearer $BACKED_TOKEN" \ -H "Idempotency-Key: 0b5e6f1a-93d2-4c47-8a1e-5f2d7c9b3e60" \ -H "If-Match: 4" \ -H "Content-Type: application/json" \ -d '{"founded_on":"2014-03-01"}' ``` --- Source: https://backed.giving/developers/guides/webhook-signatures.md # Webhook signature verification Check the Backed-Signature header on every webhook delivery with HMAC-SHA256, in Node.js or Python, and answer in time. Backed sends each event an endpoint is subscribed to as an HTTPS `POST` with a JSON body. Anyone could send a request to your endpoint’s address, so check the signature before you trust the body. Set endpoints up on the `Webhooks` tab: see [Webhooks](https://admin.backed.giving/help/integrations/webhooks) in the Help Centre. Every event type is in the [Event catalogue](https://backed.giving/developers/events.md). ## The headers Delivery headers: | Header | What it holds | | --- | --- | | `Backed-Signature` | `t=,v1=`: the time it was signed and the hex HMAC-SHA256 of it. For 24 hours after the secret is rolled there are two `v1=` values, one for each secret. | | `Backed-Event-Id` | The event’s id, the body’s `id`. The same on every retry and replay: de-duplicate on it. | | `Backed-Event-Type` | The event type, the body’s `type`. | ## Check the signature 1. Read `t` and every `v1=` value from the `Backed-Signature` header. 2. Build the signed string: `t`, a full stop, then the raw request body exactly as it arrived. Parse nothing first: re-serialised JSON is not the same bytes. 3. Compute the HMAC-SHA256 of that string, keyed with your endpoint’s signing secret (the whole value, `whsec_` included), as lowercase hex. 4. Compare it with each `v1=` value in constant time. One match is enough. 5. Reject the delivery if `t` is more than five minutes old, so a captured delivery cannot be replayed later. Node.js: ```js import { createHmac, timingSafeEqual } from 'node:crypto'; import express from 'express'; /** * Whether a delivery really came from Backed. `header` is the Backed-Signature header, * `rawBody` the request body exactly as received (a Buffer or string, before any JSON parsing), * `secret` the endpoint's signing secret, whsec_ included. */ export function verifyBackedSignature(header, rawBody, secret, toleranceSeconds = 300) { let t = null; const signatures = []; for (const part of header.split(',')) { const [key, value] = part.trim().split('=', 2); if (key === 't' && /^[0-9]{1,12}$/.test(value)) t = Number(value); else if (key === 'v1' && /^[0-9a-f]{64}$/.test(value)) signatures.push(value); } if (t === null || signatures.length === 0) return false; if (Math.floor(Date.now() / 1000) - t > toleranceSeconds) return false; const expected = createHmac('sha256', secret).update(`${t}.`).update(rawBody).digest(); return signatures.some((signature) => { const given = Buffer.from(signature, 'hex'); return given.length === expected.length && timingSafeEqual(given, expected); }); } /** * The route's handler. `enqueue(event)` is yours: store the event durably (a queue or a table, * de-duplicated on event.id, the Backed-Event-Id header) and resolve once it is stored. * Do the slow work from there, after answering, so the answer comes within 10 seconds. */ export function backedWebhookHandler(secret, enqueue) { return async (req, res) => { const header = req.get('Backed-Signature') ?? ''; if (!verifyBackedSignature(header, req.body, secret)) return res.sendStatus(400); try { await enqueue(JSON.parse(req.body.toString('utf8'))); } catch { return res.sendStatus(500); // not stored: a non-2xx answer makes Backed retry it } return res.sendStatus(204); // stored: a 2xx is final, Backed will not send it again }; } // Express: read the raw body for this route, not parsed JSON. const app = express(); app.post( '/backed/webhooks', express.raw({ type: 'application/json' }), backedWebhookHandler(process.env.BACKED_WEBHOOK_SECRET, enqueue), ); ``` Python: ```python import hashlib import hmac import os import re import time from flask import Flask, request def verify_backed_signature(header: str, raw_body: bytes, secret: str, tolerance: int = 300) -> bool: """Whether a delivery really came from Backed. header is the Backed-Signature header, raw_body the request body exactly as received, secret the endpoint's signing secret, whsec_ included. """ t = None signatures = [] for part in header.split(","): key, _, value = part.strip().partition("=") # ASCII only: str.isdigit() accepts "²", and a non-ASCII signature makes compare_digest raise. if key == "t" and re.fullmatch(r"[0-9]{1,12}", value): t = int(value) elif key == "v1" and re.fullmatch(r"[0-9a-f]{64}", value): signatures.append(value) if t is None or not signatures: return False if int(time.time()) - t > tolerance: return False signed = f"{t}.".encode() + raw_body expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest() return any(hmac.compare_digest(expected, signature) for signature in signatures) # Flask: request.get_data() is the raw body. app = Flask(__name__) @app.post("/backed/webhooks") def backed_webhook(): header = request.headers.get("Backed-Signature", "") if not verify_backed_signature(header, request.get_data(), os.environ["BACKED_WEBHOOK_SECRET"]): return "", 400 event = request.get_json() try: # Yours: store the event durably (a queue or a table, de-duplicated on event["id"]), # returning once it is stored. Do the slow work from there, after answering. enqueue(event) except Exception: return "", 500 # not stored: a non-2xx answer makes Backed retry it return "", 204 # stored: a 2xx is final, Backed will not send it again ``` ## Answer in time - Answer a 2xx status only once the event is safely stored, such as on a queue or in a table, and within 10 seconds: store it, answer, then do the slow work from the store. A 2xx is final: Backed treats the delivery as delivered and never sends it again, so an event you acknowledge and then lose is lost. - If you cannot store the event, answer `500` (or any non-2xx status) so that Backed retries it. - Anything else, or no answer within 10 seconds, is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 12 hours and 24 hours. After the last it is `Failed`, and is sent again only if someone replays it. - An endpoint that fails 50 deliveries in a row is disabled, and the Administrators who manage integrations are emailed. - Deliveries about one record arrive in the order they happened. - A retry or a replay carries the same event id, so a delivery may reach you more than once: handle each id once. ## What the body holds The body names the event and the record it is about, with the record’s address in the API. It never carries form answers, bank details or contact details: read the record through the API, with the endpoint’s credential, for those. The payload’s shape is on the [Event catalogue](https://backed.giving/developers/events.md) page. `Send Test` on the endpoint’s page sends a signed `webhook.test` event, so you can check your verification before a real event arrives. --- Source: https://backed.giving/developers/guides/power-bi.md # Connect Power BI Load the OData reporting feed into Power BI Desktop, with Basic authentication or a token query, and refresh it on a schedule. Power BI reads your organisation’s records through the OData reporting feed, the same records your Backed reports read, and only your own organisation’s. ## Before you start - A credential with the `read:reporting_feed` scope (\`Read reporting feed\` on the \`API Access\` tab). Only an Administrator who can generate reports can give it. Without it the feed answers \`403 insufficient\_scope\`. - Your organisation’s address plus `/odata/v4/`, such as `https://your-org.backed.giving/odata/v4/`. The feed is OData v4. `/odata/v4/` lists its entity sets and `/odata/v4/$metadata` describes every column. The entity sets are `Applications`, `Applicants`, `RegistrationEntities`, `FundingRounds`, `Assessments`, `Decisions`, `Contracts`, `Milestones`, `Payments`, `Funds`. The OpenAPI document points to the feed (`x-odata-feed`) but does not describe it: `$metadata` does. ## Signing in A tool that can only ask for a user name and password signs in to the feed with the credential itself: the Client ID as the user name and the Client Secret as the password. The pair is checked on every request, so there is no token to renew. Only the feed takes a user name and password; the rest of the API takes a token alone. A tool that can send headers may instead send `Authorization: Bearer `, with a token from `POST /oauth/token`. ## With the OData feed connector 1. In Power BI Desktop, select `Get data`, then `OData feed`. 2. Under `URL`, enter `https://your-org.backed.giving/odata/v4/` with your organisation’s address, and select `OK`. 3. When Power BI asks how to connect, choose `Basic`. Enter the Client ID as the `User name` and the Client Secret as the `Password`, and select `Connect`. 4. In the `Navigator`, tick the entity sets you want, then select `Load` or `Transform Data`. ## With a token query If you would rather the feed see a token, start from `Get data`, `Blank query`, open `Advanced Editor`, paste this query, and replace the three settings at the top. If Power BI asks how to connect to your organisation’s address, choose `Anonymous`: the query sends its own token. Power Query (M): ```powerquery let // Your organisation's address and your credential's pair. BaseUrl = "https://your-org.backed.giving", ClientId = "your-client-id", ClientSecret = "your-client-secret", // A token, asked for at every refresh (each lasts 5 days). Token = Json.Document( Web.Contents(BaseUrl, [ RelativePath = "oauth/token", Headers = [#"Content-Type" = "application/x-www-form-urlencoded"], Content = Text.ToBinary(Uri.BuildQueryString([ grant_type = "client_credentials", client_id = ClientId, client_secret = ClientSecret ])) ]) )[access_token], // The feed, with the token on every request it makes. Feed = OData.Feed( BaseUrl & "/odata/v4/", null, [Headers = [Authorization = "Bearer " & Token], Implementation = "2.0"] ) in Feed ``` ## Refresh on a schedule 1. In Power BI Desktop, select `Publish` and choose a workspace. 2. In the Power BI service, open the semantic model’s `Settings`. 3. Under `Data source credentials`, select `Edit credentials`: `Basic` with the pair for the connector, `Anonymous` for the token query. 4. Under `Refresh`, turn on `Configure a refresh schedule`, choose the times, and select `Apply`. With `Basic`, every refresh signs in with the pair; the token query asks for a new token at every refresh. Either way the 5-day token limit never stops a refresh. A refresh that fails with `401` means the credential was deleted or its secret is wrong; with `403`, it has lost `read:reporting_feed`. ## Paging and query options - A page holds up to 1000 rows and ends with `@odata.nextLink` while more remain. Power BI follows it for you. - Filters and column choices you make in Power Query are sent as `$filter` and `$select`, so only what you keep is read. `$orderby`, `$top` and `$count` are supported too. - A step the feed cannot answer, such as `Remove Top Rows` (sent as `$skip`) or `Begins With` (sent as `startswith`), fails with `501 not_implemented`. Put it after a `Table.Buffer` step, so Power BI applies it to rows it has already read. Every column, every query option and the change-tracking read for warehouse loaders are in [OData feed for BI tools and data warehouses](https://admin.backed.giving/help/integrations/odata-feed). Tableau is in [Connect Tableau](https://backed.giving/developers/guides/tableau.md). --- Source: https://backed.giving/developers/guides/tableau.md # Connect Tableau Connect Tableau Desktop to the OData reporting feed with the credential as a user name and password, and load it into other tools and warehouses. Tableau reads your organisation’s records through the OData reporting feed, the same records your Backed reports read, and only your own organisation’s. ## Before you start - A credential with the `read:reporting_feed` scope (\`Read reporting feed\` on the \`API Access\` tab). Only an Administrator who can generate reports can give it. Without it the feed answers \`403 insufficient\_scope\`. - Your organisation’s address plus `/odata/v4/`, such as `https://your-org.backed.giving/odata/v4/`. The feed is OData v4. `/odata/v4/` lists its entity sets and `/odata/v4/$metadata` describes every column. The entity sets are `Applications`, `Applicants`, `RegistrationEntities`, `FundingRounds`, `Assessments`, `Decisions`, `Contracts`, `Milestones`, `Payments`, `Funds`. The OpenAPI document points to the feed (`x-odata-feed`) but does not describe it: `$metadata` does. ## Signing in A tool that can only ask for a user name and password signs in to the feed with the credential itself: the Client ID as the user name and the Client Secret as the password. The pair is checked on every request, so there is no token to renew. Only the feed takes a user name and password; the rest of the API takes a token alone. A tool that can send headers may instead send `Authorization: Bearer `, with a token from `POST /oauth/token`. ## With the OData connector 1. In Tableau Desktop, under `Connect`, select `To a Server`, then `OData`. 2. Under `Server`, enter `https://your-org.backed.giving/odata/v4/` with your organisation’s address. 3. Under `Authentication`, choose `Username and Password`. Enter the Client ID as the `Username` and the Client Secret as the `Password`, and select `Sign In`. 4. Pick the entity set you want, such as `Applications`, and build your view. Add another OData connection for each further entity set. ## Other tools and data warehouses Any tool that can send an `Authorization` header can read the feed, with a token or with the pair as Basic authentication. A loader that runs on a schedule should ask for a new token at each run. ## Read only what changed 1. Read an entity set in full with the header `Prefer: odata.track-changes`, following every `@odata.nextLink`. Leave out `$filter`: delta links are issued for unfiltered reads only. 2. The last page ends with `@odata.deltaLink`. Keep that address. 3. Next time, read the `@odata.deltaLink`. It returns the rows changed since, and its last page ends with a new `@odata.deltaLink`. 4. A delta link works only for the credential it was issued to, for 30 days. An older or unknown one answers `410 delta_token_expired`: read the entity set in full again. Start change tracking: ```bash curl "https://your-org.backed.giving/odata/v4/Payments" \ -H "Authorization: Bearer $BACKED_TOKEN" \ -H "Prefer: odata.track-changes" ``` Deleted rows, Field Tag columns and value formats are in [OData feed for BI tools and data warehouses](https://admin.backed.giving/help/integrations/odata-feed). Power BI is in [Connect Power BI](https://backed.giving/developers/guides/power-bi.md). --- Source: https://backed.giving/developers/guides/versioning.md # Versions and deprecation Which version is current, why a breaking change only ever comes under a new version path, and how long a superseded version stays. Every request goes to a path under a version, such as `/api/v2/`. The versions served today: API versions: | Version | Status | Deprecated | Removed | | --- | --- | --- | --- | | `/api/v2/` | Current | — | — | Any other version path, such as `/api/v1/`, answers `404` with `{"error":"not_found"}`. The token endpoint, `/oauth/token`, sits outside the versions. ## Breaking changes A change that could break a system already connected, such as removing or renaming a field, a route or a format, is published only under a new version path. The version you use keeps working as it is. A change that only adds, such as each change in the Changelog so far, is made to the current version. ## When a version is superseded - The previous version is deprecated on the day its successor is published, and stays available for at least 12 months after that before it is removed. - The Administrators of every organisation with an API credential are emailed when the deprecation starts, and again 3 months before removal. - Every response from a deprecated version carries a `Deprecation` header with the date its deprecation started, a `Sunset` header with the date it will be removed, and a `Link` header pointing to the policy. A response from the current version carries neither. Every change, with its date, is in the [Changelog](https://backed.giving/developers/changelog.md). The policy is also in the Help Centre: [Versions and deprecation](https://admin.backed.giving/help/integrations/api-access#versions-and-deprecation). The current version’s OpenAPI document is at `/api/v2/openapi.json` on your organisation’s address. --- Source: https://backed.giving/developers/reference.md # API reference Every operation of the Backed API, read and write, by resource, with a link to each one’s full reference. Every operation of the API, by resource, generated from the [OpenAPI document](https://backed.giving/developers/openapi.json). Paths are on your organisation’s own address. Lists answer at most `50` records a page; every read is also served under the documented product’s `/funding_system/api/v2` alias; any method a path does not serve answers `405 method_not_allowed`. ## Authentication Exchange a credential for an access token. [Read the Authentication reference](https://backed.giving/developers/reference/authentication.md). - [POST /oauth/token](https://backed.giving/developers/reference/authentication.md#issueOAuthToken) — Obtain an access token with the OAuth 2.0 client-credentials grant. ## Funding Rounds The Organisation’s funding rounds. [Read the Funding Rounds reference](https://backed.giving/developers/reference/funding-rounds.md). - [GET /api/v2/funding\_rounds](https://backed.giving/developers/reference/funding-rounds.md#listFundingRounds) — List funding rounds. - [GET /api/v2/funding\_rounds/{id}](https://backed.giving/developers/reference/funding-rounds.md#getFundingRound) — Get one funding round. ## Funding Applications Applications, their entries, moves, notes and tags. [Read the Funding Applications reference](https://backed.giving/developers/reference/funding-applications.md). - [GET /api/v2/funding\_applications](https://backed.giving/developers/reference/funding-applications.md#listFundingApplications) — List funding applications. - [GET /api/v2/funding\_applications/{id}](https://backed.giving/developers/reference/funding-applications.md#getFundingApplication) — Get one funding application with its form entry. - [POST /api/v2/funding\_applications/{id}/notes](https://backed.giving/developers/reference/funding-applications.md#createApplicationNote) — Add a note to a funding application. - [PUT /api/v2/funding\_applications/{id}/tags](https://backed.giving/developers/reference/funding-applications.md#replaceApplicationTags) — Replace a funding application’s tags. - [POST /api/v2/funding\_applications/{id}/transitions](https://backed.giving/developers/reference/funding-applications.md#transitionFundingApplication) — Move a funding application. ## Registration Entities Applicant organisations and individuals. [Read the Registration Entities reference](https://backed.giving/developers/reference/registration-entities.md). - [GET /api/v2/registration\_entities](https://backed.giving/developers/reference/registration-entities.md#listRegistrationEntities) — List registration entities. - [POST /api/v2/registration\_entities](https://backed.giving/developers/reference/registration-entities.md#createRegistrationEntity) — Add an applicant: a person and their registration entity. - [GET /api/v2/registration\_entities/{id}](https://backed.giving/developers/reference/registration-entities.md#getRegistrationEntity) — Get one registration entity with its users. - [PATCH /api/v2/registration\_entities/{id}](https://backed.giving/developers/reference/registration-entities.md#updateRegistrationEntity) — Edit an organisation’s Founded date and stage override. - [POST /api/v2/registration\_entities/{id}/users](https://backed.giving/developers/reference/registration-entities.md#addRegistrationEntityUser) — Invite a user to a registration entity. ## Assessment Rounds Assessor assignments. [Read the Assessment Rounds reference](https://backed.giving/developers/reference/assessment-rounds.md). - [POST /api/v2/assessment\_rounds/{id}/assignments](https://backed.giving/developers/reference/assessment-rounds.md#createAssessorAssignment) — Assign an Assessor to applications in an assessment round. ## Contracts Contracts and their recorded statuses. [Read the Contracts reference](https://backed.giving/developers/reference/contracts.md). - [GET /api/v2/contracts](https://backed.giving/developers/reference/contracts.md#listContracts) — List contracts. - [GET /api/v2/contracts/{id}](https://backed.giving/developers/reference/contracts.md#getContract) — Get one contract. - [POST /api/v2/contracts/{id}/status](https://backed.giving/developers/reference/contracts.md#recordContractStatus) — Record a contract executed or terminated. ## Milestones Milestones with their deliverables. [Read the Milestones reference](https://backed.giving/developers/reference/milestones.md). - [GET /api/v2/milestones](https://backed.giving/developers/reference/milestones.md#listMilestones) — List milestones. - [POST /api/v2/milestones](https://backed.giving/developers/reference/milestones.md#createMilestone) — Create a milestone on a funding application. - [GET /api/v2/milestones/{id}](https://backed.giving/developers/reference/milestones.md#getMilestone) — Get one milestone. - [POST /api/v2/milestones/{id}/approve](https://backed.giving/developers/reference/milestones.md#approveMilestone) — Approve a milestone. ## Payments Payment batches. [Read the Payments reference](https://backed.giving/developers/reference/payments.md). - [POST /api/v2/payments/{id}/mark\_paid](https://backed.giving/developers/reference/payments.md#markPaymentPaid) — Mark a payment batch paid. ## File Inputs Downloads of files held by application and deliverable inputs. [Read the File Inputs reference](https://backed.giving/developers/reference/file-inputs.md). - [GET /api/v2/file\_inputs/{id}](https://backed.giving/developers/reference/file-inputs.md#downloadFileInput) — Download the file an input holds. ## Files The three-step file upload. [Read the Files reference](https://backed.giving/developers/reference/files.md). - [POST /api/v2/files](https://backed.giving/developers/reference/files.md#reserveFileUpload) — Reserve a file upload. - [POST /api/v2/files/{id}/attach](https://backed.giving/developers/reference/files.md#attachFileUpload) — Attach a scanned file to its target. - [POST /api/v2/files/{id}/confirm](https://backed.giving/developers/reference/files.md#confirmFileUpload) — Confirm a file upload. ## Reference This document. [Read the Reference reference](https://backed.giving/developers/reference/reference.md). - [GET /api/v2/openapi.json](https://backed.giving/developers/reference/reference.md#documentApiOpenApi) — This OpenAPI 3.1 document. > Webhook deliveries are in the [Event catalogue](https://backed.giving/developers/events.md), and the OData reporting feed in [Connect Power BI](https://backed.giving/developers/guides/power-bi.md). --- Source: https://backed.giving/developers/reference/authentication.md # Authentication Exchange a credential for an access token. 1 operation: parameters, request body and answers, with examples. - [POST /oauth/token](#issueOAuthToken) — Obtain an access token with the OAuth 2.0 client-credentials grant. > How to use the token, and what each scope opens, is in the [Authentication guide](https://backed.giving/developers/guides/authentication.md). ## POST /oauth/token **Obtain an access token with the OAuth 2.0 client-credentials grant.** Form-encoded body; client\_id and client\_secret may instead be sent with HTTP Basic authentication. Each success issues a new token valid 432000 seconds (5 days). Throttled to 30 requests a minute per client\_id, counted before the secret is checked. - Operation ID: `issueOAuthToken` - Authentication: No token: the credential’s `client_id` and `client_secret`, in the form body or with HTTP Basic authentication. Example request: ```bash curl -X POST "https://your-org.backed.giving/oauth/token" \ -d grant_type=client_credentials \ -d client_id=your-client-id \ -d client_secret=your-client-secret ``` ### Request body `application/x-www-form-urlencoded`, the `TokenRequest` schema. `client_id` and `client_secret` are required unless sent with HTTP Basic authentication. Fields of the TokenRequest body: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `grant_type` | `string` | Yes | Always `client_credentials`. | | `client_id` | `string` | No | Your Client ID | | `client_secret` | `string` | No | Your Client Secret | Example request body: ```text grant_type=client_credentials&client_id=your-client-id&client_secret=your-client-secret ``` ### 200 response Token issued. Shown once; never cached. Headers: `Cache-Control`. Fields of TokenResponse: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `access_token` | `string` | Yes | | | `token_type` | `string` | Yes | Always `Bearer`. | | `expires_in` | `number` | Yes | | | `created_at` | `integer` | Yes | | Example 200 response: ```json { "access_token": "q3Vx9LmT2sKp4Rz7Wn1YeB8cD5fG6hJ0kN_aQ-uIoPr", "token_type": "Bearer", "expires_in": 432000, "created_at": 1762650000 } ``` ### 400 response `invalid_request`: A query parameter is invalid, or a write’s body is not a JSON object. `unsupported_grant_type`: The grant type is not client\_credentials. Headers: `Cache-Control`. Example 400 response: invalid_request: ```json { "error": "invalid_request", "error_description": "Missing required parameter: grant_type" } ``` Example 400 response: unsupported_grant_type: ```json { "error": "unsupported_grant_type", "error_description": "Only client_credentials is supported" } ``` ### 401 response Client authentication failed: an unknown client\_id, a wrong secret, a revoked credential or another Organisation’s, or API access is not Full. Headers: `Cache-Control`. Example 401 response: ```json { "error": "invalid_client", "error_description": "Client authentication failed" } ``` ### 403 response The request was sent over plain HTTP and was not processed. Example 403 response: ```json { "error": "https_required" } ``` ### 404 response An unknown host or path, or an id that is unknown, deleted or of another Organisation. Example 404 response: ```json { "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: ```json { "error": "rate_limited", "error_description": "Too many requests" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "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: ```json { "error": "temporarily_unavailable" } ``` --- Source: https://backed.giving/developers/reference/funding-rounds.md # Funding Rounds The Organisation’s funding rounds. 2 operations: parameters, request body and answers, with examples. - [GET /api/v2/funding\_rounds](#listFundingRounds) — List funding rounds. - [GET /api/v2/funding\_rounds/{id}](#getFundingRound) — Get one funding round. ## GET /api/v2/funding_rounds **List funding rounds.** Every funding round of the Organisation in ascending id order, at most 50 a page. - Operation ID: `listFundingRounds` - Authentication: A Bearer token from a credential holding the `read` scope. - Also served at `/funding_system/api/v2/funding_rounds`, the documented product’s alias path. Example request: ```bash curl "https://your-org.backed.giving/api/v2/funding_rounds" \ -H "Authorization: Bearer $BACKED_TOKEN" ``` ### Parameters Parameters of GET /api/v2/funding_rounds: | Name | In | Required | Type | Description | | --- | --- | --- | --- | --- | | `limit` | query | No | `integer` | Results per page (default 50, at most 50; a larger value is read as 50). Not an integer, or below 1: 400. Example: `50`. | | `gt` | query | No | `integer` | Return records with an id greater than this value. Page forward by passing the last id you received. Example: `1287`. | | `lt` | query | No | `integer` | Return records with an id less than this value. | ### 200 response A JSON array of funding rounds. Fields of FundingRound: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | `integer` | Yes | | | `created_at` | `string` | Yes | | | `updated_at` | `string` | Yes | | | `name` | `string` | Yes | | | `description` | `string \| null` | Yes | | | `start_date` | `string \| null` | Yes | | | `ent_date` | `string \| null` | Yes | | | `total_funding` | `string \| null` | Yes | | | `funding_amount_per_application` | `string \| null` | Yes | | | `category` | `string \| null` | Yes | | | `round_number` | `integer \| null` | Yes | | | `phase` | `string \| null` | Yes | | | `phase_parent_id` | `integer \| null` | Yes | | | `is_private` | `boolean` | Yes | | | `is_continuous` | `boolean` | Yes | | | `archived` | `boolean` | Yes | | | `published_at` | `string \| null` | Yes | | | `board_hui_date` | `string \| null` | Yes | | | `board_notifications` | `string \| null` | Yes | | | `letter_of_offer_deadline_date` | `string \| null` | Yes | | | `grace_period_in_minutes` | `integer \| null` | Yes | | | `cloned_from_id` | `integer \| null` | Yes | | | `group` | `object` | Yes | | | `funding_category` | `object \| null` | Yes | | | `programme` | `object \| null` | Yes | | Example 200 response: ```json [ { "id": 42, "created_at": "2026-03-02T21:15:04.000Z", "updated_at": "2026-06-18T03:40:11.000Z", "name": "Community Grants 2026 Round 1", "description": "Grants of up to $25,000 for community-led projects.", "start_date": "2026-04-01T00:00:00.000Z", "ent_date": "2026-05-31T11:59:00.000Z", "total_funding": "500000.0", "funding_amount_per_application": "25000.0", "category": "Community", "round_number": 1, "phase": null, "phase_parent_id": null, "is_private": false, "is_continuous": false, "archived": false, "published_at": "2026-03-20T22:00:00.000Z", "board_hui_date": "2026-07-14T00:00:00.000Z", "board_notifications": null, "letter_of_offer_deadline_date": null, "grace_period_in_minutes": 15, "cloned_from_id": null, "group": { "id": 3, "name": "Community Grants" }, "funding_category": { "id": 7, "name": "Community" }, "programme": { "id": 2, "name": "Community Investment" } } ] ``` ### 400 response A query parameter is invalid, or a write’s body is not a JSON object. Example 400 response: ```json { "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: ```json { "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: ```json { "error": "https_required" } ``` ### 404 response An unknown host or path, or an id that is unknown, deleted or of another Organisation. Example 404 response: ```json { "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: ```json { "error": "rate_limited", "error_description": "Too many requests" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "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: ```json { "error": "temporarily_unavailable" } ``` ## GET /api/v2/funding_rounds/{id} **Get one funding round.** The same object as the list item. - Operation ID: `getFundingRound` - Authentication: A Bearer token from a credential holding the `read` scope. - Also served at `/funding_system/api/v2/funding_rounds/{id}`, the documented product’s alias path. Example request: ```bash curl "https://your-org.backed.giving/api/v2/funding_rounds/42" \ -H "Authorization: Bearer $BACKED_TOKEN" ``` ### Parameters Parameters of GET /api/v2/funding_rounds/{id}: | Name | In | Required | Type | Description | | --- | --- | --- | --- | --- | | `id` | path | Yes | `integer` | The record id. Anything but a positive integer is 404. Example: `42`. | ### 200 response The funding round. Fields of FundingRound: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | `integer` | Yes | | | `created_at` | `string` | Yes | | | `updated_at` | `string` | Yes | | | `name` | `string` | Yes | | | `description` | `string \| null` | Yes | | | `start_date` | `string \| null` | Yes | | | `ent_date` | `string \| null` | Yes | | | `total_funding` | `string \| null` | Yes | | | `funding_amount_per_application` | `string \| null` | Yes | | | `category` | `string \| null` | Yes | | | `round_number` | `integer \| null` | Yes | | | `phase` | `string \| null` | Yes | | | `phase_parent_id` | `integer \| null` | Yes | | | `is_private` | `boolean` | Yes | | | `is_continuous` | `boolean` | Yes | | | `archived` | `boolean` | Yes | | | `published_at` | `string \| null` | Yes | | | `board_hui_date` | `string \| null` | Yes | | | `board_notifications` | `string \| null` | Yes | | | `letter_of_offer_deadline_date` | `string \| null` | Yes | | | `grace_period_in_minutes` | `integer \| null` | Yes | | | `cloned_from_id` | `integer \| null` | Yes | | | `group` | `object` | Yes | | | `funding_category` | `object \| null` | Yes | | | `programme` | `object \| null` | Yes | | Example 200 response: ```json { "id": 42, "created_at": "2026-03-02T21:15:04.000Z", "updated_at": "2026-06-18T03:40:11.000Z", "name": "Community Grants 2026 Round 1", "description": "Grants of up to $25,000 for community-led projects.", "start_date": "2026-04-01T00:00:00.000Z", "ent_date": "2026-05-31T11:59:00.000Z", "total_funding": "500000.0", "funding_amount_per_application": "25000.0", "category": "Community", "round_number": 1, "phase": null, "phase_parent_id": null, "is_private": false, "is_continuous": false, "archived": false, "published_at": "2026-03-20T22:00:00.000Z", "board_hui_date": "2026-07-14T00:00:00.000Z", "board_notifications": null, "letter_of_offer_deadline_date": null, "grace_period_in_minutes": 15, "cloned_from_id": null, "group": { "id": 3, "name": "Community Grants" }, "funding_category": { "id": 7, "name": "Community" }, "programme": { "id": 2, "name": "Community Investment" } } ``` ### 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: ```json { "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: ```json { "error": "https_required" } ``` ### 404 response An unknown host or path, or an id that is unknown, deleted or of another Organisation. Example 404 response: ```json { "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: ```json { "error": "rate_limited", "error_description": "Too many requests" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "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: ```json { "error": "temporarily_unavailable" } ``` --- Source: https://backed.giving/developers/reference/funding-applications.md # Funding Applications Applications, their entries, moves, notes and tags. 5 operations: parameters, request body and answers, with examples. - [GET /api/v2/funding\_applications](#listFundingApplications) — List funding applications. - [GET /api/v2/funding\_applications/{id}](#getFundingApplication) — Get one funding application with its form entry. - [POST /api/v2/funding\_applications/{id}/notes](#createApplicationNote) — Add a note to a funding application. - [PUT /api/v2/funding\_applications/{id}/tags](#replaceApplicationTags) — Replace a funding application’s tags. - [POST /api/v2/funding\_applications/{id}/transitions](#transitionFundingApplication) — Move a funding application. ## GET /api/v2/funding_applications **List funding applications.** The Organisation's live funding applications in ascending id order, at most 50 a page, narrowed by the filter flags and `q`. `bank_account_number` is null unless the credential may read bank account numbers. - Operation ID: `listFundingApplications` - Authentication: A Bearer token from a credential holding the `read` scope. - Also served at `/funding_system/api/v2/funding_applications`, the documented product’s alias path. Example request: ```bash curl "https://your-org.backed.giving/api/v2/funding_applications" \ -H "Authorization: Bearer $BACKED_TOKEN" ``` ### Parameters Parameters of GET /api/v2/funding_applications: | Name | In | Required | Type | Description | | --- | --- | --- | --- | --- | | `limit` | query | No | `integer` | Results per page (default 50, at most 50; a larger value is read as 50). Not an integer, or below 1: 400. Example: `50`. | | `gt` | query | No | `integer` | Return records with an id greater than this value. Page forward by passing the last id you received. Example: `1287`. | | `lt` | query | No | `integer` | Return records with an id less than this value. | | `submitted` | query | No | `string` | Set to `1` to return only submitted applications; any other value is ignored. Example: `1`. | | `with_contract` | query | No | `string` | Set to `1` to return only applications with a contract. Example: `1`. | | `not_initial` | query | No | `string` | Set to `1` to exclude applications in their initial state. Example: `1`. | | `q` | query | No | `string` | Text search; blank is ignored. Example: `river`. | | `extra_methods[]` | query | No | `array of string` | Additional properties to include, each once, in the order named. Any other value is 400. One of `requested_amount`, `recommended_amount`, `bank_account_number`, `registration_entity_id`. Example: `requested_amount, recommended_amount`. | ### 200 response A JSON array of funding applications. Fields of FundingApplication: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | `integer` | Yes | | | `created_at` | `string` | Yes | | | `updated_at` | `string` | Yes | | | `funding_round_id` | `integer` | Yes | | | `state` | `string` | Yes | | | `title` | `string \| null` | Yes | | | `submitted_at` | `string \| null` | Yes | | | `funding_category` | `object \| null` | Yes | | | `registration_entity` | `object` | Yes | | | `lock_version` | `integer` | Yes | | | `requested_amount` | `string \| null` | No | | | `recommended_amount` | `string \| null` | No | | | `bank_account_number` | `string \| null` | No | | | `registration_entity_id` | `integer` | No | | Example 200 response: ```json [ { "id": 1287, "created_at": "2026-03-02T21:15:04.000Z", "updated_at": "2026-06-18T03:40:11.000Z", "funding_round_id": 42, "state": "submitted", "title": "Te Awa river restoration", "submitted_at": "2026-05-20T04:12:55.000Z", "funding_category": { "id": 7, "name": "Community" }, "registration_entity": { "id": 311, "name": "Te Awa Restoration Trust" }, "lock_version": 3, "requested_amount": "24500.0" } ] ``` ### 400 response A query parameter is invalid, or a write’s body is not a JSON object. Example 400 response: ```json { "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: ```json { "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: ```json { "error": "https_required" } ``` ### 404 response An unknown host or path, or an id that is unknown, deleted or of another Organisation. Example 404 response: ```json { "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: ```json { "error": "rate_limited", "error_description": "Too many requests" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "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: ```json { "error": "temporarily_unavailable" } ``` ## GET /api/v2/funding_applications/{id} **Get one funding application with its form entry.** Every list property (and the named extra methods), then `entry` with every input. Answers hidden by form logic are included and not marked. - Operation ID: `getFundingApplication` - Authentication: A Bearer token from a credential holding the `read` scope. - Also served at `/funding_system/api/v2/funding_applications/{id}`, the documented product’s alias path. Example request: ```bash curl "https://your-org.backed.giving/api/v2/funding_applications/42" \ -H "Authorization: Bearer $BACKED_TOKEN" ``` ### Parameters Parameters of GET /api/v2/funding_applications/{id}: | Name | In | Required | Type | Description | | --- | --- | --- | --- | --- | | `id` | path | Yes | `integer` | The record id. Anything but a positive integer is 404. Example: `42`. | | `extra_methods[]` | query | No | `array of string` | Additional properties to include, each once, in the order named. Any other value is 400. One of `requested_amount`, `recommended_amount`, `bank_account_number`, `registration_entity_id`. Example: `requested_amount, recommended_amount`. | ### 200 response The funding application with its form entry. Fields of FundingApplicationDetail: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | `integer` | Yes | | | `created_at` | `string` | Yes | | | `updated_at` | `string` | Yes | | | `funding_round_id` | `integer` | Yes | | | `state` | `string` | Yes | | | `title` | `string \| null` | Yes | | | `submitted_at` | `string \| null` | Yes | | | `funding_category` | `object \| null` | Yes | | | `registration_entity` | `object` | Yes | | | `lock_version` | `integer` | Yes | | | `requested_amount` | `string \| null` | No | | | `recommended_amount` | `string \| null` | No | | | `bank_account_number` | `string \| null` | No | | | `registration_entity_id` | `integer` | No | | | `entry` | `object` | Yes | | Example 200 response: ```json { "id": 1287, "created_at": "2026-03-02T21:15:04.000Z", "updated_at": "2026-06-18T03:40:11.000Z", "funding_round_id": 42, "state": "submitted", "title": "Te Awa river restoration", "submitted_at": "2026-05-20T04:12:55.000Z", "funding_category": { "id": 7, "name": "Community" }, "registration_entity": { "id": 311, "name": "Te Awa Restoration Trust" }, "lock_version": 3, "requested_amount": "24500.0", "entry": { "id": 9001, "created_at": "2026-03-02T21:15:04.000Z", "updated_at": "2026-05-20T04:12:55.000Z", "inputs": [ { "id": 55120, "value": "Te Awa river restoration", "input_group_id": null, "input_group_order": null, "field": { "id": 801, "internal_identifier": "project_title", "name": "Project title", "order": 1 }, "file": null, "tahua_file_url": null }, { "id": 55121, "value": "budget.pdf", "input_group_id": null, "input_group_order": null, "field": { "id": 802, "internal_identifier": "budget", "name": "Budget", "order": 2 }, "file": { "filename": "budget.pdf", "content_type": "application/pdf", "byte_size": 182044 }, "tahua_file_url": "https://your-org.backed.giving/api/v2/file_inputs/55121" } ] } } ``` ### 400 response A query parameter is invalid, or a write’s body is not a JSON object. Example 400 response: ```json { "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: ```json { "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: ```json { "error": "https_required" } ``` ### 404 response An unknown host or path, or an id that is unknown, deleted or of another Organisation. Example 404 response: ```json { "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: ```json { "error": "rate_limited", "error_description": "Too many requests" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "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: ```json { "error": "temporarily_unavailable" } ``` ## POST /api/v2/funding_applications/{id}/notes **Add a note to a funding application.** Kept as an Internal Comment (`internal_comments.create`), labelled with the credential and the run-as Administrator. - Operation ID: `createApplicationNote` - Authentication: A Bearer token from a credential holding the `write:applications` scope. Runs as the credential’s Administrator, and needs an `Idempotency-Key` header. Example request: ```bash curl -X POST "https://your-org.backed.giving/api/v2/funding_applications/42/notes" \ -H "Authorization: Bearer $BACKED_TOKEN" \ -H "Idempotency-Key: 7d1f0c2e-5b8a-4f3e-9c61-2a4b8e9d0f13" \ -H "Content-Type: application/json" \ -d '{"body":"Budget checked against the supplier quote."}' ``` ### Parameters Parameters of POST /api/v2/funding_applications/{id}/notes: | Name | In | Required | Type | Description | | --- | --- | --- | --- | --- | | `id` | path | Yes | `integer` | The record id. Anything but a positive integer is 404. Example: `42`. | | `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 `ApplicationNoteCreate` schema. The note’s text, kept on the application as an Internal Comment. Fields of the ApplicationNoteCreate body: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `body` | `string` | Yes | | Example request body: ```json { "body": "Budget checked against the supplier quote." } ``` ### 201 response The note. Headers: `Idempotent-Replayed`. Fields of ApplicationNote: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | `integer` | Yes | | | `body` | `string` | Yes | | | `author_label` | `string` | Yes | | | `created_at` | `string` | Yes | | Example 201 response: ```json { "id": 7781, "body": "Budget checked against the supplier quote.", "author_label": "API: Finance sync as Mere Tane", "created_at": "2026-05-20T04:12:55.000Z" } ``` ### 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: ```json { "error": "idempotency_key_required", "error_description": "A write needs an Idempotency-Key header of 1 to 255 characters" } ``` Example 400 response: invalid_request: ```json { "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: ```json { "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: ```json { "error": "https_required" } ``` Example 403 response: insufficient_scope: ```json { "error": "insufficient_scope", "error_description": "This credential lacks the write:applications scope" } ``` Example 403 response: run_as_disabled: ```json { "error": "run_as_disabled", "error_description": "This credential has no enabled run-as Administrator" } ``` Example 403 response: forbidden: ```json { "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. Example 404 response: ```json { "error": "not_found" } ``` ### 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: ```json { "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: ```json { "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: ```json { "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: ```json { "error": "idempotency_key_reused", "error_description": "This Idempotency-Key was used for a different request" } ``` Example 422 response: unknown_field: ```json { "error": "unknown_field", "error_description": "bank_account_number is not a writable field" } ``` Example 422 response: validation_failed: ```json { "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: ```json { "error": "rate_limited", "error_description": "Too many requests" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "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: ```json { "error": "temporarily_unavailable" } ``` ## PUT /api/v2/funding_applications/{id}/tags **Replace a funding application’s tags.** The hand-set tags become exactly these existing tags of the Organisation (`applications.tags`). A tag not in the Organisation’s vocabulary is 422 `unknown_tag`; none is created. Needs the Assessments module. - Operation ID: `replaceApplicationTags` - Authentication: A Bearer token from a credential holding the `write:applications` scope. Runs as the credential’s Administrator, and needs an `Idempotency-Key` header. Example request: ```bash curl -X PUT "https://your-org.backed.giving/api/v2/funding_applications/42/tags" \ -H "Authorization: Bearer $BACKED_TOKEN" \ -H "Idempotency-Key: 7d1f0c2e-5b8a-4f3e-9c61-2a4b8e9d0f13" \ -H "Content-Type: application/json" \ -d '{"tags":["priority","rural"]}' ``` ### Parameters Parameters of PUT /api/v2/funding_applications/{id}/tags: | Name | In | Required | Type | Description | | --- | --- | --- | --- | --- | | `id` | path | Yes | `integer` | The record id. Anything but a positive integer is 404. Example: `42`. | | `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 `ApplicationTags` schema. The application’s hand-set tags, each an existing tag of the Organisation. Answered lower-cased and sorted. Fields of the ApplicationTags body: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `tags` | `array of string` | Yes | | Example request body: ```json { "tags": [ "priority", "rural" ] } ``` ### 200 response The tags now set. Headers: `Idempotent-Replayed`. Fields of ApplicationTags: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `tags` | `array of string` | Yes | | Example 200 response: ```json { "tags": [ "priority", "rural" ] } ``` ### 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: ```json { "error": "idempotency_key_required", "error_description": "A write needs an Idempotency-Key header of 1 to 255 characters" } ``` Example 400 response: invalid_request: ```json { "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: ```json { "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: ```json { "error": "https_required" } ``` Example 403 response: insufficient_scope: ```json { "error": "insufficient_scope", "error_description": "This credential lacks the write:applications scope" } ``` Example 403 response: run_as_disabled: ```json { "error": "run_as_disabled", "error_description": "This credential has no enabled run-as Administrator" } ``` Example 403 response: forbidden: ```json { "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: ```json { "error": "not_found" } ``` Example 404 response: module_disabled: ```json { "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: ```json { "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: ```json { "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: ```json { "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. `unknown_tag`: A tag is not one of the Organisation’s tags. None is created. Example 422 response: idempotency_key_reused: ```json { "error": "idempotency_key_reused", "error_description": "This Idempotency-Key was used for a different request" } ``` Example 422 response: unknown_field: ```json { "error": "unknown_field", "error_description": "bank_account_number is not a writable field" } ``` Example 422 response: validation_failed: ```json { "error": "validation_failed", "error_description": "Invalid fields: due_date", "fields": [ "due_date" ] } ``` Example 422 response: unknown_tag: ```json { "error": "unknown_tag", "error_description": "urgent is not a tag of this Organisation" } ``` ### 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: ```json { "error": "rate_limited", "error_description": "Too many requests" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "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: ```json { "error": "temporarily_unavailable" } ``` ## POST /api/v2/funding_applications/{id}/transitions **Move a funding application.** One of the Administrator’s record moves through the application’s own rules, with the messages and audit entries the screen makes. A move not open from the current status is 409 `invalid_transition` naming it. An `unlock` needs a `deadline` that is a future UTC timestamp with milliseconds, such as `2027-06-07T11:59:00.000Z`. - Operation ID: `transitionFundingApplication` - Authentication: A Bearer token from a credential holding the `write:applications` scope. Runs as the credential’s Administrator, and needs an `Idempotency-Key` header. Example request: ```bash curl -X POST "https://your-org.backed.giving/api/v2/funding_applications/42/transitions" \ -H "Authorization: Bearer $BACKED_TOKEN" \ -H "Idempotency-Key: 7d1f0c2e-5b8a-4f3e-9c61-2a4b8e9d0f13" \ -H "Content-Type: application/json" \ -d '{"transition":"unlock","message":"Please attach the updated budget.","deadline":"2027-06-07T11:59:00.000Z"}' ``` ### Parameters Parameters of POST /api/v2/funding_applications/{id}/transitions: | Name | In | Required | Type | Description | | --- | --- | --- | --- | --- | | `id` | path | Yes | `integer` | The record id. Anything but a positive integer is 404. Example: `42`. | | `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 `ApplicationTransition` schema. One of the Administrator’s record moves, with its parameters: `unlock` takes `message` and `deadline`, `request_more_information` `form_template_id`, the edit-request decisions `edit_request_id`. An unlock’s `deadline` is a future UTC timestamp with milliseconds (`2027-06-07T11:59:00.000Z`), the moment the editing window closes by itself; a date alone, or a moment already past, is 422 `validation_failed`. Fields of the ApplicationTransition body: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `transition` | `string` | Yes | One of `withdraw`, `unlock`, `request_more_information`, `allow_edit_request`, `decline_edit_request`, `release_from_verifier`. | | `lock_version` | `integer` | No | | | `message` | `string` | No | | | `deadline` | `string` | No | | | `form_template_id` | `integer` | No | | | `edit_request_id` | `integer` | No | | Example request body: ```json { "transition": "unlock", "message": "Please attach the updated budget.", "deadline": "2027-06-07T11:59:00.000Z" } ``` ### 200 response The funding application as the list answers it. Headers: `Idempotent-Replayed`. Fields of FundingApplication: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | `integer` | Yes | | | `created_at` | `string` | Yes | | | `updated_at` | `string` | Yes | | | `funding_round_id` | `integer` | Yes | | | `state` | `string` | Yes | | | `title` | `string \| null` | Yes | | | `submitted_at` | `string \| null` | Yes | | | `funding_category` | `object \| null` | Yes | | | `registration_entity` | `object` | Yes | | | `lock_version` | `integer` | Yes | | | `requested_amount` | `string \| null` | No | | | `recommended_amount` | `string \| null` | No | | | `bank_account_number` | `string \| null` | No | | | `registration_entity_id` | `integer` | No | | Example 200 response: ```json { "id": 1287, "created_at": "2026-03-02T21:15:04.000Z", "updated_at": "2026-06-18T03:40:11.000Z", "funding_round_id": 42, "state": "submitted", "title": "Te Awa river restoration", "submitted_at": "2026-05-20T04:12:55.000Z", "funding_category": { "id": 7, "name": "Community" }, "registration_entity": { "id": 311, "name": "Te Awa Restoration Trust" }, "lock_version": 3, "requested_amount": "24500.0" } ``` ### 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: ```json { "error": "idempotency_key_required", "error_description": "A write needs an Idempotency-Key header of 1 to 255 characters" } ``` Example 400 response: invalid_request: ```json { "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: ```json { "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: ```json { "error": "https_required" } ``` Example 403 response: insufficient_scope: ```json { "error": "insufficient_scope", "error_description": "This credential lacks the write:applications scope" } ``` Example 403 response: run_as_disabled: ```json { "error": "run_as_disabled", "error_description": "This credential has no enabled run-as Administrator" } ``` Example 403 response: forbidden: ```json { "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. Example 404 response: ```json { "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_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: ```json { "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: ```json { "error": "invalid_transition", "error_description": "The record cannot make this move from approved", "current_status": "approved" } ``` Example 409 response: invalid_state: ```json { "error": "invalid_state", "error_description": "The target does not take files in its current state" } ``` ### 412 response If-Match is missing, malformed or not the record’s current lock\_version, or the record changed under the write. Nothing was written. Example 412 response: ```json { "error": "precondition_failed", "error_description": "If-Match must carry the record’s current lock_version" } ``` ### 413 response The body, or the declared file, is larger than the limit that applies. Example 413 response: ```json { "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: ```json { "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: ```json { "error": "idempotency_key_reused", "error_description": "This Idempotency-Key was used for a different request" } ``` Example 422 response: unknown_field: ```json { "error": "unknown_field", "error_description": "bank_account_number is not a writable field" } ``` Example 422 response: validation_failed: ```json { "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: ```json { "error": "rate_limited", "error_description": "Too many requests" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "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: ```json { "error": "temporarily_unavailable" } ``` --- Source: https://backed.giving/developers/reference/registration-entities.md # Registration Entities Applicant organisations and individuals. 5 operations: parameters, request body and answers, with examples. - [GET /api/v2/registration\_entities](#listRegistrationEntities) — List registration entities. - [POST /api/v2/registration\_entities](#createRegistrationEntity) — Add an applicant: a person and their registration entity. - [GET /api/v2/registration\_entities/{id}](#getRegistrationEntity) — Get one registration entity with its users. - [PATCH /api/v2/registration\_entities/{id}](#updateRegistrationEntity) — Edit an organisation’s Founded date and stage override. - [POST /api/v2/registration\_entities/{id}/users](#addRegistrationEntityUser) — Invite a user to a registration entity. ## GET /api/v2/registration_entities **List registration entities.** Every live registration entity of the Organisation in ascending id order, at most 50 a page. No users: personal data is on the single record only. - Operation ID: `listRegistrationEntities` - Authentication: A Bearer token from a credential holding the `read` scope. - Also served at `/funding_system/api/v2/registration_entities`, the documented product’s alias path. Example request: ```bash curl "https://your-org.backed.giving/api/v2/registration_entities" \ -H "Authorization: Bearer $BACKED_TOKEN" ``` ### Parameters Parameters of GET /api/v2/registration_entities: | Name | In | Required | Type | Description | | --- | --- | --- | --- | --- | | `limit` | query | No | `integer` | Results per page (default 50, at most 50; a larger value is read as 50). Not an integer, or below 1: 400. Example: `50`. | | `gt` | query | No | `integer` | Return records with an id greater than this value. Page forward by passing the last id you received. Example: `1287`. | | `lt` | query | No | `integer` | Return records with an id less than this value. | ### 200 response A JSON array of registration entities. Fields of RegistrationEntity: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | `integer` | Yes | | | `created_at` | `string` | Yes | | | `updated_at` | `string` | Yes | | | `name` | `string` | Yes | | | `registration_type` | `object` | Yes | | | `lock_version` | `integer` | Yes | | Example 200 response: ```json [ { "id": 311, "created_at": "2026-03-02T21:15:04.000Z", "updated_at": "2026-06-18T03:40:11.000Z", "name": "Te Awa Restoration Trust", "registration_type": { "id": 2, "name": "Organisation" }, "lock_version": 4 } ] ``` ### 400 response A query parameter is invalid, or a write’s body is not a JSON object. Example 400 response: ```json { "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: ```json { "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: ```json { "error": "https_required" } ``` ### 404 response An unknown host or path, or an id that is unknown, deleted or of another Organisation. Example 404 response: ```json { "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: ```json { "error": "rate_limited", "error_description": "Too many requests" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "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: ```json { "error": "temporarily_unavailable" } ``` ## POST /api/v2/registration_entities **Add an applicant: a person and their registration entity.** `Add Applicant` as the run-as Administrator (`applicants.add`). Without `invite` nobody is told and the record reads Unclaimed. - Operation ID: `createRegistrationEntity` - Authentication: A Bearer token from a credential holding the `write:registrations` scope. Runs as the credential’s Administrator, and needs an `Idempotency-Key` header. Example request: ```bash curl -X POST "https://your-org.backed.giving/api/v2/registration_entities" \ -H "Authorization: Bearer $BACKED_TOKEN" \ -H "Idempotency-Key: 7d1f0c2e-5b8a-4f3e-9c61-2a4b8e9d0f13" \ -H "Content-Type: application/json" \ -d '{"registration_type_id":2,"entity_name":"Te Awa Restoration Trust","first_name":"Aroha","last_name":"Ngata","email":"aroha@example.org","invite":true}' ``` ### Parameters Parameters of POST /api/v2/registration_entities: | 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 `RegistrationEntityCreate` schema. `Add Applicant`: the Registration Type, the person, and for an organisation the entity’s name. `invite` sends the applicant invitation (default false). Fields of the RegistrationEntityCreate body: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `registration_type_id` | `integer` | Yes | | | `entity_name` | `string` | No | | | `first_name` | `string` | Yes | | | `last_name` | `string` | Yes | | | `email` | `string` | Yes | | | `invite` | `boolean` | No | | Example request body: ```json { "registration_type_id": 2, "entity_name": "Te Awa Restoration Trust", "first_name": "Aroha", "last_name": "Ngata", "email": "aroha@example.org", "invite": true } ``` ### 201 response The registration entity as the single read answers it. Headers: `Idempotent-Replayed`. Fields of RegistrationEntityDetail: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | `integer` | Yes | | | `created_at` | `string` | Yes | | | `updated_at` | `string` | Yes | | | `name` | `string` | Yes | | | `registration_type` | `object` | Yes | | | `lock_version` | `integer` | Yes | | | `users` | `array of object` | Yes | | Example 201 response: ```json { "id": 311, "created_at": "2026-03-02T21:15:04.000Z", "updated_at": "2026-06-18T03:40:11.000Z", "name": "Te Awa Restoration Trust", "registration_type": { "id": 2, "name": "Organisation" }, "lock_version": 4, "users": [ { "id": 5012, "email": "aroha@example.org", "first_name": "Aroha", "last_name": "Ngata" } ] } ``` ### 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: ```json { "error": "idempotency_key_required", "error_description": "A write needs an Idempotency-Key header of 1 to 255 characters" } ``` Example 400 response: invalid_request: ```json { "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: ```json { "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: ```json { "error": "https_required" } ``` Example 403 response: insufficient_scope: ```json { "error": "insufficient_scope", "error_description": "This credential lacks the write:applications scope" } ``` Example 403 response: run_as_disabled: ```json { "error": "run_as_disabled", "error_description": "This credential has no enabled run-as Administrator" } ``` Example 403 response: forbidden: ```json { "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. Example 404 response: ```json { "error": "not_found" } ``` ### 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: ```json { "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: ```json { "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: ```json { "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: ```json { "error": "idempotency_key_reused", "error_description": "This Idempotency-Key was used for a different request" } ``` Example 422 response: unknown_field: ```json { "error": "unknown_field", "error_description": "bank_account_number is not a writable field" } ``` Example 422 response: validation_failed: ```json { "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: ```json { "error": "rate_limited", "error_description": "Too many requests" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "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: ```json { "error": "temporarily_unavailable" } ``` ## GET /api/v2/registration_entities/{id} **Get one registration entity with its users.** Every list property, then `users[]`: the account owner first. - Operation ID: `getRegistrationEntity` - Authentication: A Bearer token from a credential holding the `read` scope. - Also served at `/funding_system/api/v2/registration_entities/{id}`, the documented product’s alias path. Example request: ```bash curl "https://your-org.backed.giving/api/v2/registration_entities/42" \ -H "Authorization: Bearer $BACKED_TOKEN" ``` ### Parameters Parameters of GET /api/v2/registration_entities/{id}: | Name | In | Required | Type | Description | | --- | --- | --- | --- | --- | | `id` | path | Yes | `integer` | The record id. Anything but a positive integer is 404. Example: `42`. | ### 200 response The registration entity with its users. Fields of RegistrationEntityDetail: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | `integer` | Yes | | | `created_at` | `string` | Yes | | | `updated_at` | `string` | Yes | | | `name` | `string` | Yes | | | `registration_type` | `object` | Yes | | | `lock_version` | `integer` | Yes | | | `users` | `array of object` | Yes | | Example 200 response: ```json { "id": 311, "created_at": "2026-03-02T21:15:04.000Z", "updated_at": "2026-06-18T03:40:11.000Z", "name": "Te Awa Restoration Trust", "registration_type": { "id": 2, "name": "Organisation" }, "lock_version": 4, "users": [ { "id": 5012, "email": "aroha@example.org", "first_name": "Aroha", "last_name": "Ngata" } ] } ``` ### 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: ```json { "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: ```json { "error": "https_required" } ``` ### 404 response An unknown host or path, or an id that is unknown, deleted or of another Organisation. Example 404 response: ```json { "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: ```json { "error": "rate_limited", "error_description": "Too many requests" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "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: ```json { "error": "temporarily_unavailable" } ``` ## PATCH /api/v2/registration_entities/{id} **Edit an organisation’s Founded date and stage override.** The profile’s own edit (`applicants.edit`) at the `If-Match` version: a stale version is 412 before anything is written. Never the bank account. - Operation ID: `updateRegistrationEntity` - Authentication: A Bearer token from a credential holding the `write:registrations` scope. Runs as the credential’s Administrator, and needs an `Idempotency-Key` header. Example request: ```bash curl -X PATCH "https://your-org.backed.giving/api/v2/registration_entities/42" \ -H "Authorization: Bearer $BACKED_TOKEN" \ -H "If-Match: 4" \ -H "Idempotency-Key: 7d1f0c2e-5b8a-4f3e-9c61-2a4b8e9d0f13" \ -H "Content-Type: application/json" \ -d '{"founded_on":"2014-03-01","stage_override":"growth","stage_override_reason":"Confirmed by the 2025 annual report."}' ``` ### Parameters Parameters of PATCH /api/v2/registration_entities/{id}: | Name | In | Required | Type | Description | | --- | --- | --- | --- | --- | | `id` | path | Yes | `integer` | The record id. Anything but a positive integer is 404. Example: `42`. | | `If-Match` | header | Yes | `string` | The record’s `lock_version` as you last read it, bare or quoted (`3` or `"3"`). Example: `4`. | | `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 `RegistrationEntityUpdate` schema. An organisation’s `Founded` date and stage override. Only the properties sent change. Fields of the RegistrationEntityUpdate body: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `founded_on` | `string \| null` | No | | | `stage_override` | `string \| null` | No | One of `seed`, `early`, `growth`, `advanced`. | | `stage_override_reason` | `string \| null` | No | | Example request body: ```json { "founded_on": "2014-03-01", "stage_override": "growth", "stage_override_reason": "Confirmed by the 2025 annual report." } ``` ### 200 response The registration entity as the single read answers it. Headers: `Idempotent-Replayed`. Fields of RegistrationEntityDetail: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | `integer` | Yes | | | `created_at` | `string` | Yes | | | `updated_at` | `string` | Yes | | | `name` | `string` | Yes | | | `registration_type` | `object` | Yes | | | `lock_version` | `integer` | Yes | | | `users` | `array of object` | Yes | | Example 200 response: ```json { "id": 311, "created_at": "2026-03-02T21:15:04.000Z", "updated_at": "2026-06-18T03:40:11.000Z", "name": "Te Awa Restoration Trust", "registration_type": { "id": 2, "name": "Organisation" }, "lock_version": 4, "users": [ { "id": 5012, "email": "aroha@example.org", "first_name": "Aroha", "last_name": "Ngata" } ] } ``` ### 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: ```json { "error": "idempotency_key_required", "error_description": "A write needs an Idempotency-Key header of 1 to 255 characters" } ``` Example 400 response: invalid_request: ```json { "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: ```json { "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: ```json { "error": "https_required" } ``` Example 403 response: insufficient_scope: ```json { "error": "insufficient_scope", "error_description": "This credential lacks the write:applications scope" } ``` Example 403 response: run_as_disabled: ```json { "error": "run_as_disabled", "error_description": "This credential has no enabled run-as Administrator" } ``` Example 403 response: forbidden: ```json { "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. Example 404 response: ```json { "error": "not_found" } ``` ### 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: ```json { "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" } ``` ### 412 response If-Match is missing, malformed or not the record’s current lock\_version, or the record changed under the write. Nothing was written. Example 412 response: ```json { "error": "precondition_failed", "error_description": "If-Match must carry the record’s current lock_version" } ``` ### 413 response The body, or the declared file, is larger than the limit that applies. Example 413 response: ```json { "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: ```json { "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: ```json { "error": "idempotency_key_reused", "error_description": "This Idempotency-Key was used for a different request" } ``` Example 422 response: unknown_field: ```json { "error": "unknown_field", "error_description": "bank_account_number is not a writable field" } ``` Example 422 response: validation_failed: ```json { "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: ```json { "error": "rate_limited", "error_description": "Too many requests" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "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: ```json { "error": "temporarily_unavailable" } ``` ## POST /api/v2/registration_entities/{id}/users **Invite a user to a registration entity.** `Invite Applicant` (`applicants.invite`): an existing person is attached, never duplicated; a new address is sent the applicant invitation, never a password. - Operation ID: `addRegistrationEntityUser` - Authentication: A Bearer token from a credential holding the `write:registrations` scope. Runs as the credential’s Administrator, and needs an `Idempotency-Key` header. Example request: ```bash curl -X POST "https://your-org.backed.giving/api/v2/registration_entities/42/users" \ -H "Authorization: Bearer $BACKED_TOKEN" \ -H "Idempotency-Key: 7d1f0c2e-5b8a-4f3e-9c61-2a4b8e9d0f13" \ -H "Content-Type: application/json" \ -d '{"email":"rawiri@example.org","first_name":"Rawiri","last_name":"Ngata"}' ``` ### Parameters Parameters of POST /api/v2/registration_entities/{id}/users: | Name | In | Required | Type | Description | | --- | --- | --- | --- | --- | | `id` | path | Yes | `integer` | The record id. Anything but a positive integer is 404. Example: `42`. | | `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 `RegistrationEntityUserCreate` schema. `Invite Applicant`: an existing person of the Organisation is attached; a new address is sent the applicant invitation. Fields of the RegistrationEntityUserCreate body: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `email` | `string` | Yes | | | `first_name` | `string` | Yes | | | `last_name` | `string` | Yes | | Example request body: ```json { "email": "rawiri@example.org", "first_name": "Rawiri", "last_name": "Ngata" } ``` ### 201 response The membership. Headers: `Idempotent-Replayed`. Fields of RegistrationEntityMembership: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `registration_entity_id` | `integer` | Yes | | | `user_id` | `integer` | Yes | | | `email` | `string` | Yes | | | `invited` | `boolean` | Yes | | Example 201 response: ```json { "registration_entity_id": 311, "user_id": 5013, "email": "rawiri@example.org", "invited": true } ``` ### 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: ```json { "error": "idempotency_key_required", "error_description": "A write needs an Idempotency-Key header of 1 to 255 characters" } ``` Example 400 response: invalid_request: ```json { "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: ```json { "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: ```json { "error": "https_required" } ``` Example 403 response: insufficient_scope: ```json { "error": "insufficient_scope", "error_description": "This credential lacks the write:applications scope" } ``` Example 403 response: run_as_disabled: ```json { "error": "run_as_disabled", "error_description": "This credential has no enabled run-as Administrator" } ``` Example 403 response: forbidden: ```json { "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. Example 404 response: ```json { "error": "not_found" } ``` ### 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: ```json { "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: ```json { "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: ```json { "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: ```json { "error": "idempotency_key_reused", "error_description": "This Idempotency-Key was used for a different request" } ``` Example 422 response: unknown_field: ```json { "error": "unknown_field", "error_description": "bank_account_number is not a writable field" } ``` Example 422 response: validation_failed: ```json { "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: ```json { "error": "rate_limited", "error_description": "Too many requests" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "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: ```json { "error": "temporarily_unavailable" } ``` --- Source: https://backed.giving/developers/reference/assessment-rounds.md # Assessment Rounds Assessor assignments. 1 operation: parameters, request body and answers, with examples. - [POST /api/v2/assessment\_rounds/{id}/assignments](#createAssessorAssignment) — Assign an Assessor to applications in an assessment round. ## POST /api/v2/assessment_rounds/{id}/assignments **Assign an Assessor to applications in an assessment round.** A Draft round’s pending pairs, or a Published round’s Assessments. An Assessor with a declared conflict of interest is 409 `conflict_declared`. Needs the Assessments module. - Operation ID: `createAssessorAssignment` - Authentication: A Bearer token from a credential holding the `write:assessments` scope. Runs as the credential’s Administrator, and needs an `Idempotency-Key` header. Example request: ```bash curl -X POST "https://your-org.backed.giving/api/v2/assessment_rounds/42/assignments" \ -H "Authorization: Bearer $BACKED_TOKEN" \ -H "Idempotency-Key: 7d1f0c2e-5b8a-4f3e-9c61-2a4b8e9d0f13" \ -H "Content-Type: application/json" \ -d '{"assessor_user_id":5100,"funding_application_ids":[1287,1288],"notify":true}' ``` ### Parameters Parameters of POST /api/v2/assessment_rounds/{id}/assignments: | Name | In | Required | Type | Description | | --- | --- | --- | --- | --- | | `id` | path | Yes | `integer` | The record id. Anything but a positive integer is 404. Example: `42`. | | `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 `AssessorAssignmentCreate` schema. One Assessor given applications of the assessment round. `notify` (a Published round only) runs `Notify Assessors`. Fields of the AssessorAssignmentCreate body: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `assessor_user_id` | `integer` | Yes | | | `funding_application_ids` | `array of integer` | Yes | | | `notify` | `boolean` | No | | Example request body: ```json { "assessor_user_id": 5100, "funding_application_ids": [ 1287, 1288 ], "notify": true } ``` ### 201 response What the Assessor now holds. Headers: `Idempotent-Replayed`. Fields of AssessorAssignment: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `assessment_round_id` | `integer` | Yes | | | `assessor_user_id` | `integer` | Yes | | | `round_status` | `string` | Yes | One of `draft`, `published`. | | `funding_application_ids` | `array of integer` | Yes | | | `notified` | `boolean` | Yes | | Example 201 response: ```json { "assessment_round_id": 88, "assessor_user_id": 5100, "round_status": "published", "funding_application_ids": [ 1287, 1288 ], "notified": true } ``` ### 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: ```json { "error": "idempotency_key_required", "error_description": "A write needs an Idempotency-Key header of 1 to 255 characters" } ``` Example 400 response: invalid_request: ```json { "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: ```json { "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: ```json { "error": "https_required" } ``` Example 403 response: insufficient_scope: ```json { "error": "insufficient_scope", "error_description": "This credential lacks the write:applications scope" } ``` Example 403 response: run_as_disabled: ```json { "error": "run_as_disabled", "error_description": "This credential has no enabled run-as Administrator" } ``` Example 403 response: forbidden: ```json { "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: ```json { "error": "not_found" } ``` Example 404 response: module_disabled: ```json { "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_state`: The record is not in a state that admits this write. `conflict_declared`: The Assessor has declared a conflict of interest with an application. Headers: `Retry-After`. Example 409 response: idempotency_in_progress: ```json { "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_state: ```json { "error": "invalid_state", "error_description": "The target does not take files in its current state" } ``` Example 409 response: conflict_declared: ```json { "error": "conflict_declared", "error_description": "The Assessor has declared a conflict of interest" } ``` ### 413 response The body, or the declared file, is larger than the limit that applies. Example 413 response: ```json { "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: ```json { "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: ```json { "error": "idempotency_key_reused", "error_description": "This Idempotency-Key was used for a different request" } ``` Example 422 response: unknown_field: ```json { "error": "unknown_field", "error_description": "bank_account_number is not a writable field" } ``` Example 422 response: validation_failed: ```json { "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: ```json { "error": "rate_limited", "error_description": "Too many requests" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "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: ```json { "error": "temporarily_unavailable" } ``` --- Source: https://backed.giving/developers/reference/contracts.md # Contracts Contracts and their recorded statuses. 3 operations: parameters, request body and answers, with examples. - [GET /api/v2/contracts](#listContracts) — List contracts. - [GET /api/v2/contracts/{id}](#getContract) — Get one contract. - [POST /api/v2/contracts/{id}/status](#recordContractStatus) — Record a contract executed or terminated. ## GET /api/v2/contracts **List contracts.** Every contract of the Organisation in ascending id order, at most 50 a page. - Operation ID: `listContracts` - Authentication: A Bearer token from a credential holding the `read` scope. - Also served at `/funding_system/api/v2/contracts`, the documented product’s alias path. Example request: ```bash curl "https://your-org.backed.giving/api/v2/contracts" \ -H "Authorization: Bearer $BACKED_TOKEN" ``` ### Parameters Parameters of GET /api/v2/contracts: | Name | In | Required | Type | Description | | --- | --- | --- | --- | --- | | `limit` | query | No | `integer` | Results per page (default 50, at most 50; a larger value is read as 50). Not an integer, or below 1: 400. Example: `50`. | | `gt` | query | No | `integer` | Return records with an id greater than this value. Page forward by passing the last id you received. Example: `1287`. | | `lt` | query | No | `integer` | Return records with an id less than this value. | ### 200 response A JSON array of contracts. Fields of Contract: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | `integer` | Yes | | | `created_at` | `string` | Yes | | | `updated_at` | `string` | Yes | | | `title` | `string` | Yes | | | `amount` | `string \| null` | Yes | | | `contract_start_date` | `string \| null` | Yes | | | `contract_end_date` | `string \| null` | Yes | | | `state` | `string` | Yes | One of `draft`, `sent`, `partially_signed`, `declined`, `signed`, `completed`, `withdrawing`, `withdrawn`, `terminating`, `terminated`. | | `contract_type` | `string` | Yes | One of `standard`, `variation`. | | `registration_entity` | `object` | Yes | | | `lock_version` | `integer` | Yes | | Example 200 response: ```json [ { "id": 640, "created_at": "2026-03-02T21:15:04.000Z", "updated_at": "2026-06-18T03:40:11.000Z", "title": "Funding agreement: Te Awa river restoration", "amount": "24500.0", "contract_start_date": "2026-08-01", "contract_end_date": "2027-07-31", "state": "signed", "contract_type": "standard", "registration_entity": { "id": 311, "name": "Te Awa Restoration Trust" }, "lock_version": 1 } ] ``` ### 400 response A query parameter is invalid, or a write’s body is not a JSON object. Example 400 response: ```json { "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: ```json { "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: ```json { "error": "https_required" } ``` ### 404 response An unknown host or path, or an id that is unknown, deleted or of another Organisation. Example 404 response: ```json { "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: ```json { "error": "rate_limited", "error_description": "Too many requests" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "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: ```json { "error": "temporarily_unavailable" } ``` ## GET /api/v2/contracts/{id} **Get one contract.** The same object as the list item. - Operation ID: `getContract` - Authentication: A Bearer token from a credential holding the `read` scope. - Also served at `/funding_system/api/v2/contracts/{id}`, the documented product’s alias path. Example request: ```bash curl "https://your-org.backed.giving/api/v2/contracts/42" \ -H "Authorization: Bearer $BACKED_TOKEN" ``` ### Parameters Parameters of GET /api/v2/contracts/{id}: | Name | In | Required | Type | Description | | --- | --- | --- | --- | --- | | `id` | path | Yes | `integer` | The record id. Anything but a positive integer is 404. Example: `42`. | ### 200 response The contract. Fields of Contract: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | `integer` | Yes | | | `created_at` | `string` | Yes | | | `updated_at` | `string` | Yes | | | `title` | `string` | Yes | | | `amount` | `string \| null` | Yes | | | `contract_start_date` | `string \| null` | Yes | | | `contract_end_date` | `string \| null` | Yes | | | `state` | `string` | Yes | One of `draft`, `sent`, `partially_signed`, `declined`, `signed`, `completed`, `withdrawing`, `withdrawn`, `terminating`, `terminated`. | | `contract_type` | `string` | Yes | One of `standard`, `variation`. | | `registration_entity` | `object` | Yes | | | `lock_version` | `integer` | Yes | | Example 200 response: ```json { "id": 640, "created_at": "2026-03-02T21:15:04.000Z", "updated_at": "2026-06-18T03:40:11.000Z", "title": "Funding agreement: Te Awa river restoration", "amount": "24500.0", "contract_start_date": "2026-08-01", "contract_end_date": "2027-07-31", "state": "signed", "contract_type": "standard", "registration_entity": { "id": 311, "name": "Te Awa Restoration Trust" }, "lock_version": 1 } ``` ### 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: ```json { "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: ```json { "error": "https_required" } ``` ### 404 response An unknown host or path, or an id that is unknown, deleted or of another Organisation. Example 404 response: ```json { "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: ```json { "error": "rate_limited", "error_description": "Too many requests" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "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: ```json { "error": "temporarily_unavailable" } ``` ## POST /api/v2/contracts/{id}/status **Record a contract executed or terminated.** `executed` is the Administrator’s counter-signature (`contracts.counter_sign`); `terminated` is `Terminate Contract` (`contracts.terminate`) and needs a `reason`. Needs the Contracts module. - Operation ID: `recordContractStatus` - 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: ```bash curl -X POST "https://your-org.backed.giving/api/v2/contracts/42/status" \ -H "Authorization: Bearer $BACKED_TOKEN" \ -H "Idempotency-Key: 7d1f0c2e-5b8a-4f3e-9c61-2a4b8e9d0f13" \ -H "Content-Type: application/json" \ -d '{"status":"executed"}' ``` ### Parameters Parameters of POST /api/v2/contracts/{id}/status: | Name | In | Required | Type | Description | | --- | --- | --- | --- | --- | | `id` | path | Yes | `integer` | The record id. Anything but a positive integer is 404. Example: `42`. | | `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 `ContractStatusChange` schema. The status to record: `executed` (the Administrator’s counter-signature) or `terminated` (with a `reason`). Any other value is 409 `invalid_transition`. Fields of the ContractStatusChange body: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `status` | `string` | Yes | | | `reason` | `string` | No | | Example request body: ```json { "status": "executed" } ``` ### 200 response The contract as the single read answers it. Headers: `Idempotent-Replayed`. Fields of Contract: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | `integer` | Yes | | | `created_at` | `string` | Yes | | | `updated_at` | `string` | Yes | | | `title` | `string` | Yes | | | `amount` | `string \| null` | Yes | | | `contract_start_date` | `string \| null` | Yes | | | `contract_end_date` | `string \| null` | Yes | | | `state` | `string` | Yes | One of `draft`, `sent`, `partially_signed`, `declined`, `signed`, `completed`, `withdrawing`, `withdrawn`, `terminating`, `terminated`. | | `contract_type` | `string` | Yes | One of `standard`, `variation`. | | `registration_entity` | `object` | Yes | | | `lock_version` | `integer` | Yes | | Example 200 response: ```json { "id": 640, "created_at": "2026-03-02T21:15:04.000Z", "updated_at": "2026-06-18T03:40:11.000Z", "title": "Funding agreement: Te Awa river restoration", "amount": "24500.0", "contract_start_date": "2026-08-01", "contract_end_date": "2027-07-31", "state": "signed", "contract_type": "standard", "registration_entity": { "id": 311, "name": "Te Awa Restoration Trust" }, "lock_version": 1 } ``` ### 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: ```json { "error": "idempotency_key_required", "error_description": "A write needs an Idempotency-Key header of 1 to 255 characters" } ``` Example 400 response: invalid_request: ```json { "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: ```json { "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: ```json { "error": "https_required" } ``` Example 403 response: insufficient_scope: ```json { "error": "insufficient_scope", "error_description": "This credential lacks the write:applications scope" } ``` Example 403 response: run_as_disabled: ```json { "error": "run_as_disabled", "error_description": "This credential has no enabled run-as Administrator" } ``` Example 403 response: forbidden: ```json { "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: ```json { "error": "not_found" } ``` Example 404 response: module_disabled: ```json { "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: ```json { "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: ```json { "error": "invalid_transition", "error_description": "The record cannot make this move from approved", "current_status": "approved" } ``` Example 409 response: invalid_state: ```json { "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: ```json { "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: ```json { "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: ```json { "error": "idempotency_key_reused", "error_description": "This Idempotency-Key was used for a different request" } ``` Example 422 response: unknown_field: ```json { "error": "unknown_field", "error_description": "bank_account_number is not a writable field" } ``` Example 422 response: validation_failed: ```json { "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: ```json { "error": "rate_limited", "error_description": "Too many requests" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "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: ```json { "error": "temporarily_unavailable" } ``` --- Source: https://backed.giving/developers/reference/milestones.md # Milestones Milestones with their deliverables. 4 operations: parameters, request body and answers, with examples. - [GET /api/v2/milestones](#listMilestones) — List milestones. - [POST /api/v2/milestones](#createMilestone) — Create a milestone on a funding application. - [GET /api/v2/milestones/{id}](#getMilestone) — Get one milestone. - [POST /api/v2/milestones/{id}/approve](#approveMilestone) — Approve a milestone. ## 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: ```bash curl "https://your-org.backed.giving/api/v2/milestones" \ -H "Authorization: Bearer $BACKED_TOKEN" ``` ### Parameters Parameters of GET /api/v2/milestones: | Name | In | Required | Type | Description | | --- | --- | --- | --- | --- | | `limit` | query | No | `integer` | Results per page (default 50, at most 50; a larger value is read as 50). Not an integer, or below 1: 400. Example: `50`. | | `gt` | query | No | `integer` | Return records with an id greater than this value. Page forward by passing the last id you received. Example: `1287`. | | `lt` | query | No | `integer` | Return records with an id less than this value. | | `filters[deliverables][value]` | query | No | `string` | Match deliverable input values; `%` is a wildcard for partial matching. Example: `%Smith%`. | | `filters[deliverables][tags][]` | query | No | `array of string` | Filter by the internal\_identifier of deliverable fields. Example: `artist_name, song_title`. | | `filters[deliverables][tag]` | query | No | `string` | Combine a tag with filters\[deliverables\]\[value\] to search within one field. Example: `artist_name`. | | `filters[ids][]` | query | No | `array of integer` | Filter by milestone ids. Example: `2210, 2211`. | ### 200 response A JSON array of milestones. Fields of Milestone: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | `integer` | Yes | | | `created_at` | `string` | Yes | | | `updated_at` | `string` | Yes | | | `title` | `string` | Yes | | | `contract_id` | `integer \| null` | Yes | | | `funding_amount` | `string` | Yes | | | `description` | `string \| null` | Yes | | | `state` | `string` | Yes | One of `not_submitted`, `pending_review`, `submitted_to_verifier`, `submitted`, `resubmission_requested`, `approved`, `finance_approved`. | | `paid_at` | `string \| null` | Yes | | | `decline_reason` | `string \| null` | Yes | | | `due_date` | `string` | Yes | | | `contract` | `object \| null` | Yes | | | `lock_version` | `integer` | Yes | | | `deliverables_template_entry` | `object` | Yes | | Example 200 response: ```json [ { "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: ```json { "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: ```json { "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: ```json { "error": "https_required" } ``` ### 404 response An unknown host or path, or an id that is unknown, deleted or of another Organisation. Example 404 response: ```json { "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: ```json { "error": "rate_limited", "error_description": "Too many requests" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "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: ```json { "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: ```bash 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: | 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 `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: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `funding_application_id` | `string` | Yes | | | `title` | `string` | Yes | | | `due_date` | `string` | Yes | | | `amount` | `string` | Yes | | | `form_template_id` | `string` | Yes | | | `milestone_template_id` | `string \| null` | No | | | `description` | `string \| null` | No | | | `reviewer_user_ids` | `array of string` | No | | Example request body: ```json { "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: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | `integer` | Yes | | | `created_at` | `string` | Yes | | | `updated_at` | `string` | Yes | | | `title` | `string` | Yes | | | `contract_id` | `integer \| null` | Yes | | | `funding_amount` | `string` | Yes | | | `description` | `string \| null` | Yes | | | `state` | `string` | Yes | One of `not_submitted`, `pending_review`, `submitted_to_verifier`, `submitted`, `resubmission_requested`, `approved`, `finance_approved`. | | `paid_at` | `string \| null` | Yes | | | `decline_reason` | `string \| null` | Yes | | | `due_date` | `string` | Yes | | | `contract` | `object \| null` | Yes | | | `lock_version` | `integer` | Yes | | | `deliverables_template_entry` | `object` | Yes | | Example 201 response: ```json { "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: ```json { "error": "idempotency_key_required", "error_description": "A write needs an Idempotency-Key header of 1 to 255 characters" } ``` Example 400 response: invalid_request: ```json { "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: ```json { "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: ```json { "error": "https_required" } ``` Example 403 response: insufficient_scope: ```json { "error": "insufficient_scope", "error_description": "This credential lacks the write:applications scope" } ``` Example 403 response: run_as_disabled: ```json { "error": "run_as_disabled", "error_description": "This credential has no enabled run-as Administrator" } ``` Example 403 response: forbidden: ```json { "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: ```json { "error": "not_found" } ``` Example 404 response: module_disabled: ```json { "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: ```json { "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: ```json { "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: ```json { "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: ```json { "error": "idempotency_key_reused", "error_description": "This Idempotency-Key was used for a different request" } ``` Example 422 response: unknown_field: ```json { "error": "unknown_field", "error_description": "bank_account_number is not a writable field" } ``` Example 422 response: validation_failed: ```json { "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: ```json { "error": "rate_limited", "error_description": "Too many requests" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "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: ```json { "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: ```bash curl "https://your-org.backed.giving/api/v2/milestones/42" \ -H "Authorization: Bearer $BACKED_TOKEN" ``` ### Parameters Parameters of GET /api/v2/milestones/{id}: | Name | In | Required | Type | Description | | --- | --- | --- | --- | --- | | `id` | path | Yes | `integer` | The record id. Anything but a positive integer is 404. Example: `42`. | ### 200 response The milestone. Fields of Milestone: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | `integer` | Yes | | | `created_at` | `string` | Yes | | | `updated_at` | `string` | Yes | | | `title` | `string` | Yes | | | `contract_id` | `integer \| null` | Yes | | | `funding_amount` | `string` | Yes | | | `description` | `string \| null` | Yes | | | `state` | `string` | Yes | One of `not_submitted`, `pending_review`, `submitted_to_verifier`, `submitted`, `resubmission_requested`, `approved`, `finance_approved`. | | `paid_at` | `string \| null` | Yes | | | `decline_reason` | `string \| null` | Yes | | | `due_date` | `string` | Yes | | | `contract` | `object \| null` | Yes | | | `lock_version` | `integer` | Yes | | | `deliverables_template_entry` | `object` | Yes | | Example 200 response: ```json { "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: ```json { "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: ```json { "error": "https_required" } ``` ### 404 response An unknown host or path, or an id that is unknown, deleted or of another Organisation. Example 404 response: ```json { "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: ```json { "error": "rate_limited", "error_description": "Too many requests" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "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: ```json { "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: ```bash 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: | Name | In | Required | Type | Description | | --- | --- | --- | --- | --- | | `id` | path | Yes | `integer` | The record id. Anything but a positive integer is 404. Example: `42`. | | `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 milestone as the single read answers it. Headers: `Idempotent-Replayed`. Fields of Milestone: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | `integer` | Yes | | | `created_at` | `string` | Yes | | | `updated_at` | `string` | Yes | | | `title` | `string` | Yes | | | `contract_id` | `integer \| null` | Yes | | | `funding_amount` | `string` | Yes | | | `description` | `string \| null` | Yes | | | `state` | `string` | Yes | One of `not_submitted`, `pending_review`, `submitted_to_verifier`, `submitted`, `resubmission_requested`, `approved`, `finance_approved`. | | `paid_at` | `string \| null` | Yes | | | `decline_reason` | `string \| null` | Yes | | | `due_date` | `string` | Yes | | | `contract` | `object \| null` | Yes | | | `lock_version` | `integer` | Yes | | | `deliverables_template_entry` | `object` | Yes | | Example 200 response: ```json { "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: ```json { "error": "idempotency_key_required", "error_description": "A write needs an Idempotency-Key header of 1 to 255 characters" } ``` Example 400 response: invalid_request: ```json { "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: ```json { "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: ```json { "error": "https_required" } ``` Example 403 response: insufficient_scope: ```json { "error": "insufficient_scope", "error_description": "This credential lacks the write:applications scope" } ``` Example 403 response: run_as_disabled: ```json { "error": "run_as_disabled", "error_description": "This credential has no enabled run-as Administrator" } ``` Example 403 response: forbidden: ```json { "error": "forbidden", "error_description": "The run-as Administrator may not do this to this record" } ``` Example 403 response: approval_limit_exceeded: ```json { "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: ```json { "error": "not_found" } ``` Example 404 response: module_disabled: ```json { "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: ```json { "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: ```json { "error": "invalid_transition", "error_description": "The record cannot make this move from approved", "current_status": "approved" } ``` Example 409 response: invalid_state: ```json { "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: ```json { "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: ```json { "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: ```json { "error": "idempotency_key_reused", "error_description": "This Idempotency-Key was used for a different request" } ``` Example 422 response: unknown_field: ```json { "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: ```json { "error": "rate_limited", "error_description": "Too many requests" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "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: ```json { "error": "temporarily_unavailable" } ``` --- Source: https://backed.giving/developers/reference/payments.md # Payments Payment batches. 1 operation: parameters, request body and answers, with examples. - [POST /api/v2/payments/{id}/mark\_paid](#markPaymentPaid) — Mark a payment batch paid. ## POST /api/v2/payments/{id}/mark_paid **Mark a payment batch paid.** `Mark as Paid` on the payment batch (`payments.mark_paid`), the Paid Date written to each payment it marks. Needs the Payments module. - Operation ID: `markPaymentPaid` - 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: ```bash curl -X POST "https://your-org.backed.giving/api/v2/payments/42/mark_paid" \ -H "Authorization: Bearer $BACKED_TOKEN" \ -H "Idempotency-Key: 7d1f0c2e-5b8a-4f3e-9c61-2a4b8e9d0f13" \ -H "Content-Type: application/json" \ -d '{"paid_date":"2026-11-09","reference":"BNZ-20261109-0042"}' ``` ### Parameters Parameters of POST /api/v2/payments/{id}/mark_paid: | Name | In | Required | Type | Description | | --- | --- | --- | --- | --- | | `id` | path | Yes | `integer` | The record id. Anything but a positive integer is 404. Example: `42`. | | `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 `PaymentMarkPaid` schema. `Mark as Paid` on a payment batch: the Paid Date and the bank reference. Fields of the PaymentMarkPaid body: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `paid_date` | `string` | Yes | | | `reference` | `string` | Yes | | Example request body: ```json { "paid_date": "2026-11-09", "reference": "BNZ-20261109-0042" } ``` ### 200 response The batch as the mark left it. Headers: `Idempotent-Replayed`. Fields of PaymentMarkedPaid: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | `integer` | Yes | | | `status` | `string` | Yes | Always `paid`. | | `marked_paid_at` | `string` | Yes | | | `paid_date` | `string` | Yes | | | `reference` | `string` | Yes | | | `payment_count` | `integer` | Yes | | | `amount_total` | `string` | Yes | | Example 200 response: ```json { "id": 315, "status": "paid", "marked_paid_at": "2026-11-09T02:30:00.000Z", "paid_date": "2026-11-09", "reference": "BNZ-20261109-0042", "payment_count": 12, "amount_total": "182500.0" } ``` ### 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: ```json { "error": "idempotency_key_required", "error_description": "A write needs an Idempotency-Key header of 1 to 255 characters" } ``` Example 400 response: invalid_request: ```json { "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: ```json { "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: ```json { "error": "https_required" } ``` Example 403 response: insufficient_scope: ```json { "error": "insufficient_scope", "error_description": "This credential lacks the write:applications scope" } ``` Example 403 response: run_as_disabled: ```json { "error": "run_as_disabled", "error_description": "This credential has no enabled run-as Administrator" } ``` Example 403 response: forbidden: ```json { "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: ```json { "error": "not_found" } ``` Example 404 response: module_disabled: ```json { "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. Headers: `Retry-After`. Example 409 response: idempotency_in_progress: ```json { "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: ```json { "error": "invalid_transition", "error_description": "The record cannot make this move from approved", "current_status": "approved" } ``` ### 413 response The body, or the declared file, is larger than the limit that applies. Example 413 response: ```json { "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: ```json { "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: ```json { "error": "idempotency_key_reused", "error_description": "This Idempotency-Key was used for a different request" } ``` Example 422 response: unknown_field: ```json { "error": "unknown_field", "error_description": "bank_account_number is not a writable field" } ``` Example 422 response: validation_failed: ```json { "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: ```json { "error": "rate_limited", "error_description": "Too many requests" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "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: ```json { "error": "temporarily_unavailable" } ``` --- Source: https://backed.giving/developers/reference/file-inputs.md # File Inputs Downloads of files held by application and deliverable inputs. 1 operation: parameters, request body and answers, with examples. - [GET /api/v2/file\_inputs/{id}](#downloadFileInput) — Download the file an input holds. ## GET /api/v2/file_inputs/{id} **Download the file an input holds.** The raw bytes of a live application’s file answer or a live milestone’s deliverable file, as `tahua_file_url` names it. A file still being scanned, infected or disposed is 404. - Operation ID: `downloadFileInput` - Authentication: A Bearer token from a credential holding the `read` scope. - Also served at `/funding_system/api/v2/file_inputs/{id}`, the documented product’s alias path. Example request: ```bash curl "https://your-org.backed.giving/api/v2/file_inputs/55121" \ -H "Authorization: Bearer $BACKED_TOKEN" ``` ### Parameters Parameters of GET /api/v2/file_inputs/{id}: | Name | In | Required | Type | Description | | --- | --- | --- | --- | --- | | `id` | path | Yes | `integer` | The id of the file input to download, as its `tahua_file_url` names it. Example: `55121`. | ### 200 response The file’s bytes, with its own content type. Headers: `Content-Disposition`, `Content-Length`, `Cache-Control`. The body is the raw bytes (`*/*`), not JSON. ### 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: ```json { "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: ```json { "error": "https_required" } ``` ### 404 response An unknown host or path, or an id that is unknown, deleted or of another Organisation. Example 404 response: ```json { "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: ```json { "error": "rate_limited", "error_description": "Too many requests" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "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: ```json { "error": "temporarily_unavailable" } ``` --- Source: https://backed.giving/developers/reference/files.md # Files The three-step file upload. 3 operations: parameters, request body and answers, with examples. - [POST /api/v2/files](#reserveFileUpload) — Reserve a file upload. - [POST /api/v2/files/{id}/attach](#attachFileUpload) — Attach a scanned file to its target. - [POST /api/v2/files/{id}/confirm](#confirmFileUpload) — 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:files` scope. Runs as the credential’s Administrator, and needs an `Idempotency-Key` header. Example request: ```bash 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 Parameters of POST /api/v2/files: | 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. Fields of the FileUploadRequest body: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `filename` | `string` | Yes | | | `content_type` | `string` | Yes | | | `size` | `integer` | Yes | | | `target` | `object` | Yes | | Example request body: ```json { "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`. Fields of FileUploadReserved: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | `string` | Yes | | | `upload` | `object` | Yes | | | `expires_at` | `string` | Yes | | | `max_bytes` | `integer` | Yes | | | `state` | `string` | Yes | Always `awaiting_upload`. | Example 201 response: ```json { "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. Example 400 response: idempotency_key_required: ```json { "error": "idempotency_key_required", "error_description": "A write needs an Idempotency-Key header of 1 to 255 characters" } ``` Example 400 response: invalid_request: ```json { "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: ```json { "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: ```json { "error": "https_required" } ``` Example 403 response: insufficient_scope: ```json { "error": "insufficient_scope", "error_description": "This credential lacks the write:applications scope" } ``` Example 403 response: run_as_disabled: ```json { "error": "run_as_disabled", "error_description": "This credential has no enabled run-as Administrator" } ``` Example 403 response: forbidden: ```json { "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. Example 404 response: ```json { "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`. Example 409 response: idempotency_in_progress: ```json { "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_state: ```json { "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: ```json { "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: ```json { "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: ```json { "error": "idempotency_key_reused", "error_description": "This Idempotency-Key was used for a different request" } ``` Example 422 response: unknown_field: ```json { "error": "unknown_field", "error_description": "bank_account_number is not a writable field" } ``` Example 422 response: validation_failed: ```json { "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: ```json { "error": "rate_limited", "error_description": "Too many requests" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "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: ```json { "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:files` scope. Runs as the credential’s Administrator, and needs an `Idempotency-Key` header. Example request: ```bash 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 Parameters of POST /api/v2/files/{id}/attach: | 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`. Fields of FileAttached: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | `integer` | Yes | | | `state` | `string` | Yes | Always `attached`. | | `target` | `object` | Yes | | Example 200 response: ```json { "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. Example 400 response: ```json { "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`. Example 401 response: ```json { "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: ```json { "error": "https_required" } ``` Example 403 response: insufficient_scope: ```json { "error": "insufficient_scope", "error_description": "This credential lacks the write:applications scope" } ``` Example 403 response: run_as_disabled: ```json { "error": "run_as_disabled", "error_description": "This credential has no enabled run-as Administrator" } ``` Example 403 response: forbidden: ```json { "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. Example 404 response: ```json { "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`. Example 409 response: idempotency_in_progress: ```json { "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: file_not_scanned: ```json { "error": "file_not_scanned", "error_description": "The file has not finished its malware scan" } ``` Example 409 response: invalid_state: ```json { "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: ```json { "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: ```json { "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. Example 422 response: idempotency_key_reused: ```json { "error": "idempotency_key_reused", "error_description": "This Idempotency-Key was used for a different request" } ``` Example 422 response: file_infected: ```json { "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`. Example 429 response: ```json { "error": "rate_limited", "error_description": "Too many requests" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "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: ```json { "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:files` scope. Runs as the credential’s Administrator, and needs an `Idempotency-Key` header. Example request: ```bash 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 Parameters of POST /api/v2/files/{id}/confirm: | 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`. Fields of FileConfirmed: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `id` | `string` | Yes | | | `state` | `string` | Yes | Always `scanning`. | Example 200 response: ```json { "id": "upl_3Jq9xVb2LmT8sKp4Rz7Wn1Ye", "state": "scanning" } ``` ### 400 response The write has no Idempotency-Key header of 1 to 255 characters. Example 400 response: ```json { "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`. Example 401 response: ```json { "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: ```json { "error": "https_required" } ``` Example 403 response: insufficient_scope: ```json { "error": "insufficient_scope", "error_description": "This credential lacks the write:applications scope" } ``` Example 403 response: run_as_disabled: ```json { "error": "run_as_disabled", "error_description": "This credential has no enabled run-as Administrator" } ``` Example 403 response: forbidden: ```json { "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. Example 404 response: ```json { "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`. Example 409 response: idempotency_in_progress: ```json { "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_state: ```json { "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: ```json { "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: ```json { "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. Example 422 response: ```json { "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`. Example 429 response: ```json { "error": "rate_limited", "error_description": "Too many requests" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "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: ```json { "error": "temporarily_unavailable" } ``` --- Source: https://backed.giving/developers/reference/reference.md # Reference This document. 1 operation: parameters, request body and answers, with examples. - [GET /api/v2/openapi.json](#documentApiOpenApi) — This OpenAPI 3.1 document. ## GET /api/v2/openapi.json **This OpenAPI 3.1 document.** The same document on every Organisation host, with that host as its server. No credential is needed. - Operation ID: `documentApiOpenApi` - Authentication: None: no credential is needed. Example request: ```bash curl "https://your-org.backed.giving/api/v2/openapi.json" ``` ### 200 response The OpenAPI 3.1 document. Example 200 response: ```json { "openapi": "3.1.0", "info": { "title": "Backed Public API", "version": "2" } } ``` ### 403 response The request was sent over plain HTTP and was not processed. Example 403 response: ```json { "error": "https_required" } ``` ### 404 response An unknown host or path, or an id that is unknown, deleted or of another Organisation. Example 404 response: ```json { "error": "not_found" } ``` ### 500 response An unexpected server error. Example 500 response: ```json { "error": "server_error" } ``` --- Source: https://backed.giving/developers/events.md # Event catalogue Every webhook event type, with its record type, version and meaning, and the delivery payload with an example. Every event type a webhook endpoint can subscribe to, with its version and what it means. It is the same list as the `Event catalogue` on the `Webhooks` tab in Backed. An endpoint receives only your organisation’s events, and only the types it is subscribed to. How to check each delivery is in [Webhook signature verification](https://backed.giving/developers/guides/webhook-signatures.md). ## The payload Each delivery is a `POST` with this JSON body and the `Backed-Signature`, `Backed-Event-Id` and `Backed-Event-Type` headers. The body is a reference, not a copy of the record: it never carries form answers, bank details or contact details. Read the record through the API at `data.url` for those. Payload fields: | Field | Type | What it holds | | --- | --- | --- | | `id` | `string` | The event’s id, as the `Backed-Event-Id` header. The same on every retry and replay. | | `type` | `string` | The event type, as the `Backed-Event-Type` header. | | `version` | `integer` | The event type’s version. | | `occurred_at` | `string` | When it happened, in UTC. | | `organisation` | `string` | Your organisation’s id. | | `data` | `object` | The record the event is about. | | `data.record_type` | `string` | The record’s type: the catalogue’s `Record type`. | | `data.record_id` | `string` | The record’s id. | | `data.url` | `string \| null` | The record’s address in the read API on your organisation’s host, or `null` where the API serves no such record. | Example payload: ```json { "id": "918273", "type": "rounds.funding_round.published", "version": 1, "occurred_at": "2026-11-09T01:00:00.000Z", "organisation": "17", "data": { "record_type": "funding_round", "record_id": "42", "url": "https://your-org.backed.giving/api/v2/funding_rounds/42" } } ``` ## Event types 186 event types, by name. `webhook.test` is sent only by `Send Test` on an endpoint’s page. Event types: | Event type | Record type | Version | What it means | | --- | --- | --- | --- | | `applications.collaborator.added` | `funding_application` | 1 | Collaborator added. | | `applications.collaborator.invitation_completed` | `funding_application` | 1 | Collaborator invitation completed. | | `applications.collaborator.invited` | `funding_application` | 1 | Collaborator invited. | | `applications.collaborator.removed` | `funding_application` | 1 | Collaborator removed. | | `applications.duplicate_group.detected` | `duplicate_group` | 1 | Duplicate group detected. | | `applications.duplicate_group.resolved` | `duplicate_group` | 1 | Duplicate group resolved. | | `applications.eligibility_screening.decided` | `eligibility_screening` | 1 | Eligibility screening decided. | | `applications.eligibility_screening.retake_allowed` | `eligibility_screening` | 1 | Eligibility screening retake allowed. | | `applications.eligibility_screening.submitted` | `eligibility_screening` | 1 | Eligibility screening submitted. | | `applications.funding_application.approved` | `funding_application` | 1 | Funding application approved. | | `applications.funding_application.copied` | `funding_application` | 1 | Funding application copied. | | `applications.funding_application.decided` | `funding_application` | 1 | Funding application decided. | | `applications.funding_application.decision_revoked` | `funding_application` | 1 | Funding application decision revoked. | | `applications.funding_application.edit_request_approved` | `funding_application` | 1 | Funding application edit request approved. | | `applications.funding_application.edit_request_declined` | `funding_application` | 1 | Funding application edit request declined. | | `applications.funding_application.edit_request_withdrawn` | `funding_application` | 1 | Funding application edit request withdrawn. | | `applications.funding_application.edit_requested` | `funding_application` | 1 | Funding application edit requested. | | `applications.funding_application.eligibility_set` | `funding_application` | 1 | Funding application eligibility set. | | `applications.funding_application.information_request_closed` | `funding_application` | 1 | Funding application information request closed. | | `applications.funding_application.information_request_reminded` | `funding_application` | 1 | Funding application information request reminded. | | `applications.funding_application.information_requested` | `funding_application` | 1 | Funding application information requested. | | `applications.funding_application.location_inputs_changed` | `funding_application` | 1 | Funding application location inputs changed. | | `applications.funding_application.offer_answered` | `funding_application` | 1 | Funding application offer answered. | | `applications.funding_application.payment_status_changed` | `funding_application` | 1 | Funding application payment status changed. | | `applications.funding_application.stale_draft_reminded` | `funding_application` | 1 | Funding application stale draft reminded. | | `applications.funding_application.started` | `funding_application` | 1 | Funding application started. | | `applications.funding_application.submitted` | `funding_application` | 1 | Funding application submitted. | | `applications.funding_application.submitted_to_verifier` | `funding_application` | 1 | Funding application submitted to verifier. | | `applications.funding_application.tagged_answers_changed` | `funding_application` | 1 | Funding application tagged answers changed. | | `applications.funding_application.unlocked` | `funding_application` | 1 | Funding application unlocked. | | `applications.funding_application.verifier_assigned` | `funding_application` | 1 | Funding application verifier assigned. | | `applications.funding_application.withdrawal_countdown_cancelled` | `funding_application` | 1 | Funding application withdrawal countdown cancelled. | | `applications.funding_application.withdrawn` | `funding_application` | 1 | Funding application withdrawn. | | `applications.late_access.granted` | `late_access_grant` | 1 | Late access granted. | | `applications.referee_request.completed` | `referee_request` | 1 | Referee request completed. | | `applications.referee_request.declined` | `referee_request` | 1 | Referee request declined. | | `applications.referee_request.expired` | `referee_request` | 1 | Referee request expired. | | `assessments.assessment.acceptance_reminder_due` | `assessment` | 1 | Assessment acceptance reminder due. | | `assessments.assessment.assigned` | `assessment` | 1 | Assessment assigned. | | `assessments.assessment.declined` | `assessment` | 1 | Assessment declined. | | `assessments.assessment.removed` | `assessment` | 1 | Assessment removed. | | `assessments.assessment.reopened` | `assessment` | 1 | Assessment reopened. | | `assessments.assessment.score_revised` | `assessment` | 1 | Assessment score revised. | | `assessments.assessment.started` | `assessment` | 1 | Assessment started. | | `assessments.assessment.submitted` | `assessment` | 1 | Assessment submitted. | | `assessments.assessment_round.created` | `assessment_round` | 1 | Assessment round created. | | `assessments.assessment_round.published` | `assessment_round` | 1 | Assessment round published. | | `assessments.assessment_share.items_changed` | `assessment_share` | 1 | Assessment share items changed. | | `assessments.assessment_share.published` | `assessment_share` | 1 | Assessment share published. | | `assessments.capacity_assessment.reconciled` | `capacity_assessment` | 1 | Capacity assessment reconciled. | | `assessments.capacity_assessment.started` | `capacity_assessment` | 1 | Capacity assessment started. | | `assessments.capacity_assessment.submitted` | `capacity_assessment` | 1 | Capacity assessment submitted. | | `assessments.conflict.confirmed` | `assessment` | 1 | Conflict confirmed. | | `assessments.conflict.revoked` | `assessment` | 1 | Conflict revoked. | | `assessments.decision.accepted` | `assessment_decision` | 1 | Decision accepted. | | `assessments.score_recalculation_job.succeeded` | `score_recalculation_job` | 1 | Score recalculation job succeeded. | | `contracts.contract.amount_changed` | `contract` | 1 | Contract amount changed. | | `contracts.contract.completed` | `contract` | 1 | Contract completed. | | `contracts.contract.created` | `contract` | 1 | Contract created. | | `contracts.contract.dates_changed` | `contract` | 1 | Contract dates changed. | | `contracts.contract.declined` | `contract` | 1 | Contract declined. | | `contracts.contract.review_passed` | `contract` | 1 | Contract review passed. | | `contracts.contract.review_requested` | `contract` | 1 | Contract review requested. | | `contracts.contract.reviewer_changed` | `contract` | 1 | Contract reviewer changed. | | `contracts.contract.sent` | `contract` | 1 | Contract sent. | | `contracts.contract.signed_by_admin` | `contract` | 1 | Contract signed by admin. | | `contracts.contract.signed_by_applicant_verifier` | `contract` | 1 | Contract signed by applicant verifier. | | `contracts.contract.tags_changed` | `contract` | 1 | Contract tags changed. | | `contracts.contract.terminated` | `contract` | 1 | Contract terminated. | | `contracts.contract.termination_reverted` | `contract` | 1 | Contract termination reverted. | | `contracts.contract.termination_started` | `contract` | 1 | Contract termination started. | | `contracts.contract.variation_created` | `contract` | 1 | Contract variation created. | | `contracts.contract.withdrawal_reverted` | `contract` | 1 | Contract withdrawal reverted. | | `contracts.contract.withdrawal_started` | `contract` | 1 | Contract withdrawal started. | | `contracts.contract.withdrawn` | `contract` | 1 | Contract withdrawn. | | `contracts.contract_letter.created` | `contract_letter` | 1 | Contract letter created. | | `contracts.contract_letter.reverted` | `contract_letter` | 1 | Contract letter reverted. | | `contracts.contract_letter.review_requested` | `contract_letter` | 1 | Contract letter review requested. | | `contracts.contract_letter.sent` | `contract_letter` | 1 | Contract letter sent. | | `contracts.contract_obligation.met` | `contract_obligation` | 1 | Contract obligation met. | | `contracts.contract_obligation.overdue` | `contract_obligation` | 1 | Contract obligation overdue. | | `contracts.contract_obligation.waived` | `contract_obligation` | 1 | Contract obligation waived. | | `contracts.contract_request.approved` | `contract_request` | 1 | Contract request approved. | | `contracts.contract_request.contact_switched` | `contract_request` | 1 | Contract request contact switched. | | `contracts.contract_request.declined` | `contract_request` | 1 | Contract request declined. | | `contracts.contract_request.information_requested` | `contract_request` | 1 | Contract request information requested. | | `contracts.contract_request.resubmitted` | `contract_request` | 1 | Contract request resubmitted. | | `contracts.contract_request.submitted` | `contract_request` | 1 | Contract request submitted. | | `contracts.contract_signatory.declined` | `contract_signatory` | 1 | Contract signatory declined. | | `contracts.contract_signatory.offered` | `contract_signatory` | 1 | Contract signatory offered. | | `contracts.contract_signatory.reset` | `contract_signatory` | 1 | Contract signatory reset. | | `contracts.contract_signatory.signed` | `contract_signatory` | 1 | Contract signatory signed. | | `decisions.appeal.assigned` | `appeal` | 1 | Appeal assigned. | | `decisions.appeal.resolved` | `appeal` | 1 | Appeal resolved. | | `decisions.appeal.submitted` | `appeal` | 1 | Appeal submitted. | | `decisions.condition.approval_undone` | `condition` | 1 | Condition approval undone. | | `decisions.condition.approved` | `condition` | 1 | Condition approved. | | `decisions.condition.attached` | `condition` | 1 | Condition attached. | | `decisions.condition.resubmission_requested` | `condition` | 1 | Condition resubmission requested. | | `decisions.condition.submitted` | `condition` | 1 | Condition submitted. | | `decisions.decision.created` | `decision` | 1 | Decision created. | | `decisions.decision.review_forwarded` | `decision` | 1 | Decision review forwarded. | | `decisions.decision.review_requested` | `decision` | 1 | Decision review requested. | | `decisions.decision.reviewer_changed` | `decision` | 1 | Decision reviewer changed. | | `decisions.decision.sent` | `decision` | 1 | Decision sent. | | `decisions.decision_line.letter_failed` | `decision_line` | 1 | Decision line letter failed. | | `decisions.decision_line.letter_issued` | `decision_line` | 1 | Decision line letter issued. | | `decisions.decision_line.letter_resent` | `decision_line` | 1 | Decision line letter resent. | | `decisions.decision_line.revoked` | `decision_line` | 1 | Decision line revoked. | | `decisions.decision_line.sent` | `decision_line` | 1 | Decision line sent. | | `decisions.grant_publication.due` | `grant_publication` | 1 | Grant publication due. | | `decisions.grant_publication.excluded` | `grant_publication` | 1 | Grant publication excluded. | | `decisions.grant_publication.exported` | `grant_publication` | 1 | Grant publication exported. | | `decisions.offer.accepted` | `offer_response` | 1 | Offer accepted. | | `decisions.offer.declined` | `offer_response` | 1 | Offer declined. | | `decisions.offer.institution_accepted` | `offer_response` | 1 | Offer institution accepted. | | `decisions.offer.institution_declined` | `offer_response` | 1 | Offer institution declined. | | `funds.allocation.changed` | `funding_application` | 1 | Allocation changed. | | `funds.attribution.changed` | `funding_application` | 1 | Attribution changed. | | `funds.batch.generated` | `payment_batch` | 1 | Batch generated. | | `funds.distribution_amount.changed` | `fund` | 1 | Distribution amount changed. | | `funds.distribution_cycle.completed` | `fund_distribution_cycle` | 1 | Distribution cycle completed. | | `funds.fund.created` | `fund` | 1 | Fund created. | | `funds.fund.deleted` | `fund` | 1 | Fund deleted. | | `funds.fund.donor_report_published` | `fund` | 1 | Fund donor report published. | | `funds.fund.grant_recommendation_submitted` | `fund` | 1 | Fund grant recommendation submitted. | | `funds.fund.updated` | `fund` | 1 | Fund updated. | | `funds.fund_csv_import.apply_requested` | `fund_csv_import` | 1 | Fund csv import apply requested. | | `funds.grant_recommendation.decided` | `grant_recommendation` | 1 | Grant recommendation decided. | | `milestones.milestone.approval_undone` | `milestone` | 1 | Milestone approval undone. | | `milestones.milestone.approved` | `milestone` | 1 | Milestone approved. | | `milestones.milestone.contract_reordered` | `milestone` | 1 | Milestone contract reordered. | | `milestones.milestone.created` | `milestone` | 1 | Milestone created. | | `milestones.milestone.details_changed` | `milestone` | 1 | Milestone details changed. | | `milestones.milestone.finance_approval_reverted` | `milestone` | 1 | Milestone finance approval reverted. | | `milestones.milestone.finance_approved` | `milestone` | 1 | Milestone finance approved. | | `milestones.milestone.forwarded_to_funder` | `milestone` | 1 | Milestone forwarded to funder. | | `milestones.milestone.linked_to_contract` | `milestone` | 1 | Milestone linked to contract. | | `milestones.milestone.payment_status_changed` | `milestone` | 1 | Milestone payment status changed. | | `milestones.milestone.resubmission_requested` | `milestone` | 1 | Milestone resubmission requested. | | `milestones.milestone.reviewer_assigned` | `milestone` | 1 | Milestone reviewer assigned. | | `milestones.milestone.share_access_changed` | `milestone` | 1 | Milestone share access changed. | | `milestones.milestone.submitted` | `milestone` | 1 | Milestone submitted. | | `milestones.milestone.submitted_to_verifier` | `milestone` | 1 | Milestone submitted to verifier. | | `milestones.milestone.tag_reselected` | `milestone` | 1 | Milestone tag reselected. | | `milestones.milestone_batch.created` | `milestone_batch` | 1 | Milestone batch created. | | `milestones.milestone_batch.deleted` | `milestone_batch` | 1 | Milestone batch deleted. | | `milestones.milestone_batch.sent` | `milestone_batch` | 1 | Milestone batch sent. | | `milestones.milestone_template.created` | `milestone_template` | 1 | Milestone template created. | | `milestones.milestones.progressed` | `milestone` | 1 | Milestones progressed. | | `milestones.milestones.published` | `milestone` | 1 | Milestones published. | | `payments.finance_decision.recorded` | `payment_batch` | 1 | Finance decision recorded. | | `payments.payment.deleted` | `payment` | 1 | Payment deleted. | | `payments.payment.edited` | `payment` | 1 | Payment edited. | | `payments.payment.marked_paid` | `payment` | 1 | Payment marked paid. | | `payments.payment.refund_received` | `payment` | 1 | Payment refund received. | | `payments.payment.voided` | `payment` | 1 | Payment voided. | | `payments.payment_batch.approved` | `payment_batch` | 1 | Payment batch approved. | | `payments.payment_batch.created` | `payment_batch` | 1 | Payment batch created. | | `payments.payment_batch.deleted` | `payment_batch` | 1 | Payment batch deleted. | | `payments.payment_batch.marked_paid` | `payment_batch` | 1 | Payment batch marked paid. | | `payments.payment_batch.recipient_approved` | `payment_batch` | 1 | Payment batch recipient approved. | | `payments.payment_batch.resubmitted` | `payment_batch` | 1 | Payment batch resubmitted. | | `payments.payment_batch.returned_to_draft` | `payment_batch` | 1 | Payment batch returned to draft. | | `payments.payment_batch.review_requested` | `payment_batch` | 1 | Payment batch review requested. | | `payments.payment_batch.reviewer_reassigned` | `payment_batch` | 1 | Payment batch reviewer reassigned. | | `payments.payment_letter.sent` | `payment_letter` | 1 | Payment letter sent. | | `payments.repayment.requested` | `payment_repayment` | 1 | Repayment requested. | | `registration.entity_domain.verified` | `registration_entity_domain` | 1 | Entity domain verified. | | `registration.entity_duplicate.detected` | `entity_duplicate_pair` | 1 | Entity duplicate detected. | | `registration.entity_join_request.created` | `entity_join_request` | 1 | Entity join request created. | | `registration.entity_join_request.decided` | `entity_join_request` | 1 | Entity join request decided. | | `registration.entity_member.invited` | `registration_entity` | 1 | Entity member invited. | | `registration.registration.approved` | `registration` | 1 | Registration approved. | | `registration.registration.details_changed` | `registration` | 1 | Registration details changed. | | `registration.registration.resubmission_requested` | `registration` | 1 | Registration resubmission requested. | | `registration.registration.submitted` | `registration` | 1 | Registration submitted. | | `registration.registration_entity.bank_account_changed` | `registration_entity` | 1 | Registration entity bank account changed. | | `registration.transfer.completed` | `registration_entity` | 1 | Transfer completed. | | `rounds.funding_round.closed` | `funding_round` | 1 | Funding round closed. | | `rounds.funding_round.listing_changed` | `funding_round` | 1 | Funding round listing changed. | | `rounds.funding_round.made_live` | `funding_round` | 1 | Funding round made live. | | `rounds.funding_round.published` | `funding_round` | 1 | Funding round published. | | `rounds.funding_round.unpublished` | `funding_round` | 1 | Funding round unpublished. | | `rounds.private_grant_invitee.invited` | `private_grant_invitee` | 1 | Private grant invitee invited. | | `webhook.test` | `webhook_endpoint` | 1 | A test event sent by Send test event; never sent otherwise. | --- Source: https://backed.giving/developers/changelog.md # Changelog Every change to the Backed API, newest first, with its date and version; a breaking change is published only under a new version path. Every change to the Backed API, newest first, each dated by the day it reached production. The current version is `/api/v2/`. > A breaking change is published only under a new version path: the version you use keeps working as it is. How long a superseded version stays, and how you are told, is in [Versions and deprecation](https://backed.giving/developers/guides/versioning.md). ## 2026-11-09 · OpenAPI document and developer site - Date: `2026-11-09` - Version: `/api/v2/` - Breaking: no. Every Organisation host serves this OpenAPI 3.1 document at /api/v2/openapi.json, and the developer site at https://backed.giving/developers publishes the reference, the guides, the event catalogue and this changelog. ## 2026-11-09 · Plain HTTP refused - Date: `2026-11-09` - Version: `/api/v2/` - Breaking: no. A request that reaches the API over plain HTTP is answered 403 https\_required and is not processed, on every route, the token endpoint included. ## 2026-11-09 · Webhooks and the OData reporting feed - Date: `2026-11-09` - Version: `/api/v2/` - Breaking: no. Signed webhook deliveries of the event catalogue to the Organisation's endpoints, and the OData v4 feed at /odata/v4 for Power BI, Tableau and warehouse loaders (scope read:reporting\_feed). ## 2026-11-07 · Write API - Date: `2026-11-07` - Version: `/api/v2/` - Breaking: no. Registration entities, application transitions, notes and tags, assessor assignments, milestones, payment batches, contract statuses and file uploads, each under a write scope and an Idempotency-Key, run as the credential’s Administrator. ## 2026-10-29 · Milestones - Date: `2026-10-29` - Version: `/api/v2/` - Breaking: no. GET /api/v2/milestones and /api/v2/milestones/{id}, with each Milestone’s deliverables entry and the filters\[…\] parameters. ## 2026-10-22 · Registration entities - Date: `2026-10-22` - Version: `/api/v2/` - Breaking: no. GET /api/v2/registration\_entities and /api/v2/registration\_entities/{id}; the single record carries its users. ## 2026-10-12 · Contracts - Date: `2026-10-12` - Version: `/api/v2/` - Breaking: no. GET /api/v2/contracts and /api/v2/contracts/{id}. ## 2026-10-01 · Funding rounds, funding applications and file inputs - Date: `2026-10-01` - Version: `/api/v2/` - Breaking: no. The read-only v2 resources: GET /api/v2/funding\_rounds, /api/v2/funding\_applications (with extra\_methods\[\]) and their single records, and the file download GET /api/v2/file\_inputs/{id}. ## 2026-09-30 · Token endpoint - Date: `2026-09-30` - Version: `/api/v2/` - Breaking: no. POST /oauth/token issues 5-day Bearer tokens by the OAuth 2.0 client-credentials grant. --- Source: https://backed.giving/developers/guides.md # Guides Every developer guide, with a line on what each covers. - [Authentication](https://backed.giving/developers/guides/authentication.md): Get a 5-day Bearer token from a credential with the OAuth 2.0 client-credentials grant, send it on every request, and choose the scopes a credential holds. - [Pagination](https://backed.giving/developers/guides/pagination.md): Read a long list 50 records at a time with the limit, gt and lt parameters, in ascending id order. - [Errors](https://backed.giving/developers/guides/errors.md): What every error code means, the status it comes with, and which ones are safe to retry. - [Idempotency](https://backed.giving/developers/guides/idempotency.md): Make every write safe to retry with an Idempotency-Key, and update a record at the version you read with If-Match. - [Webhook signature verification](https://backed.giving/developers/guides/webhook-signatures.md): Check the Backed-Signature header on every webhook delivery with HMAC-SHA256, in Node.js or Python, and answer in time. - [Connect Power BI](https://backed.giving/developers/guides/power-bi.md): Load the OData reporting feed into Power BI Desktop, with Basic authentication or a token query, and refresh it on a schedule. - [Connect Tableau](https://backed.giving/developers/guides/tableau.md): Connect Tableau Desktop to the OData reporting feed with the credential as a user name and password, and load it into other tools and warehouses. - [Versions and deprecation](https://backed.giving/developers/guides/versioning.md): Which version is current, why a breaking change only ever comes under a new version path, and how long a superseded version stays.