> ## Documentation Index
> Fetch the complete documentation index at: https://docs.passlet.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Pass updates

> Change the data and template version of issued Apple Wallet and Google Wallet passes via the API. Wallets update automatically.

`PATCH /v1/passes/{id}` changes a pass after it was issued. One request can change any of:

* **`label`**: the pass holder display name.
* **`variables`**: the values the pass shows, for example a new seat or a higher tier.
* **`templateVersion`**: the template version the pass renders, to move it to a newer published version after a redesign.

It requires the `passes:write` scope ([access tokens](/developers/access-tokens)) and works on `ISSUED` passes only. The response is the changed pass, right away. Passlet then pushes the change to Apple Wallet and Google Wallet in the background, and holders don't need to do anything. Once the wallets are updated, a `pass.updated` [webhook](/developers/webhooks) reports the change.

## Update pass data

Send only what changes. `variables` is merged over the stored values: the variables you send replace their values, and every variable you leave out keeps its current one.

```bash theme={null}
curl -X PATCH "https://api.passlet.io/v1/passes/$PASS_ID" \
  -H "Authorization: Bearer $PASSLET_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "variables": { "seat": "14C", "tier": "gold" } }'
```

To remove a value, send `null`: the variable falls back to its template default. A variable without a default needs a value, so removing it fails with `400` (`MISSING_REQUIRED_FIELD`).

The merged variables are validated against the template version the pass renders, the same way as at issue time. A value that doesn't fit its variable type is rejected with `400` (`VALIDATION_ERROR`). Values Passlet computes for each pass, such as a generated signed scan code, are read-only: sending one returns `400` (`SYSTEM_VARIABLE_READONLY`). Every rejected variable is listed in [`fieldErrors`](/api-reference/errors#field-errors) as `variables.<name>`.

## Upgrade the template version

Passes stay pinned to the template version they were issued from; publishing a new version never changes them (see [Templates](/guides/templates#draft-vs-published)). Every pass shows the version it renders in `templateVersion`, and the newest published version of its template in `latestTemplateVersion`. To find all passes of a template that can be upgraded, filter the pass list:

```bash theme={null}
curl "https://api.passlet.io/v1/passes?templateId=$TEMPLATE_ID&upgradeAvailable=true" \
  -H "Authorization: Bearer $PASSLET_API_TOKEN"
```

To upgrade a pass, set `templateVersion` to `latest`, or to a version number to move it to exactly that version:

```bash theme={null}
curl -X PATCH "https://api.passlet.io/v1/passes/$PASS_ID" \
  -H "Authorization: Bearer $PASSLET_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "templateVersion": "latest" }'
```

The stored variables carry over, and the new version's defaults fill in the variables the pass has no value for. If the new version adds a variable without a default, the request fails with `400` (`MISSING_REQUIRED_FIELD`) and names the variable in `fieldErrors`, for example `variables.memberNumber`. Send its value in the same request:

```json theme={null}
{ "templateVersion": "latest", "variables": { "memberNumber": "M-1234" } }
```

To know the required variables up front, `GET /v1/templates/{id}/published-version/variables-schema` lists the variables of the template's current published version and marks the required ones.

Passes only move forward. A version older than the one the pass renders is rejected with `409` (`TEMPLATE_VERSION_NOT_NEWER`), and the version it already renders changes nothing. A pass also can't move to a version that turns off a wallet platform the pass is already issued on (`409`, `CONFLICT`).

## Preview a change

Add `dryRun=true` to see a change before you make it. Passlet checks the request exactly as it would for real and returns the pass as the change would leave it, but saves nothing and updates no wallets.

```bash theme={null}
curl -X PATCH "https://api.passlet.io/v1/passes/$PASS_ID?dryRun=true" \
  -H "Authorization: Bearer $PASSLET_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "templateVersion": "latest" }'
```

A dry run fails with the same errors as the real request, so it also tells you which variables an upgrade needs.

## Revisions

Every change creates a new revision of the pass. The `revision` field of a pass increases with each one, and it matches the revision number the console shows in the pass history. A request that leaves the pass as it is, for example one that sends the values it already has, creates no revision and doesn't touch the wallets. That makes retries safe: when you send the same change again, there is nothing left to change.

<Note>Pass responses also carry `version`, which is deprecated. Use `revision` instead.</Note>

## Avoid overwriting other changes

When several systems change the same pass, one can overwrite a change it never saw. To prevent that, send the revision your change is based on in the `If-Match` header. `GET /v1/passes/{id}` returns it in the `ETag` response header, and so does every change.

```bash theme={null}
curl -X PATCH "https://api.passlet.io/v1/passes/$PASS_ID" \
  -H "Authorization: Bearer $PASSLET_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H 'If-Match: "4"' \
  -d '{ "variables": { "seat": "14C" } }'
```

If the pass has changed since, nothing is written and the request fails with `412` (`PRECONDITION_FAILED`). Fetch the pass again, decide what to change, and send the change with the new revision. Without `If-Match`, the change applies to the pass as it is.

## Errors

| HTTP | `errorCode` | When |
| - | - | - |
| 400 | `MISSING_REQUIRED_FIELD` | A variable without a default has no value |
| 400 | `VALIDATION_ERROR` | A value doesn't fit its variable type, or the body has an unknown field |
| 400 | `SYSTEM_VARIABLE_READONLY` | The request sets a value Passlet computes itself |
| 400 | `INVALID_INPUT` | `templateVersion` isn't a published version of the pass's template |
| 400 | `INVALID_FORMAT` | `If-Match` isn't a revision such as `"4"` |
| 404 | `PASS_NOT_FOUND` | No pass with this ID exists in your workspace |
| 409 | `PASS_ALREADY_VOIDED` | The pass is voided |
| 409 | `PASS_NOT_ISSUED` | The pass is still being issued, failed to issue, or expired |
| 409 | `TEMPLATE_VERSION_NOT_NEWER` | `templateVersion` is older than the version the pass renders |
| 409 | `CONFLICT` | `templateVersion` turns off a wallet platform the pass is issued on |
| 409 | `VERSION_CONFLICT` | Another change to the pass landed while this one was processed; retry it |
| 412 | `PRECONDITION_FAILED` | The pass changed since the revision in `If-Match` |

See [Errors](/api-reference/errors) for the error format.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.