Pass
Filtering: see Neo.Web.Api.Controllers.FilterModel for the fields and operators.
Sorting: see Neo.Web.Api.Controllers.SortModel. Without a sort, the order is unspecified and may change between pages.
Pagination: zero-based pageIndex and pageSize. The number of passes matching the filters, all pages together, is in the x-pagination-total response header.
Example — list loyalty passes, page 0:
GET /api/{tenantId}/passes?pageIndex=0&pageSize=20
&filter[0].field=passType&filter[0].operator=equals&filter[0].value=loyalty
&sortBy[0].field=creationDate&sortBy[0].direction=DESCExample — passes installed on Apple Wallet:
GET /api/{tenantId}/passes?filter[0].field=installationStatus&filter[0].operator=contains&filter[0].value=appleZero-based page index. Default 0.
0Passes per page. Default 20; 0 returns an empty page with the total still set.
20The page of passes.
A pass as listed by GET /passes.
Pass id: a 16-character string assigned at creation, unless the creator set it through a neo.id identifier. Also the Apple serial number and the suffix of the Google object id.
Secret of the pass, used by the holder's wallet to receive pass updates. Assigned at creation unless set through additionalData.neo.secret; treat as confidential.
When the pass was issued, UTC. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings.
When the pass content last changed, UTC; equals creationDate when it never has. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings.
Pass template the pass renders from.
Theme applied to the pass. null when the pass renders from its pass template alone.
A filter or sort names an unsupported field, a filter has no operator, or paging is negative.
The caller is not authenticated.
The caller lacks the tenant.pass:read scope, or the endpoint is not enabled for the tenant.
Over 1200 requests per minute to this endpoint for the tenant (default limit); retry after Retry-After.
GET /api/{tenantId}/passes HTTP/1.1
Host: app.neostore.cloud
Authorization: Bearer YOUR_OAUTH2_TOKEN
Accept: */*
[
{
"id": "text",
"secret": "text",
"creationDate": "2026-01-01T00:00:00.000Z",
"lastUpdateDate": "2026-01-01T00:00:00.000Z",
"passType": "text",
"identifiers": {
"ANY_ADDITIONAL_PROPERTY": "text"
},
"metadata": {
"ANY_ADDITIONAL_PROPERTY": null
},
"additionalData": {
"ANY_ADDITIONAL_PROPERTY": null
},
"providers": {
"ANY_ADDITIONAL_PROPERTY": "anything"
},
"themeId": null
}
]Security: without the tenant.pass:write scope, each identifier must be proven (see Neo.Web.Api.Controllers.PassController.PassData.Identifiers); unproven ones are dropped, and the request is refused when none is left. The customer identifiers of an authenticated holder are added.
Effects: the pass is created and a pass-created event is emitted. Nothing is sent to a device until the holder downloads the pass.
Example — create with an HMAC proof:
POST /api/{tenantId}/passes?passType=loyalty
{
"identifiers": {
"shopify.customerId": "12345",
"shopify.customerId.hmac": "{hmac-of-12345}"
},
"additionalData": { "loyaltyTier": "silver" }
}Example — create with the tenant.pass:write scope (no proof required):
POST /api/{tenantId}/passes?passType=loyalty
{
"identifiers": { "shopify.customerId": "12345" },
"additionalData": { "loyaltyTier": "silver" }
}Pass template to issue the pass from; matched exactly. Required unless a connector of the tenant picks the template.
Content of a new pass.
Free data stored with the pass and available to its templates (for example loyalty tier, store code, campaign flags).
Theme applied to the pass, overriding the look of its pass template. Omit or send null to
leave the current theme untouched (none on create); send an empty string to remove it. An id no
declared theme has is refused with a 400.
Initial state of the pass's extensions, keyed by extension name (for example apple,
google), stored with the pass.
The pass was created; the body is its id.
passType or themeId names a template or theme the tenant does not have.
The caller is not authenticated and no identifier could be verified.
The caller is authenticated without the tenant.pass:write scope, and no identifier could be verified, or this action is not enabled for the tenant.
Over 1200 requests per minute to this endpoint for the tenant (default limit); retry after Retry-After.
POST /api/{tenantId}/passes HTTP/1.1
Host: app.neostore.cloud
Authorization: Bearer YOUR_OAUTH2_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 105
{
"extensions": null,
"identifiers": {
"ANY_ADDITIONAL_PROPERTY": "text"
},
"additionalData": null,
"themeId": null
}textChanges a pass, addressed by its pass id or by external identifiers, and pushes the result to the holder's devices.
Same changes and delivery as PATCH {passId}.
Example — update by platform pass ID:
PATCH /api/{tenantId}/passes?id=xK9mP2nQr7sT
{
"additionalData": { "loyaltyTier": "gold" },
"options": { "updateMetadata": true }
}Example — update by external identifier:
PATCH /api/{tenantId}/passes?id.shopify.customerId=12345
{
"identifiers": { "email": "[email protected]" },
"additionalData": { "loyaltyTier": "gold" },
"options": { "updateMetadata": false }
}Query: only a pass of this pass template matches. Use passType in the body to move the pass to another template.
Changes to one pass. Omitted fields are left as they are.
Free data stored with the pass and available to its templates (for example loyalty tier, store code, campaign flags).
Theme applied to the pass, overriding the look of its pass template. Omit or send null to
leave the current theme untouched (none on create); send an empty string to remove it. An id no
declared theme has is refused with a 400.
Pass template to move the pass to; it then renders from that template. Omitted or blank, the pass keeps its template. A name the tenant has no template for is refused with a 400.
Same as options.updateMetadata, which replaces it.
falseSame as options.bypassQueue, which replaces it.
falseApplied and pushed within the request (options.bypassQueue true).
No content
Queued.
The body's passType or themeId names a template or theme the tenant does not have, or id is combined with other identifiers.
The caller is not authenticated.
The caller lacks the tenant.pass:write scope, or the endpoint is not enabled for the tenant.
No pass matches the identifiers, or no id or id. parameter is given.
Synchronous update only: the pass was changed concurrently; retry the request.
Over 50000 requests per minute to this endpoint for the tenant (default limit); retry after Retry-After.
PATCH /api/{tenantId}/passes HTTP/1.1
Host: app.neostore.cloud
Authorization: Bearer YOUR_OAUTH2_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 178
{
"identifiers": {
"ANY_ADDITIONAL_PROPERTY": "text"
},
"additionalData": null,
"themeId": null,
"passType": null,
"options": {
"bypassQueue": false,
"updateMetadata": true,
"correlationId": null
}
}No content
Downloads a pass for a wallet: the Apple `.pkpass` file, the Google Wallet save token, a preview, or a redirect chosen from the device.
Download an Apple Wallet pass:
GET /api/{tenantId}/passes/xK9mP2nQr7sT?device=appleAuto-redirect to the appropriate wallet based on user-agent:
GET /api/{tenantId}/passes/xK9mP2nQr7sT?device=autoTrack the installation source (email campaign, email medium):
GET /api/{tenantId}/passes/xK9mP2nQr7sT?device=apple&neo.src=email-campaign|emailPass id (5 to 50 letters, digits, _ or -).
^[\w-]{5,50}$apple: the signed .pkpass file. google: the signed JWT that saves the pass to Google
Wallet. preview: the rendered texts and colors of the Apple pass, as JSON. auto: a redirect to
?device=apple on an iPhone, otherwise to /view.
Comma-separated campaign tags, the first segment of neo.src; duplicates are dropped.
Channel the link was sent through (for example email, sms), the second segment.
Page or system the holder came from, the third segment; defaults to the Referer header.
Taken from the request's User-Agent header, not from neo.src.
The pass content.
No content
device=auto: redirect to the Apple download or the pass page.
device is not one of the supported values.
The endpoint is not enabled for the tenant.
Over 500 requests per minute to this endpoint for the tenant (default limit); retry after Retry-After.
GET /api/{tenantId}/passes/{passId} HTTP/1.1
Host: app.neostore.cloud
Accept: */*
No content
Changes a pass, addressed by its pass id, and pushes the result to the holder's devices.
Changes: identifiers and additionalData are merged into the pass (a blank identifier or a null data value removes its key; omitted keys are kept). passType and themeId replace the current ones when sent.
Delivery: queued by default (202). With options.bypassQueue the change is stored and pushed within the request (200).
Example — update metadata and push notification:
PATCH /api/{tenantId}/passes/xK9mP2nQr7sT
{
"additionalData": { "loyaltyTier": "platinum", "points": 5000 },
"options": { "updateMetadata": true, "bypassQueue": false }
}Example — urgent update, bypass queue:
PATCH /api/{tenantId}/passes/xK9mP2nQr7sT
{
"additionalData": { "boardingStatus": "boarding" },
"options": { "updateMetadata": true, "bypassQueue": true }
}Pass id (5 to 50 letters, digits, _ or -).
^[\w-]{5,50}$Query: only a pass of this pass template matches. Use passType in the body to move the pass to another template.
Changes to one pass. Omitted fields are left as they are.
Free data stored with the pass and available to its templates (for example loyalty tier, store code, campaign flags).
Theme applied to the pass, overriding the look of its pass template. Omit or send null to
leave the current theme untouched (none on create); send an empty string to remove it. An id no
declared theme has is refused with a 400.
Pass template to move the pass to; it then renders from that template. Omitted or blank, the pass keeps its template. A name the tenant has no template for is refused with a 400.
Same as options.updateMetadata, which replaces it.
falseSame as options.bypassQueue, which replaces it.
falseApplied and pushed within the request (options.bypassQueue true).
No content
Queued.
The body's passType or themeId names a template or theme the tenant does not have.
The caller is not authenticated.
The caller lacks the tenant.pass:write scope, or the endpoint is not enabled for the tenant.
No pass has this id, or the pass is not of the pass template given in passType.
Synchronous update only: the pass was changed concurrently; retry the request.
Over 50000 requests per minute to this endpoint for the tenant (default limit); retry after Retry-After.
PATCH /api/{tenantId}/passes/{passId} HTTP/1.1
Host: app.neostore.cloud
Authorization: Bearer YOUR_OAUTH2_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 178
{
"identifiers": {
"ANY_ADDITIONAL_PROPERTY": "text"
},
"additionalData": null,
"themeId": null,
"passType": null,
"options": {
"bypassQueue": false,
"updateMetadata": true,
"correlationId": null
}
}No content
Returns a WebSocket URL on which a pass page is told when the pass changes, so it can refresh.
GET /api/{tenantId}/passes/xK9mP2nQr7sT/connect
Pass id (5 to 50 letters, digits, _ or -).
^[\w-]{5,50}$The WebSocket URL.
Where to listen for live changes of one pass.
WebSocket URL for the pass, carrying an expiring access token: request a new one to reconnect.
The endpoint is not enabled for the tenant.
No pass has this id.
Over 500 requests per minute to this endpoint for the tenant (default limit); retry after Retry-After.
GET /api/{tenantId}/passes/{passId}/connect HTTP/1.1
Host: app.neostore.cloud
Accept: */*
{
"url": "text"
}Returns the data the pass templates render from for this pass, merged into one object.
Does not change the pass.
GET /api/{tenantId}/passes/xK9mP2nQr7sT/dataPass id (5 to 50 letters, digits, _ or -).
^[\w-]{5,50}$The merged template data.
No content
The endpoint is not enabled for the tenant.
Over 1200 requests per minute to this endpoint for the tenant (default limit); retry after Retry-After.
GET /api/{tenantId}/passes/{passId}/data HTTP/1.1
Host: app.neostore.cloud
Accept: */*
No content
Returns the full pass, extensions and secret included, for diagnostics.
GET /api/{tenantId}/passes/xK9mP2nQr7sT/data/raw
Pass id (5 to 50 letters, digits, _ or -).
^[\w-]{5,50}$The full pass.
No content
The endpoint is not enabled for the tenant.
Over 1200 requests per minute to this endpoint for the tenant (default limit); retry after Retry-After.
GET /api/{tenantId}/passes/{passId}/data/raw HTTP/1.1
Host: app.neostore.cloud
Accept: */*
No content
Sends a notification to the holder of a pass, addressed by its pass id.
Delivery is queued by default: a 202 means the notification is accepted, not yet sent; queued delivery is at-least-once and unordered. Set options.bypassQueue for synchronous delivery, which answers 200. On Apple the message shows on the pass for the pass template's notification duration and the pass is pushed to its devices; on Google it is added as a message on the pass.
Pass id assigned by the platform.
The message to show the holder, in one or several languages.
Message shown on devices whose language has no entry in localizedContent. Optional when
localizedContent has text; it then defaults to the tenant's default language entry, or else
the first language in ordinal order.
Message per device language. Keys are a lowercase ISO 639-1 code, optionally with an uppercase
region (en, fr-FR). Entries with blank text are not sent.
Notification sent synchronously (options.bypassQueue true).
No content
Notification queued for delivery.
Neither content nor localizedContent has text, or correlationId is outside 10 to 100 characters.
The caller is not authenticated.
The caller lacks the tenant.pass:write scope.
No pass has this id.
Over 50000 requests per minute to this endpoint for the tenant (default limit); retry after Retry-After.
PUT /api/{tenantId}/passes/{passId}/notification HTTP/1.1
Host: app.neostore.cloud
Authorization: Bearer YOUR_OAUTH2_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 112
{
"content": null,
"localizedContent": null,
"options": {
"updateData": false,
"correlationId": null,
"bypassQueue": false
}
}No content
Redirects to the public page where the holder sees the pass and adds it to a wallet.
The target is {public site}/{tenantId}/{download page of the pass template}. The request's query parameters are forwarded, with id set to the pass id.
GET /api/{tenantId}/passes/xK9mP2nQr7sT/viewPass id.
Redirect to the download page configured on the pass template.
No content
The endpoint is not enabled for the tenant.
No pass has this id.
Over 1200 requests per minute to this endpoint for the tenant (default limit); retry after Retry-After.
GET /api/{tenantId}/passes/{passId}/view HTTP/1.1
Host: app.neostore.cloud
Accept: */*
No content
Downloads several passes at once: an Apple `.pkpasses` bundle, or one Google Wallet save token carrying them all.
Download a bundle of Apple Wallet passes:
GET /api/{tenantId}/passes/xK9mP2nQr7sT,a3Bc4dEf5gHi?device=appleRetrieve JSON for Google Wallet:
GET /api/{tenantId}/passes/xK9mP2nQr7sT,a3Bc4dEf5gHi?device=googlePass ids read from a comma-separated route segment; blank entries are dropped.
^[\w-]{5,50},([\w-]{5,50},?)+$apple or google.
Comma-separated campaign tags, the first segment of neo.src; duplicates are dropped.
Channel the link was sent through (for example email, sms), the second segment.
Page or system the holder came from, the third segment; defaults to the Referer header.
Taken from the request's User-Agent header, not from neo.src.
The passes.
No content
device is not apple or google.
The endpoint is not enabled for the tenant.
Over 1200 requests per minute to this endpoint for the tenant (default limit); retry after Retry-After.
GET /api/{tenantId}/passes/{passIds} HTTP/1.1
Host: app.neostore.cloud
Accept: */*
No content
Finds the pass of a holder from their identifiers in another system.
Lookup with an HMAC proof:
GET /api/{tenantId}/passes/findPass?shopify.customerId=12345&shopify.customerId.hmac={hmac}Lookup with the tenant.pass:read scope (no proof required):
GET /api/{tenantId}/passes/findPass?shopify.customerId=12345Restricts the search to this pass template.
The id of the most recently created matching pass.
No pass matches the identifiers.
The caller is not authenticated and no identifier could be verified.
The caller is authenticated without the tenant.pass:read scope, and no identifier could be verified, or this action is not enabled for the tenant.
Over 1200 requests per minute to this endpoint for the tenant (default limit); retry after Retry-After.
GET /api/{tenantId}/passes/findPass HTTP/1.1
Host: app.neostore.cloud
Accept: */*
textLists passes from a list of pass ids, or from a key in a connected system (an order, a subscription) that the connector resolves to passes.
Direct mode: ids=id1,id2 returns one item per id given.
Supplier mode: ids.{provider}={key} (for example ids.shopify=ORDER-12345) asks that connector for the passes of the key, creating the missing ones. The connector may require ids.{provider}.hmac or ids.{provider}.secret as proof.
Preview: with includePreview=true, each item adds title, subTitle, backgroundColor, foregroundColor, headerText and isEnabled, rendered from the Apple pass.
Example — direct pass IDs lookup:
GET /api/{tenantId}/passes/findPasses?ids=xK9mP2nQr7sT,a3Bc4dEf5gHiExample — resolve via supplier:
GET /api/{tenantId}/passes/findPasses?ids.shopify=ORDER-12345&includePreview=trueExample — resolve via supplier with HMAC:
GET /api/{tenantId}/passes/findPasses?ids.shopify=ORDER-12345&ids.shopify.hmac={hmac}Adds the rendered title, subtitle, colors, header text and enabled state of each pass.
falseThe passes.
Neither ids nor an ids.{provider} parameter, several providers, an empty provider value, or a provider with no connector.
The endpoint is not enabled for the tenant.
Over 1200 requests per minute to this endpoint for the tenant (default limit); retry after Retry-After.
GET /api/{tenantId}/passes/findPasses HTTP/1.1
Host: app.neostore.cloud
Accept: */*
[]Sends a notification to the holder of a pass, addressed by its pass id or by external identifiers.
Same delivery as PUT {passId}/notification.
The message to show the holder, in one or several languages.
Message shown on devices whose language has no entry in localizedContent. Optional when
localizedContent has text; it then defaults to the tenant's default language entry, or else
the first language in ordinal order.
Message per device language. Keys are a lowercase ISO 639-1 code, optionally with an uppercase
region (en, fr-FR). Entries with blank text are not sent.
Notification sent synchronously (options.bypassQueue true).
No content
Notification queued for delivery.
Neither content nor localizedContent has text, correlationId is outside 10 to 100 characters, or id is combined with other identifiers.
The caller is not authenticated.
The caller lacks the tenant.pass:write scope.
No pass matches the identifiers, or no id or id. parameter is given.
Over 50000 requests per minute to this endpoint for the tenant (default limit); retry after Retry-After.
PUT /api/{tenantId}/passes/notification HTTP/1.1
Host: app.neostore.cloud
Authorization: Bearer YOUR_OAUTH2_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 112
{
"content": null,
"localizedContent": null,
"options": {
"updateData": false,
"correlationId": null,
"bypassQueue": false
}
}No content
Applies the same change to every pass matching the filters and pushes each pass to its holder's devices.
Filtering: same fields and operators as GET /passes. No filter selects every pass of the tenant.
Asynchronous: each pass is queued as its own update, spread over time by options.throughput. A 200 means scheduled, not done; follow progress with GET pushUpdate/status under options.correlationId (a random id when omitted) and stop it with POST pushUpdate/{correlationId}/cancel.
Changes: additionalData is merged into each pass and passType replaces the template when sent. themeId: omit to leave each pass on its current theme, send a declared id to apply it, send an empty string to remove it. Identifiers are not changed by a bulk update.
Example — push a seasonal offer to all loyalty passes:
POST /api/{tenantId}/passes/pushUpdate
?filter[0].field=passType&filter[0].operator=equals&filter[0].value=loyalty
{
"additionalData": { "offer": "summer2025", "discount": "20%" },
"options": {
"updateMetadata": true,
"throughput": 12,
"correlationId": "campaign-summer-2025"
}
}Changes to apply to an existing pass, with options.
Free data stored with the pass and available to its templates (for example loyalty tier, store code, campaign flags).
Theme applied to the pass, overriding the look of its pass template. Omit or send null to
leave the current theme untouched (none on create); send an empty string to remove it. An id no
declared theme has is refused with a 400.
Pass template to move the pass to; it then renders from that template. Omitted or blank, the pass keeps its template. A name the tenant has no template for is refused with a 400.
Same as options.updateMetadata, which replaces it.
falseThe passes are scheduled; the body gives how many.
What a bulk update scheduled.
Passes matched by the filters and queued for the update; 0 when none matched.
passType or themeId names a template or theme the tenant does not have, a filter names an unsupported field, or a filter has no operator.
The caller is not authenticated.
The caller lacks the tenant.pass:write scope, or the endpoint is not enabled for the tenant.
Over 1200 requests per minute to this endpoint for the tenant (default limit); retry after Retry-After.
POST /api/{tenantId}/passes/pushUpdate HTTP/1.1
Host: app.neostore.cloud
Authorization: Bearer YOUR_OAUTH2_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 173
{
"identifiers": {
"ANY_ADDITIONAL_PROPERTY": "text"
},
"additionalData": null,
"themeId": null,
"passType": null,
"options": {
"throughput": 1,
"updateMetadata": true,
"correlationId": null
}
}{
"passCount": 1
}Stops an update operation: its pass updates not yet run are skipped.
Updates already applied stay applied, and one running at that moment may still complete.
Example:
POST /api/{tenantId}/passes/pushUpdate/campaign-summer-2025/cancelThe operation's correlation id, as sent in options.correlationId or listed as operationData.
The operation is marked cancelled.
No content
The caller is not authenticated.
The caller lacks the tenant.pass:read scope, or the endpoint is not enabled for the tenant.
Over 1200 requests per minute to this endpoint for the tenant (default limit); retry after Retry-After.
POST /api/{tenantId}/passes/pushUpdate/{correlationId}/cancel HTTP/1.1
Host: app.neostore.cloud
Authorization: Bearer YOUR_OAUTH2_TOKEN
Accept: */*
No content
Lists the progress of every update operation of the tenant, most recently started first.
An operation is every pass update scheduled under one correlation id: bulk updates, queued single updates (each gets its own random id when none is sent) and synchronous updates sent with a correlation id.
Example:
GET /api/{tenantId}/passes/pushUpdate/statusThe operations.
Progress of one update operation: every pass update queued or applied under the same correlation id.
Id of the progress record.
Name of the operation that first scheduled updates under this correlation id.
The correlation id: the value to pass to POST pushUpdate/{correlationId}/cancel.
When the first update was scheduled.
When a pass of the operation last progressed; null before any did.
Pass updates scheduled under the operation.
Pass updates that finished, successfully or not.
Pass updates that failed.
Pass updates pushed to Apple devices.
Pass updates sent to Google Wallet.
The operation was cancelled: its pass updates not yet run are skipped.
The caller is not authenticated.
The caller lacks the tenant.pass:read scope, or the endpoint is not enabled for the tenant.
Over 1200 requests per minute to this endpoint for the tenant (default limit); retry after Retry-After.
GET /api/{tenantId}/passes/pushUpdate/status HTTP/1.1
Host: app.neostore.cloud
Authorization: Bearer YOUR_OAUTH2_TOKEN
Accept: */*
[
{
"id": "text",
"operationName": "text",
"operationData": "text",
"startDate": "2026-01-01T00:00:00.000Z",
"lastActivity": null,
"passCount": 1,
"completed": 1,
"errors": 1,
"apple": 1,
"google": 1,
"isCanceled": true
}
]Counts issued passes and the passes currently installed on Apple Wallet and Google Wallet.
Counted at the time of the call. For dashboards, trends and breakdowns, use the Insights API.
Restricts the counts to passes of this pass template. Matched exactly; a name with no pass yields zeros.
The counts.
Pass statistics for the tenant, or for one pass template.
The caller is not authenticated.
The caller lacks the tenant.pass:read scope, or the endpoint is not enabled for the tenant.
Over 1200 requests per minute to this endpoint for the tenant (default limit); retry after Retry-After.
GET /api/{tenantId}/passes/stats HTTP/1.1
Host: app.neostore.cloud
Authorization: Bearer YOUR_OAUTH2_TOKEN
Accept: */*
{
"count": {
"total": 1,
"active": 1,
"apple": 1,
"google": 1
}
}Last updated

