> This is the Markdown of a Backed Help Centre page, https://admin.backed.giving/help/integrations/api-c02-write-api-part-one-registrations-applications-no.md. Every page is listed at https://admin.backed.giving/help/llms.txt.

> This page uses Backed's default terms, such as Funding Round and Application Template. An organisation may rename them, so read each as your organisation's own term if you've renamed it.

This page is for your organisation's developers. It lists the Backed API routes that create or
change registrations, applications, notes, tags and assessor assignments, and what each one refuses.
For milestones, payments, contracts and files, see
[Write API: milestones, payments, contracts and files](api-c03-write-api-part-two-milestones-payments-contracts.md).

No write route reads or changes a bank account number. A `bank_account_number` in a request body is
refused with `422 unknown_field`, and no write response carries one.

## Before you start

A credential can only read until you give it a write scope and an Administrator to act as:

1. Go to `Settings > Profile and terms` and open the `API Access` tab.
2. Select `Edit scopes` on the credential's row, or `New Credential` for a new one.
3. Tick the scopes the system needs. See [API access](api-access.md#scopes).
4. Choose the Administrator it acts as under `Acts as`. This needs `Can edit permissions`.
5. Select `Save`.

## The six scopes

| Scope                 | Label on the tab           | Opens                                                      |
| --------------------- | -------------------------- | ---------------------------------------------------------- |
| `read`                | `Read`                     | Every read route. Every credential has it.                 |
| `write:registrations` | `Write registrations`      | The registration routes on this page.                      |
| `write:applications`  | `Write applications`       | The transition, note and tag routes on this page.          |
| `write:assessments`   | `Write assessments`        | The assignment route on this page.                         |
| `write:post_award`    | `Write post-award records` | Milestones, payments and contracts, on the part two page.  |
| `write:files`         | `Write files`              | Uploading and attaching files, on the part two page.       |

## Who a write runs as

Every write runs with the permissions and data access rules of the credential's `Acts as`
Administrator, so it can't do anything they couldn't do in Backed. It goes through the same checks,
sends the same messages and records the same audit entries as the action on the screen.

In the record's history a write shows the credential's name, then `Acting as` and the
Administrator's name.

If that Administrator is removed, disabled or no longer an Administrator, every write answers
`403 run_as_disabled` until you choose someone else. Reads carry on.

## Headers every write needs

Send your access token as a bearer token, the body as JSON, and these:

- **`Idempotency-Key`**, required on every `POST`, `PUT` and `PATCH`: 1 to 255 characters your system
  makes up, unique to that change.
  - The same key and the same body within 24 hours answers the first response again, with the header
    `Idempotent-Replayed: true`, and changes nothing.
  - The same key with a different body or path is refused with `422 idempotency_key_reused`.
  - No key is refused with `400 idempotency_key_required`.
  - A refused request stores nothing, so you can retry it with the same key.
  - Creating or updating an entity, and attaching a user, are reserved first. If the first request is still running
    or stopped partway, a retry answers `409 idempotency_in_progress` with `Retry-After: 30` and
    never writes twice. If it keeps answering that, check whether the record exists before using a
    new key; the key clears after 24 hours.
- **`If-Match`**, required on `PATCH`: the record's `lock_version` as you last read it, such as
  `If-Match: 7`. If it is missing or out of date the update is refused with
  `412 precondition_failed` and nothing changes. Read the record again, then retry.

## Routes

Every path is on your organisation's host. Each route can also give the refusals in
[Refusals every write can give](#refusals-every-write-can-give).

| Method  | Path                                           | Scope                 | Answers | Refusals of its own                                                                       |
| ------- | ---------------------------------------------- | --------------------- | ------- | ----------------------------------------------------------------------------------------- |
| `POST`  | `/api/v2/registration_entities`                | `write:registrations` | `201`   | `409 invalid_state`                                                                       |
| `PATCH` | `/api/v2/registration_entities/:id`            | `write:registrations` | `200`   | `404 not_found`, `412 precondition_failed`, `409 invalid_state`                           |
| `POST`  | `/api/v2/registration_entities/:id/users`      | `write:registrations` | `201`   | `404 not_found`, `409 invalid_state`                                                      |
| `POST`  | `/api/v2/funding_applications/:id/transitions` | `write:applications`  | `200`   | `404 not_found`, `409 invalid_transition`, `412 precondition_failed`                      |
| `POST`  | `/api/v2/funding_applications/:id/notes`       | `write:applications`  | `201`   | `404 not_found`, `409 invalid_state`                                                      |
| `PUT`   | `/api/v2/funding_applications/:id/tags`        | `write:applications`  | `200`   | `404 not_found`, `422 unknown_tag`, `409 invalid_state`                                   |
| `POST`  | `/api/v2/assessment_rounds/:id/assignments`    | `write:assessments`   | `201`   | `404 not_found`, `409 conflict_declared`, `409 invalid_state`                             |

Another organisation's id answers the same `404 not_found` as an id that was never issued. Where a
route gives `409 invalid_state`, the text says why the record can't take the change.

### Registrations

`POST /api/v2/registration_entities` does what `Add Applicant` does, and needs the same permission.
Send `registration_type_id`, `first_name`, `last_name` and `email`, plus `entity_name` for an
organisation. Send `invite: true` to invite the person now. By default nobody is told, and the
record reads `Unclaimed`. It answers with the record as `GET /api/v2/registration_entities/:id` reads
it.

`PATCH /api/v2/registration_entities/:id` updates an organisation's `founded_on`, `stage_override`
and `stage_override_reason`. Only the fields you send change.

`POST /api/v2/registration_entities/:id/users` adds a person by `email`, `first_name` and `last_name`.
A person who already has an account is attached, never duplicated. A new address gets an invitation,
never a password. It answers `registration_entity_id`, `user_id`, `email` and `invited`, which is
`false` when an account that has been claimed was added directly.

You can't write registration answers by Field Tag yet. A `fields` object is refused with
`422 unknown_field`, naming its first tag as `fields.<tag>`.

### Application transitions

`POST /api/v2/funding_applications/:id/transitions` takes a `transition`, an optional `lock_version`,
and that move's own fields. It answers with the application. A field that belongs to another move is
refused with `422 unknown_field`.

| `transition`               | Its own fields          |
| -------------------------- | ----------------------- |
| `withdraw`                 | None                    |
| `unlock`                   | `message`, `deadline`   |
| `request_more_information` | `form_template_id`      |
| `allow_edit_request`       | `edit_request_id`       |
| `decline_edit_request`     | `edit_request_id`       |
| `release_from_verifier`    | None                    |

A move that isn't open from where the application stands is refused with `409 invalid_transition`,
and `current_status` says where it stands. A `lock_version` that is out of date is refused with
`412 precondition_failed`. Decisions to approve or decline stay in Backed: the API has no transition
for them.

### Notes

`POST /api/v2/funding_applications/:id/notes` takes a `body`, and adds it to the application as an
internal comment, which applicants don't see. Application notes belong to the applicant, so this
route doesn't write to them. It answers `id`, `body`, `author_label` and `created_at`.

The comment's first line is `author_label`, `API: <credential name> as <Administrator>`, and your
text follows it.

### Tags

`PUT /api/v2/funding_applications/:id/tags` takes `tags`, a list of up to 100 names, and replaces the
application's hand-set tags with them. Each must be a tag your organisation already has: one that
isn't is refused with `422 unknown_tag`, naming it, and nothing changes. The API never creates a tag.
It answers `tags`, lower case and sorted.

### Assessor assignments

`POST /api/v2/assessment_rounds/:id/assignments` gives one assessor applications in a round. Tags and assignments need the Organisation's assessments module on; with it off they answer `404 module_disabled`. Send
`assessor_user_id`, `funding_application_ids` (1 to 500) and optionally `notify`.

- On a `Draft` round the assignments wait, and nobody is told until the round is published.
- On a `Published` round the assessments are created. `notify: true` also runs `Notify Assessors`.
- On any other round the request is refused with `409 invalid_state`.

If the assessor has declared a conflict of interest on any of the applications, the request is
refused with `409 conflict_declared` and nothing is assigned. It answers `assessment_round_id`,
`assessor_user_id`, `round_status`, `funding_application_ids` and `notified`.

## Refusals every write can give

A refusal's body is `{"error": "<code>", "error_description": "<text>"}`. Your system should switch
on `error`.

| Status | `error`                    | Why                                                                                   |
| ------ | -------------------------- | ------------------------------------------------------------------------------------- |
| `400`  | `invalid_request`          | The body isn't a JSON object.                                                         |
| `400`  | `idempotency_key_required` | There's no `Idempotency-Key`, or it's longer than 255 characters.                     |
| `403`  | `insufficient_scope`       | The credential doesn't hold the route's scope. The text names the scope.              |
| `403`  | `run_as_disabled`          | The credential has no enabled Administrator to act as.                                |
| `403`  | `forbidden`                | The Administrator's permissions or data access rules don't allow this on this record. |
| `413`  | `payload_too_large`        | The request body is too large.                                                        |
| `415`  | `unsupported_media_type`   | The request body isn't `application/json`.                                            |
| `422`  | `idempotency_key_reused`   | The key was already used for a different request.                                     |
| `409`  | `idempotency_in_progress`  | The first request with this key has not finished. Check the record before retrying.  |
| `422`  | `unknown_field`            | The body has a field the route doesn't document. The text names the first one.        |
| `422`  | `validation_failed`        | A value breaks the screen's own rules. `fields` names each failing field.             |

## See also

- [API access](api-access.md)
- [Write API: milestones, payments, contracts and files](api-c03-write-api-part-two-milestones-payments-contracts.md)
