> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.confirm.global/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.confirm.global/_mcp/server.

# Multiple Locations

An applicant often fits more than one of a registry's locations. CONFIRM models that as **one candidate with one application per location**. Each application has its own status, and the applicant is screened once for all of them.

This page covers the whole cycle: sending an applicant to several locations, adding or moving a location later, recording your own outcome at one location, and the webhooks you receive along the way.

## Send an applicant to several locations

Use `location_refs` (an array) instead of `location_ref` on [`POST /candidates`](/partner-api/candidates/create-candidate):

```bash
curl https://api.confirm.global/partner/v1/candidates \
  -H "Authorization: Bearer cfk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "external_ref": "cl_app_84323",
    "location_refs": ["cl_loc_88213", "cl_loc_88214"],
    "first_name": "Rosa",
    "last_name": "Delgado",
    "phone": "+18135550188",
    "address": { "city": "Clearwater", "state": "FL", "postal_code": "33756" },
    "consent": { "contact": true },
    "webhook_url": "https://hooks.example.com/confirm/2d9e7b10-5c4a-4f3e-8a61-0b7c3e9f4d22"
  }'
```

The response has one entry per location in `applications`, in the order you sent them:

```json
{
  "candidate_id": "6e1b4d27-90af-4c38-b5d2-7a3f8c1e0b94",
  "external_ref": "cl_app_84323",
  "deduped": false,
  "portal_url": "https://app.confirm.global/c/suncoast-nurse-registry/candidates/6e1b4d27-90af-4c38-b5d2-7a3f8c1e0b94",
  "applications": [
    {
      "location_ref": "cl_loc_88213",
      "application_id": "a3c58e19-4b7d-4e02-9f61-2d8b0c7e5a13",
      "deduped": false,
      "screening": "enqueued",
      "screening_hold": null,
      "webhook_url": "https://hooks.example.com/confirm/2d9e7b10-5c4a-4f3e-8a61-0b7c3e9f4d22"
    },
    {
      "location_ref": "cl_loc_88214",
      "application_id": "f70d2b64-1e9c-4a85-b3d7-5c6e8a2f9014",
      "deduped": false,
      "screening": "enqueued",
      "screening_hold": null,
      "webhook_url": "https://hooks.example.com/confirm/2d9e7b10-5c4a-4f3e-8a61-0b7c3e9f4d22"
    }
  ]
}
```

Store each `application_id` against its location. Every webhook about this applicant names the location it is about.

### The rules

* **Exactly one of `location_ref` or `location_refs`.** Both, or neither, is `400 invalid_request`.
* **1 to 20 locations.** A ref repeated in the list is used once.
* **All in the same registry.** Locations in different registries fail with `400 location_refs_span_organizations`, and the error lists each ref with its `organization_ref`. Send one request per registry. `organization_ref`, if you send it, applies to every ref.
* **All or nothing on errors.** An unknown ref (`404 unknown_location_ref`) or a closed location the applicant is not already at (`409 location_closed`) fails the whole request before anything is created.
* **Idempotent, per location.** Each entry has its own `deduped`. The top-level `deduped` is `true` only when every application already existed. The status code is `201` when at least one application was created, `200` when none was.
* **Contact details, consent, address and `webhook_url` apply to every location** in the request.

## One screening call

The applicant gets **one** heads-up SMS and **one** AI screening call covering every location in the request. They are not called once per location.

* **Each location is scored on its own.** After the call, each location gets its own `screening.completed` webhook, with its own `result` and `tier`. Knockout questions count per location: failing one location's knockout question does not fail the others.
* **Distance is checked per location.** If you send the applicant's `address`, CONFIRM measures their commute to each location at intake. A location they live too far from is ruled out straight away: its entry reports `screening: "skipped_ruled_out"`, its status becomes `rejected` (with a `candidate.status_changed` webhook), and it is left out of the call. The other locations go ahead.
* **Set each location's pay.** A location without a pay rate (`pay` on [`POST /locations`](/partner-api/locations/create-location)) skips the pay question on screening calls.

If you send the same applicant again later with another location, and the screening call has not been placed yet, the new location joins that call. Once the call has happened, the new location gets a short follow-up call instead of a second full screening. See [follow-up calls](#follow-up-calls-for-locations-added-later).

`screening_hold` on each entry is `"calendar_not_connected"` when that location's call is queued but the workspace has no connected calendar yet, and `null` otherwise. See the [calendar guide](/partner-guides/calendar).

## Add a location later

To put an applicant you already sent at another location, use [`POST /candidates/{external_ref}/locations`](/partner-api/candidates/add-candidate-location):

```bash
curl https://api.confirm.global/partner/v1/candidates/cl_app_84321/locations \
  -H "Authorization: Bearer cfk_..." \
  -H "Content-Type: application/json" \
  -d '{ "location_ref": "cl_loc_88214" }'
```

```json
{
  "candidate_id": "9f4c2e81-6d3a-4b7e-a1c2-8e5f0d743b16",
  "application_id": "0b6e3f72-5a19-4d8c-a2e4-7c1d9f8b3a60",
  "external_ref": "cl_app_84321",
  "location_ref": "cl_loc_88214",
  "created": true,
  "status": "interview",
  "screening": "reused",
  "moved_from": null,
  "portal_url": "https://app.confirm.global/c/suncoast-nurse-registry/candidates/9f4c2e81-6d3a-4b7e-a1c2-8e5f0d743b16"
}
```

It reuses the screening the applicant already has, and `screening` tells you how:

| `screening` | Meaning                                                                                                                                                                                                                               |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `reused`    | The applicant passed a screening that asked every question this location asks. They start at the step after screening here, and that step's usual messages are sent (for example, an interview booking link). No call is placed.      |
| `pending`   | There is a completed screening, but it failed, was partial, or doesn't cover this location's questions. The application starts at screening, and a [follow-up call](#follow-up-calls-for-locations-added-later) asks what is missing. |
| `none`      | No completed screening to reuse yet, or the applicant was already at this location (`created: false`). A new application is screened as described in [follow-up calls](#follow-up-calls-for-locations-added-later).                   |

`screening` describes the application when it was created. It does not change when a follow-up call runs; the result reaches you as webhooks.

The new application keeps the applicant's `webhook_url`. You receive `candidate.location_added`. It returns `201` when it creates an application and `200` (`created: false`) when the applicant was already at that location, so it is safe to replay.

## Follow-up calls for locations added later

A location added after the applicant's screening call does not get a second full screening. It is handled the same way whether you add it (or move the applicant to it) with `POST /candidates/{external_ref}/locations` (`screening` of `pending` or `none`), send it with `POST /candidates` after the call happened, or the registry office adds it in CONFIRM:

* **The call hasn't been placed yet:** the new location joins it, and that one call covers it too.
* **The first call is still in progress:** the new location waits for it to finish, then carries on as below.
* **Questions left to ask:** a short follow-up call asks only the questions the applicant has not already answered.
* **Nothing left to ask:** no call is placed. The outcome at the new location is decided from the earlier answers.

**Earlier answers carry over only when the question is asked exactly the same way.** Each location has its own questions. A question that names a different address ("...at the Clearwater office?" versus "...at the Largo office?") is a different question and is asked again. A knockout question carries over only when the new location applies the same knockout rule to it, so a failed answer elsewhere counts here only for an identical question. Answers more than 180 days old are asked again.

**What the applicant gets.** No second heads-up SMS: the follow-up call is not a new application. The usual requirements still apply: the applicant needs contact consent and a valid phone, and the call is held while the workspace has no [connected calendar](/partner-guides/calendar).

**What you get.** The new location gets its own `screening.completed` and `candidate.status_changed`, like any screened location. When the outcome was decided from earlier answers with no call, `screening.completed` still fires for the new location, and its `completed_at` is the time of the call the answers came from.

## Move an applicant

Add `move_from_location_ref` to move instead of add:

```bash
curl https://api.confirm.global/partner/v1/candidates/cl_app_84321/locations \
  -H "Authorization: Bearer cfk_..." \
  -H "Content-Type: application/json" \
  -d '{ "location_ref": "cl_loc_88214", "move_from_location_ref": "cl_loc_88213" }'
```

The applicant gets an application at the new location, and the one at the old location is closed. **A move is not a rejection:** the applicant gets no message about it, and you receive one `candidate.location_changed`, with no `candidate.status_changed` → `rejected` for the location they left.

Both locations must be in the same registry, and the applicant must have an application at `move_from_location_ref` (`404 no_application_at_location` otherwise).

## Take an applicant off one location

There is no `DELETE`. To record that an applicant is no longer going ahead at one location, set your outcome there with [`PATCH /candidates/{external_ref}`](/partner-api/candidates/set-candidate-outcome):

```bash
curl -X PATCH "https://api.confirm.global/partner/v1/candidates/cl_app_84321?location_ref=cl_loc_88213" \
  -H "Authorization: Bearer cfk_..." \
  -H "Content-Type: application/json" \
  -d '{ "outcome": "withdrawn", "reason": "Accepted a position closer to home." }'
```

```json
{
  "candidate_id": "9f4c2e81-6d3a-4b7e-a1c2-8e5f0d743b16",
  "application_id": "51c0a7de-2f18-4a3e-96b7-1d0c8b2e4a77",
  "external_ref": "cl_app_84321",
  "location_ref": "cl_loc_88213",
  "outcome": "withdrawn",
  "status": "rejected",
  "changed": true
}
```

| `outcome`   | Use it when                          | Status at that location |
| ----------- | ------------------------------------ | ----------------------- |
| `withdrawn` | The applicant pulled out.            | `rejected`              |
| `not_hired` | The office decided not to hire them. | `rejected`              |
| `hired`     | The office hired them.               | `hired`                 |

* **Only that location changes.** The applicant's other locations carry on as before.
* **CONFIRM sends the applicant nothing** about an outcome you set.
* **You get the status change back.** It fires `candidate.status_changed` (`rejected` or `hired`) for that location, like any other status change. Deduplicate it against the change you just made.
* **Idempotent.** Sending the outcome the application already has returns `changed: false` and fires nothing.
* **Do-not-contact is final.** An applicant CONFIRM has marked do-not-contact returns `409 outcome_conflict`.

Pass `organization_ref` as a second query parameter if the same location ref exists in more than one of your registries.

## Placement

When the registry office decides where the applicant goes, they place them at a location in CONFIRM. You receive `candidate.placed` for that location, with `placed_at` and `placed_via`. Placing does not change the applicant's status and does not close their other locations: another location can still hire the same person later. When the applicant is actually hired, record it with `outcome: "hired"` (or the registry does it in CONFIRM), and the `hired` status arrives as `candidate.status_changed`.

## Webhooks at a glance

Every event is about one location and carries its `location_ref` and `application_id`. See the [webhooks guide](/partner-guides/webhooks) for full payloads.

| What happened                                                        | Event                        |
| -------------------------------------------------------------------- | ---------------------------- |
| The status at one location changed (including from your own `PATCH`) | `candidate.status_changed`   |
| A screening call's results are ready, once per location in the call  | `screening.completed`        |
| The office placed the applicant at a location                        | `candidate.placed`           |
| The applicant was added to another location                          | `candidate.location_added`   |
| The applicant was moved between locations                            | `candidate.location_changed` |