> For the complete documentation index, see [llms.txt](https://docs.thewalletcrew.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.thewalletcrew.io/api-reference/advanced/benefit.md).

# Benefit

## List pass benefits

> With the \`tenant.pass:read\` scope: every benefit and field. Without it, the pass holder's view:\
> only benefits not yet removed and shown on the benefit list page (\`behavior.showOnBenefitListPage\`, default true), holder-facing fields only\
> (no \`origin\`, \`tags\`, \`passType\`, \`behavior.mode\`, \`data.content\`, movement \`remarks\`, removal or modification dates).\
> Expired and exhausted benefits are returned until their deletion date.\
> Sorted by priority descending; the holder's view breaks ties by creation date descending.

```json
{"openapi":"3.1.1","info":{"title":"Neostore internal API","version":"v1"},"tags":[{"name":"Benefit"}],"servers":[{"url":"https://app.neostore.cloud","description":"Production Server"},{"url":"https://app-qa.neostore.cloud","description":"Staging Server"}],"security":[{"admin-bearer":["tenant.pass:read"]},{"apiKey":[]},{}],"components":{"securitySchemes":{"admin-bearer":{"type":"oauth2","flows":{"implicit":{"authorizationUrl":"https://auth.neostore.cloud/authorize?audience=https://app.neostore.cloud/api/","scopes":{}}}},"apiKey":{"type":"apiKey","name":"X-API-KEY","in":"header"}},"schemas":{"Benefit":{"required":["benefitId"],"type":"object","allOf":[{"description":"A benefit as read back.","$ref":"#/components/schemas/BenefitBase"}],"properties":{"benefitId":{"type":"string","description":"Benefit id, unique within the tenant."},"passType":{"type":["null","string"],"description":"Pass template of the linked standalone wallet pass; null if none, or without `tenant.pass:read`."},"linkedPassId":{"type":["null","string"],"description":"Id of the linked wallet pass; null until it is created."}},"additionalProperties":false,"description":"A stored benefit with its unique id."},"BenefitBase":{"required":["category","status","type"],"type":"object","properties":{"priority":{"type":["null","integer"],"description":"Rank among the pass's benefits: higher first, none last. Orders lists and wallet back fields, and picks whose main image becomes the pass artwork.","format":"int32"},"origin":{"description":"Where the benefit came from: a benefit template (`generator``benefitTemplate`, `processId` the template id) or an integration. Omitted without `tenant.pass:read`.","$ref":"#/components/schemas/BenefitOrigin"},"type":{"description":"How the benefit is consumed: `oneTime` once, `multiUse` counts down uses, `unlimited` is never used up.","$ref":"#/components/schemas/BenefitType"},"category":{"description":"What the benefit gives the holder; `other` when stored without one.","$ref":"#/components/schemas/BenefitCategory"},"tags":{"type":"array","items":{"type":"string"},"description":"Labels for segmenting and reporting, never shown to the holder; omitted without `tenant.pass:read`."},"content":{"description":"Title and description the holder reads.","$ref":"#/components/schemas/BenefitContent"},"appearance":{"description":"Images, at most one per slot. With `tenant.pass:read`, a benefit template image is returned as its image path (the value to send back on write); otherwise as a loadable URL.","$ref":"#/components/schemas/BenefitAppearance"},"links":{"type":["null","object"],"properties":{"legalInformation":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"callToAction":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"termsAndConditions":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"privacyPolicy":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"howToUse":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"website":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"other":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"}},"additionalProperties":false,"description":"Links shown on the benefits page, keyed by purpose."},"data":{"description":"Balance, movement history and provider payload.","$ref":"#/components/schemas/BenefitData"},"activationDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When the benefit becomes redeemable; null means immediately. Before it, status is `scheduled` and redemption is refused. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"deactivationDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When redemption stops; null means never. Afterwards status is `expired` until the deletion date. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"deletionDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When the benefit leaves the pass; null means never, and without `tenant.pass:read`. Until then it counts towards the 20 benefits a pass can carry, even if no longer redeemable. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"behavior":{"description":"Allowance of a `multiUse` benefit and where the benefit is surfaced.","$ref":"#/components/schemas/BenefitBehavior"},"code":{"description":"Code staff scan or type to redeem the benefit, with its rendering; null when none.","$ref":"#/components/schemas/BenefitCode"},"creationDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When the benefit was issued; server-set, ignored on write. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"lastModificationDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When the benefit last changed (update, movement, template refresh); server-set, null without `tenant.pass:read`. Editing a template does not move it unless the edit refreshes issued benefits. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"status":{"description":"Current state, derived from dates and balance. `active` means a movement would be accepted.","$ref":"#/components/schemas/BenefitStatus"}},"additionalProperties":false,"description":"A benefit as read back."},"BenefitOrigin":{"type":"object","properties":{"externalId":{"maxLength":100,"minLength":1,"type":["null","string"],"description":"External id, unique within the tenant. On POST, upserts on it first (before `processId`): updates the existing benefit, or 409 if it belongs to another pass. Kept by later writes that omit it. At most 100 characters."},"generator":{"maxLength":100,"minLength":1,"type":["null","string"],"description":"Name of the upstream system, e.g. `crm`; `benefitTemplate` marks a benefit issued from a template. Informative only. At most 100 characters."},"processId":{"maxLength":100,"minLength":1,"type":["null","string"],"description":"Id of the upstream process or batch job (e.g. `campaign3`). On POST without `externalId`, upserts on (pass, processId); need not be unique across passes. A pass template reads the benefit as `benefits.{processId}`, each run of characters other than letters, digits and `_` becoming one `_`. At most 100 characters."}},"additionalProperties":false,"description":"Where a benefit comes from in an external system, for idempotency and traceability."},"BenefitType":{"enum":["oneTime","unlimited","multiUse"],"type":"string","description":"How a benefit is consumed, which decides what its movements mean and when it is used up."},"BenefitCategory":{"enum":["other","discount","freeProduct","service","access","giftCard"],"type":"string","description":"What a benefit gives the holder, used to group and label it on the benefits page and in reports."},"BenefitContent":{"type":"object","properties":{"title":{"description":"Name the holder sees (\"Untitled benefit\" when absent). At most 200 characters per language.","$ref":"#/components/schemas/LocalizedString"},"description":{"description":"What the benefit gives and how to use it; shown on the benefits page and, when the behavior asks, on the back of the wallet pass. At most 2000 characters per language.","$ref":"#/components/schemas/LocalizedString"}},"additionalProperties":false,"description":"Holder-facing texts of a benefit."},"LocalizedString":{"type":"object","properties":{"value":{"type":["null","string"],"description":"Default-language text."},"values":{"type":["null","object"],"additionalProperties":{"type":"string"},"description":"Translations by language code (e.g. `en`, `fr-FR`); a missing culture falls back to `Value`.","propertyNames":{"pattern":"^[a-z]{2}(-[A-Z]{2})?$"}}},"additionalProperties":false,"description":"Text with a default value and optional per-language translations."},"BenefitAppearance":{"type":"object","properties":{"images":{"type":"array","items":{"description":"A benefit image and its slot.","$ref":"#/components/schemas/BenefitImage"},"description":"Images, at most one per slot."}},"additionalProperties":false,"description":"Images of a benefit, at most one per slot."},"BenefitImage":{"required":["slot","url"],"type":"object","properties":{"slot":{"description":"Where the image is used: `main` for the headline visual (and the pass artwork when the behavior asks), `thumbnail` for compact lists.","$ref":"#/components/schemas/BenefitImageSlot"},"url":{"description":"Localized image URL: a benefit template image path, or the absolute URL given by an integration.","$ref":"#/components/schemas/LocalizedString"}},"additionalProperties":false,"description":"A benefit image and its slot."},"BenefitImageSlot":{"enum":["main","thumbnail"],"type":"string","description":"The image slots a benefit can fill."},"BenefitLink":{"required":["link"],"type":"object","properties":{"title":{"description":"Button label on the benefits page. At most 100 characters per language.","$ref":"#/components/schemas/LocalizedString"},"link":{"type":"string","description":"Absolute `https` URI the button opens; any other scheme is refused with 400.","format":"uri"}},"additionalProperties":false,"description":"A link with a localized title and a target URI."},"BenefitData":{"type":"object","properties":{"value":{"type":["null","number"],"description":"Uses left: the sum of all movements. A `oneTime` or `multiUse` benefit at 0 or below is `used`; for `unlimited`, a tally of what was taken. Computed on read, ignored on write.","format":"double"},"movements":{"type":"array","items":{"description":"A change to a benefit's value (issuance, redemption, adjustment); immutable. Movements applied through `POST benefits/{benefitId}/movements` get their id and date from the server.","$ref":"#/components/schemas/BenefitMovement"},"description":"Every change to the balance, oldest first; append-only. On write, accepted only while the benefit has no movement, to open it with a non-default balance (a `oneTime` benefit takes exactly one movement of 1). POST assigns new movement ids; PUT keeps the caller's."},"content":{"maxLength":500,"minLength":1,"type":["null","string"],"description":"Free text returned as sent, read by a pass template as `benefits.{processId}.data.content` (e.g. a discount rate). Omitted without `tenant.pass:read`. 1 to 500 characters."}},"additionalProperties":false,"description":"Balance, movements and provider payload of a benefit."},"BenefitMovement":{"required":["date","movementId","value"],"type":"object","properties":{"movementId":{"type":"string","description":"Movement id, unique within the benefit; replaced on POST."},"date":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When it happened; stored as sent, so an integration can replay its history. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"value":{"type":"number","description":"Change to the balance: positive credits, negative redeems.","format":"double"},"remarks":{"maxLength":500,"minLength":1,"type":["null","string"],"description":"Note about the movement, e.g. a till receipt number; never shown to the holder, omitted without `tenant.pass:read`. 1 to 500 characters."}},"additionalProperties":false,"description":"A change to a benefit's value (issuance, redemption, adjustment); immutable. Movements applied through `POST benefits/{benefitId}/movements` get their id and date from the server."},"BenefitBehavior":{"type":"object","properties":{"target":{"type":["null","number"],"description":"Uses a `multiUse` benefit started with (the 5 in \"3 of 5 left\", where 3 is `data.value`); null when there is no allowance.","format":"double"},"mode":{"type":"array","items":{"description":"Where a benefit is shown and what each surface does with it.","$ref":"#/components/schemas/BenefitBehaviorMode"},"description":"Where the benefit is surfaced and what each surface does with it; set from the template at issue, empty without `tenant.pass:read`."}},"additionalProperties":false,"description":"Behavior settings of a benefit."},"BenefitBehaviorMode":{"enum":["none","showOnBenefitListPage","showOnPassScanner","redeemOnScan","useMainImageAsWalletImage","addDescriptionAsBackField"],"type":"string","description":"Where a benefit is shown and what each surface does with it."},"BenefitCode":{"type":"object","properties":{"value":{"type":["null","string"],"description":"The code itself, as encoded in the barcode and shown under it."},"format":{"description":"How the code is rendered: a barcode symbology, or `raw` for text only. `ean13` needs 12 or 13 digits.","$ref":"#/components/schemas/BenefitCodeFormat"}},"additionalProperties":false,"description":"Barcode or reference code with its display format."},"BenefitCodeFormat":{"enum":["ean13","code128","qrCode","pdf417","aztec","dataMatrix","code39","raw"],"type":"string","description":"How a benefit's code is rendered to the holder, and so what the till or the Pass Scanner scans."},"BenefitStatus":{"enum":["scheduled","active","used","expired","removed"],"type":"string","description":"What a benefit is doing right now, in the terms an operator answers the holder in."},"ProblemDetails":{"type":"object","properties":{"type":{"type":["null","string"]},"title":{"type":["null","string"]},"status":{"type":["null","integer"],"format":"int32"},"detail":{"type":["null","string"]},"instance":{"type":["null","string"]}},"additionalProperties":{}},"HttpValidationProblemDetails":{"type":"object","allOf":[{"$ref":"#/components/schemas/ProblemDetails"}],"properties":{"errors":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}},"additionalProperties":{}}}},"paths":{"/api/{tenantId}/passes/{passId}/benefits":{"get":{"tags":["Benefit"],"summary":"List pass benefits","description":"With the `tenant.pass:read` scope: every benefit and field. Without it, the pass holder's view:\nonly benefits not yet removed and shown on the benefit list page (`behavior.showOnBenefitListPage`, default true), holder-facing fields only\n(no `origin`, `tags`, `passType`, `behavior.mode`, `data.content`, movement `remarks`, removal or modification dates).\nExpired and exhausted benefits are returned until their deletion date.\nSorted by priority descending; the holder's view breaks ties by creation date descending.","parameters":[{"name":"passId","in":"path","description":"Id of the pass.","required":true,"schema":{"type":"string"}},{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Benefits returned; an empty array is normal.","content":{"text/plain":{"schema":{"type":"array","items":{"description":"A stored benefit with its unique id.","$ref":"#/components/schemas/Benefit"}}},"application/json":{"schema":{"type":"array","items":{"description":"A stored benefit with its unique id.","$ref":"#/components/schemas/Benefit"}}},"text/json":{"schema":{"type":"array","items":{"description":"A stored benefit with its unique id.","$ref":"#/components/schemas/Benefit"}}}}},"401":{"description":"The credentials sent are invalid.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"404":{"description":"Pass not found.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"429":{"description":"Over 1200 requests per minute to this endpoint for the tenant (default limit); retry after <c>Retry-After</c>.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent when the limiter knows it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}}}}}}}
```

## Create pass benefit

> Same as \`POST /passes/benefits\`, with the pass id in the route.

```json
{"openapi":"3.1.1","info":{"title":"Neostore internal API","version":"v1"},"tags":[{"name":"Benefit"}],"servers":[{"url":"https://app.neostore.cloud","description":"Production Server"},{"url":"https://app-qa.neostore.cloud","description":"Staging Server"}],"security":[{"admin-bearer":["tenant.pass:write"]},{"apiKey":[]}],"components":{"securitySchemes":{"admin-bearer":{"type":"oauth2","flows":{"implicit":{"authorizationUrl":"https://auth.neostore.cloud/authorize?audience=https://app.neostore.cloud/api/","scopes":{}}}},"apiKey":{"type":"apiKey","name":"X-API-KEY","in":"header"}},"schemas":{"InsertBenefitRequest":{"required":["type"],"type":"object","allOf":[{"description":"Fields shared by the create and upsert requests. On update, an omitted field keeps its current value.","$ref":"#/components/schemas/BenefitRequestBase"}],"properties":{"type":{"description":"How the benefit is consumed: `oneTime` once, `multiUse` counts down uses, `unlimited` is never used up.","$ref":"#/components/schemas/BenefitType"},"category":{"description":"What the benefit gives the holder. `giftCard` is accepted for `multiUse` only; other combinations are refused with 400.","$ref":"#/components/schemas/BenefitCategory"},"passType":{"type":["null","string"],"description":"Pass template of a standalone wallet pass created for this benefit, alongside the holder's pass (e.g. a gift card); its id is returned as `linkedPassId`. Null: no linked pass."},"options":{"description":"Processing options.","$ref":"#/components/schemas/InsertOptions"}},"additionalProperties":false,"description":"Request to create a benefit; `type` is required."},"BenefitRequestBase":{"type":"object","properties":{"priority":{"type":["null","integer"],"description":"Rank among the pass's benefits: higher first, none last. Orders lists and wallet back fields, and picks whose main image becomes the pass artwork.","format":"int32"},"origin":{"description":"Where the benefit comes from in the caller's system. Set `externalId` or `processId` to make a POST idempotent: it updates the benefit it created instead of adding another.","$ref":"#/components/schemas/BenefitOrigin"},"tags":{"type":["null","array"],"items":{"type":"string"},"description":"Labels for segmenting and reporting, never shown to the holder. At most 20 tags of 50 characters; trimmed, blanks and case-insensitive duplicates dropped. An empty array clears them on update."},"content":{"description":"Title and description the holder reads.","$ref":"#/components/schemas/BenefitContent"},"appearance":{"description":"Images the benefit carries.","$ref":"#/components/schemas/BenefitAppearanceRequest"},"links":{"type":["null","object"],"properties":{"legalInformation":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"callToAction":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"termsAndConditions":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"privacyPolicy":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"howToUse":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"website":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"other":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"}},"additionalProperties":false,"description":"Links shown on the benefits page, keyed by purpose."},"data":{"description":"Opening movements and provider payload. Movements are accepted only while the benefit has none (afterwards use the movements endpoint); without them a `oneTime` benefit starts with one use and a `multiUse` one with `behavior.target`.","$ref":"#/components/schemas/BenefitData"},"activationDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When the benefit becomes redeemable; omitted on create means immediately. Before it, status is `scheduled` and redemption is refused. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"deactivationDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When redemption stops; omitted on create means never. Afterwards status is `expired` until the deletion date. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"deletionDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When the benefit is removed from the pass; omitted on create means never. When omitted on an update that extends Neo.Web.Api.Benefit.Controllers.BenefitPassController.BenefitRequestBase.DeactivationDate, it moves forward by the same amount. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"behavior":{"description":"Allowance of a `multiUse` benefit and where the benefit is surfaced.","$ref":"#/components/schemas/BenefitBehaviorRequest"},"code":{"description":"Code staff scan or type to redeem the benefit.","$ref":"#/components/schemas/BenefitCode"}},"additionalProperties":false,"description":"Fields shared by the create and upsert requests. On update, an omitted field keeps its current value."},"BenefitOrigin":{"type":"object","properties":{"externalId":{"maxLength":100,"minLength":1,"type":["null","string"],"description":"External id, unique within the tenant. On POST, upserts on it first (before `processId`): updates the existing benefit, or 409 if it belongs to another pass. Kept by later writes that omit it. At most 100 characters."},"generator":{"maxLength":100,"minLength":1,"type":["null","string"],"description":"Name of the upstream system, e.g. `crm`; `benefitTemplate` marks a benefit issued from a template. Informative only. At most 100 characters."},"processId":{"maxLength":100,"minLength":1,"type":["null","string"],"description":"Id of the upstream process or batch job (e.g. `campaign3`). On POST without `externalId`, upserts on (pass, processId); need not be unique across passes. A pass template reads the benefit as `benefits.{processId}`, each run of characters other than letters, digits and `_` becoming one `_`. At most 100 characters."}},"additionalProperties":false,"description":"Where a benefit comes from in an external system, for idempotency and traceability."},"BenefitContent":{"type":"object","properties":{"title":{"description":"Name the holder sees (\"Untitled benefit\" when absent). At most 200 characters per language.","$ref":"#/components/schemas/LocalizedString"},"description":{"description":"What the benefit gives and how to use it; shown on the benefits page and, when the behavior asks, on the back of the wallet pass. At most 2000 characters per language.","$ref":"#/components/schemas/LocalizedString"}},"additionalProperties":false,"description":"Holder-facing texts of a benefit."},"LocalizedString":{"type":"object","properties":{"value":{"type":["null","string"],"description":"Default-language text."},"values":{"type":["null","object"],"additionalProperties":{"type":"string"},"description":"Translations by language code (e.g. `en`, `fr-FR`); a missing culture falls back to `Value`.","propertyNames":{"pattern":"^[a-z]{2}(-[A-Z]{2})?$"}}},"additionalProperties":false,"description":"Text with a default value and optional per-language translations."},"BenefitAppearanceRequest":{"type":"object","properties":{"images":{"type":"array","items":{"description":"A benefit image and its slot.","$ref":"#/components/schemas/BenefitImage"},"description":"Images, at most one per slot. Each URL is a web address, or a benefit template image path exactly as a read with `tenant.pass:read` returns it."},"mainImageUrl":{"description":"Deprecated: use Neo.Web.Api.Benefit.Controllers.BenefitPassController.BenefitAppearanceRequest.Images. The main image.","$ref":"#/components/schemas/LocalizedString"},"thumbnailUrl":{"description":"Deprecated: use Neo.Web.Api.Benefit.Controllers.BenefitPassController.BenefitAppearanceRequest.Images. The thumbnail image.","$ref":"#/components/schemas/LocalizedString"}},"additionalProperties":false,"description":"Images of an incoming benefit. `mainImageUrl` and `thumbnailUrl` are deprecated single-slot fields folded into `images`; an explicit `images` entry wins for the same slot."},"BenefitImage":{"required":["slot","url"],"type":"object","properties":{"slot":{"description":"Where the image is used: `main` for the headline visual (and the pass artwork when the behavior asks), `thumbnail` for compact lists.","$ref":"#/components/schemas/BenefitImageSlot"},"url":{"description":"Localized image URL: a benefit template image path, or the absolute URL given by an integration.","$ref":"#/components/schemas/LocalizedString"}},"additionalProperties":false,"description":"A benefit image and its slot."},"BenefitImageSlot":{"enum":["main","thumbnail"],"type":"string","description":"The image slots a benefit can fill."},"BenefitLink":{"required":["link"],"type":"object","properties":{"title":{"description":"Button label on the benefits page. At most 100 characters per language.","$ref":"#/components/schemas/LocalizedString"},"link":{"type":"string","description":"Absolute `https` URI the button opens; any other scheme is refused with 400.","format":"uri"}},"additionalProperties":false,"description":"A link with a localized title and a target URI."},"BenefitData":{"type":"object","properties":{"value":{"type":["null","number"],"description":"Uses left: the sum of all movements. A `oneTime` or `multiUse` benefit at 0 or below is `used`; for `unlimited`, a tally of what was taken. Computed on read, ignored on write.","format":"double"},"movements":{"type":"array","items":{"description":"A change to a benefit's value (issuance, redemption, adjustment); immutable. Movements applied through `POST benefits/{benefitId}/movements` get their id and date from the server.","$ref":"#/components/schemas/BenefitMovement"},"description":"Every change to the balance, oldest first; append-only. On write, accepted only while the benefit has no movement, to open it with a non-default balance (a `oneTime` benefit takes exactly one movement of 1). POST assigns new movement ids; PUT keeps the caller's."},"content":{"maxLength":500,"minLength":1,"type":["null","string"],"description":"Free text returned as sent, read by a pass template as `benefits.{processId}.data.content` (e.g. a discount rate). Omitted without `tenant.pass:read`. 1 to 500 characters."}},"additionalProperties":false,"description":"Balance, movements and provider payload of a benefit."},"BenefitMovement":{"required":["date","movementId","value"],"type":"object","properties":{"movementId":{"type":"string","description":"Movement id, unique within the benefit; replaced on POST."},"date":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When it happened; stored as sent, so an integration can replay its history. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"value":{"type":"number","description":"Change to the balance: positive credits, negative redeems.","format":"double"},"remarks":{"maxLength":500,"minLength":1,"type":["null","string"],"description":"Note about the movement, e.g. a till receipt number; never shown to the holder, omitted without `tenant.pass:read`. 1 to 500 characters."}},"additionalProperties":false,"description":"A change to a benefit's value (issuance, redemption, adjustment); immutable. Movements applied through `POST benefits/{benefitId}/movements` get their id and date from the server."},"BenefitBehaviorRequest":{"type":"object","properties":{"target":{"type":["null","number"],"description":"Uses a `multiUse` benefit starts with: seeds the balance of a benefit created without movements and caps what a later credit can give back.","format":"double"},"mode":{"type":["null","array"],"items":{"description":"Where a benefit is shown and what each surface does with it.","$ref":"#/components/schemas/BenefitBehaviorMode"},"description":"Where the benefit is surfaced and what each surface does with it; omit for the default."},"autoRedeem":{"type":["null","boolean"],"description":"Deprecated: include Neo.Runtime.Benefit.Data.BenefitBehaviorMode.RedeemOnScan in Neo.Web.Api.Benefit.Controllers.BenefitPassController.BenefitBehaviorRequest.Mode instead."}},"additionalProperties":false,"description":"Behavior of an incoming benefit. Replaces the behavior as a whole: without `mode`, the default surfaces apply (listed everywhere, image on the pass, description on the back), not the previous ones; an empty array means none."},"BenefitBehaviorMode":{"enum":["none","showOnBenefitListPage","showOnPassScanner","redeemOnScan","useMainImageAsWalletImage","addDescriptionAsBackField"],"type":"string","description":"Where a benefit is shown and what each surface does with it."},"BenefitCode":{"type":"object","properties":{"value":{"type":["null","string"],"description":"The code itself, as encoded in the barcode and shown under it."},"format":{"description":"How the code is rendered: a barcode symbology, or `raw` for text only. `ean13` needs 12 or 13 digits.","$ref":"#/components/schemas/BenefitCodeFormat"}},"additionalProperties":false,"description":"Barcode or reference code with its display format."},"BenefitCodeFormat":{"enum":["ean13","code128","qrCode","pdf417","aztec","dataMatrix","code39","raw"],"type":"string","description":"How a benefit's code is rendered to the holder, and so what the till or the Pass Scanner scans."},"BenefitType":{"enum":["oneTime","unlimited","multiUse"],"type":"string","description":"How a benefit is consumed, which decides what its movements mean and when it is used up."},"BenefitCategory":{"enum":["other","discount","freeProduct","service","access","giftCard"],"type":"string","description":"What a benefit gives the holder, used to group and label it on the benefits page and in reports."},"InsertOptions":{"type":"object","properties":{"correlationId":{"maxLength":100,"minLength":10,"type":["null","string"],"description":"Caller's own call id (10-100 characters), e.g. a campaign run id, to trace a batch. Not stored."},"notification":{"description":"Push message sent to the pass once the benefit is written, same payload as the pass notification endpoints. Omit for no notification.","$ref":"#/components/schemas/NotifyRequest"}},"additionalProperties":false,"description":"Options of benefit creation."},"NotifyRequest":{"type":"object","properties":{"content":{"type":["null","string"],"description":"Message for devices whose language has no entry in `localizedContent`. Optional when `localizedContent` has text; defaults to the tenant's default language entry, else the first language in ordinal order."},"localizedContent":{"type":["null","object"],"additionalProperties":{"type":"string"},"description":"Message per device language. Keys are a lowercase ISO 639-1 code, optionally with an uppercase\nregion (`en`, `fr-FR`). Entries with blank text are not sent.","propertyNames":{"pattern":"^[a-z]{2}(-[A-Z]{2})?$"}},"options":{"description":"Delivery options. Omitted, the notification is queued without refreshing pass data.","$ref":"#/components/schemas/NotifyRequestOptions"}},"additionalProperties":false,"description":"The message to show the holder, in one or several languages."},"NotifyRequestOptions":{"type":"object","properties":{"updateData":{"type":"boolean","description":"Also refreshes the Google Wallet pass content with the message. Apple always refreshes the whole pass.","default":false},"correlationId":{"maxLength":100,"minLength":10,"type":["null","string"],"description":"Caller-chosen id for tracing, such as a campaign or batch. 10 to 100 characters after trimming, otherwise 400."},"bypassQueue":{"type":"boolean","description":"Sends within the request (200) instead of queuing (202), for urgent low-volume messages.","default":false}},"additionalProperties":false,"description":"How the notification is delivered."},"UpsertBenefitResponse":{"type":"object","properties":{"benefitId":{"type":"string","description":"Id of the benefit written (the existing one when a POST upserted on its origin)."}},"additionalProperties":false,"description":"Written benefit."},"ProblemDetails":{"type":"object","properties":{"type":{"type":["null","string"]},"title":{"type":["null","string"]},"status":{"type":["null","integer"],"format":"int32"},"detail":{"type":["null","string"]},"instance":{"type":["null","string"]}},"additionalProperties":{}},"HttpValidationProblemDetails":{"type":"object","allOf":[{"$ref":"#/components/schemas/ProblemDetails"}],"properties":{"errors":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}},"additionalProperties":{}}}},"paths":{"/api/{tenantId}/passes/{passId}/benefits":{"post":{"tags":["Benefit"],"summary":"Create pass benefit","description":"Same as `POST /passes/benefits`, with the pass id in the route.","parameters":[{"name":"passId","in":"path","description":"Id of the pass receiving the benefit.","required":true,"schema":{"type":"string"}},{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Benefit to create, or to update when its origin matches an existing benefit.","content":{"application/json":{"schema":{"description":"Request to create a benefit; `type` is required.","$ref":"#/components/schemas/InsertBenefitRequest"}},"text/json":{"schema":{"description":"Request to create a benefit; `type` is required.","$ref":"#/components/schemas/InsertBenefitRequest"}},"application/*+json":{"schema":{"description":"Request to create a benefit; `type` is required.","$ref":"#/components/schemas/InsertBenefitRequest"}}}},"responses":{"200":{"description":"Benefit created or updated; returns the benefit id.","content":{"text/plain":{"schema":{"description":"Written benefit.","$ref":"#/components/schemas/UpsertBenefitResponse"}},"application/json":{"schema":{"description":"Written benefit.","$ref":"#/components/schemas/UpsertBenefitResponse"}},"text/json":{"schema":{"description":"Written benefit.","$ref":"#/components/schemas/UpsertBenefitResponse"}}}},"400":{"description":"Invalid payload, category not allowed for the benefit type, or `data.movements` refused.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"401":{"description":"The caller is not authenticated.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"403":{"description":"The caller lacks the <c>tenant.pass:write</c> scope.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"404":{"description":"Pass not found.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"409":{"description":"The externalId is bound to another pass, the pass is at its maximum number of benefits, or another request is writing the same benefit; retry shortly.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"429":{"description":"Over 1200 requests per minute to this endpoint for the tenant (default limit); retry after <c>Retry-After</c>.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent when the limiter knows it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}}}}}}}
```

## Get pass benefit

> Returns 404 when the pass or the benefit is missing, or the benefit belongs to another pass.

```json
{"openapi":"3.1.1","info":{"title":"Neostore internal API","version":"v1"},"tags":[{"name":"Benefit"}],"servers":[{"url":"https://app.neostore.cloud","description":"Production Server"},{"url":"https://app-qa.neostore.cloud","description":"Staging Server"}],"security":[{"admin-bearer":["tenant.pass:read"]},{"apiKey":[]}],"components":{"securitySchemes":{"admin-bearer":{"type":"oauth2","flows":{"implicit":{"authorizationUrl":"https://auth.neostore.cloud/authorize?audience=https://app.neostore.cloud/api/","scopes":{}}}},"apiKey":{"type":"apiKey","name":"X-API-KEY","in":"header"}},"schemas":{"Benefit":{"required":["benefitId"],"type":"object","allOf":[{"description":"A benefit as read back.","$ref":"#/components/schemas/BenefitBase"}],"properties":{"benefitId":{"type":"string","description":"Benefit id, unique within the tenant."},"passType":{"type":["null","string"],"description":"Pass template of the linked standalone wallet pass; null if none, or without `tenant.pass:read`."},"linkedPassId":{"type":["null","string"],"description":"Id of the linked wallet pass; null until it is created."}},"additionalProperties":false,"description":"A stored benefit with its unique id."},"BenefitBase":{"required":["category","status","type"],"type":"object","properties":{"priority":{"type":["null","integer"],"description":"Rank among the pass's benefits: higher first, none last. Orders lists and wallet back fields, and picks whose main image becomes the pass artwork.","format":"int32"},"origin":{"description":"Where the benefit came from: a benefit template (`generator``benefitTemplate`, `processId` the template id) or an integration. Omitted without `tenant.pass:read`.","$ref":"#/components/schemas/BenefitOrigin"},"type":{"description":"How the benefit is consumed: `oneTime` once, `multiUse` counts down uses, `unlimited` is never used up.","$ref":"#/components/schemas/BenefitType"},"category":{"description":"What the benefit gives the holder; `other` when stored without one.","$ref":"#/components/schemas/BenefitCategory"},"tags":{"type":"array","items":{"type":"string"},"description":"Labels for segmenting and reporting, never shown to the holder; omitted without `tenant.pass:read`."},"content":{"description":"Title and description the holder reads.","$ref":"#/components/schemas/BenefitContent"},"appearance":{"description":"Images, at most one per slot. With `tenant.pass:read`, a benefit template image is returned as its image path (the value to send back on write); otherwise as a loadable URL.","$ref":"#/components/schemas/BenefitAppearance"},"links":{"type":["null","object"],"properties":{"legalInformation":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"callToAction":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"termsAndConditions":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"privacyPolicy":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"howToUse":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"website":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"other":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"}},"additionalProperties":false,"description":"Links shown on the benefits page, keyed by purpose."},"data":{"description":"Balance, movement history and provider payload.","$ref":"#/components/schemas/BenefitData"},"activationDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When the benefit becomes redeemable; null means immediately. Before it, status is `scheduled` and redemption is refused. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"deactivationDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When redemption stops; null means never. Afterwards status is `expired` until the deletion date. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"deletionDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When the benefit leaves the pass; null means never, and without `tenant.pass:read`. Until then it counts towards the 20 benefits a pass can carry, even if no longer redeemable. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"behavior":{"description":"Allowance of a `multiUse` benefit and where the benefit is surfaced.","$ref":"#/components/schemas/BenefitBehavior"},"code":{"description":"Code staff scan or type to redeem the benefit, with its rendering; null when none.","$ref":"#/components/schemas/BenefitCode"},"creationDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When the benefit was issued; server-set, ignored on write. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"lastModificationDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When the benefit last changed (update, movement, template refresh); server-set, null without `tenant.pass:read`. Editing a template does not move it unless the edit refreshes issued benefits. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"status":{"description":"Current state, derived from dates and balance. `active` means a movement would be accepted.","$ref":"#/components/schemas/BenefitStatus"}},"additionalProperties":false,"description":"A benefit as read back."},"BenefitOrigin":{"type":"object","properties":{"externalId":{"maxLength":100,"minLength":1,"type":["null","string"],"description":"External id, unique within the tenant. On POST, upserts on it first (before `processId`): updates the existing benefit, or 409 if it belongs to another pass. Kept by later writes that omit it. At most 100 characters."},"generator":{"maxLength":100,"minLength":1,"type":["null","string"],"description":"Name of the upstream system, e.g. `crm`; `benefitTemplate` marks a benefit issued from a template. Informative only. At most 100 characters."},"processId":{"maxLength":100,"minLength":1,"type":["null","string"],"description":"Id of the upstream process or batch job (e.g. `campaign3`). On POST without `externalId`, upserts on (pass, processId); need not be unique across passes. A pass template reads the benefit as `benefits.{processId}`, each run of characters other than letters, digits and `_` becoming one `_`. At most 100 characters."}},"additionalProperties":false,"description":"Where a benefit comes from in an external system, for idempotency and traceability."},"BenefitType":{"enum":["oneTime","unlimited","multiUse"],"type":"string","description":"How a benefit is consumed, which decides what its movements mean and when it is used up."},"BenefitCategory":{"enum":["other","discount","freeProduct","service","access","giftCard"],"type":"string","description":"What a benefit gives the holder, used to group and label it on the benefits page and in reports."},"BenefitContent":{"type":"object","properties":{"title":{"description":"Name the holder sees (\"Untitled benefit\" when absent). At most 200 characters per language.","$ref":"#/components/schemas/LocalizedString"},"description":{"description":"What the benefit gives and how to use it; shown on the benefits page and, when the behavior asks, on the back of the wallet pass. At most 2000 characters per language.","$ref":"#/components/schemas/LocalizedString"}},"additionalProperties":false,"description":"Holder-facing texts of a benefit."},"LocalizedString":{"type":"object","properties":{"value":{"type":["null","string"],"description":"Default-language text."},"values":{"type":["null","object"],"additionalProperties":{"type":"string"},"description":"Translations by language code (e.g. `en`, `fr-FR`); a missing culture falls back to `Value`.","propertyNames":{"pattern":"^[a-z]{2}(-[A-Z]{2})?$"}}},"additionalProperties":false,"description":"Text with a default value and optional per-language translations."},"BenefitAppearance":{"type":"object","properties":{"images":{"type":"array","items":{"description":"A benefit image and its slot.","$ref":"#/components/schemas/BenefitImage"},"description":"Images, at most one per slot."}},"additionalProperties":false,"description":"Images of a benefit, at most one per slot."},"BenefitImage":{"required":["slot","url"],"type":"object","properties":{"slot":{"description":"Where the image is used: `main` for the headline visual (and the pass artwork when the behavior asks), `thumbnail` for compact lists.","$ref":"#/components/schemas/BenefitImageSlot"},"url":{"description":"Localized image URL: a benefit template image path, or the absolute URL given by an integration.","$ref":"#/components/schemas/LocalizedString"}},"additionalProperties":false,"description":"A benefit image and its slot."},"BenefitImageSlot":{"enum":["main","thumbnail"],"type":"string","description":"The image slots a benefit can fill."},"BenefitLink":{"required":["link"],"type":"object","properties":{"title":{"description":"Button label on the benefits page. At most 100 characters per language.","$ref":"#/components/schemas/LocalizedString"},"link":{"type":"string","description":"Absolute `https` URI the button opens; any other scheme is refused with 400.","format":"uri"}},"additionalProperties":false,"description":"A link with a localized title and a target URI."},"BenefitData":{"type":"object","properties":{"value":{"type":["null","number"],"description":"Uses left: the sum of all movements. A `oneTime` or `multiUse` benefit at 0 or below is `used`; for `unlimited`, a tally of what was taken. Computed on read, ignored on write.","format":"double"},"movements":{"type":"array","items":{"description":"A change to a benefit's value (issuance, redemption, adjustment); immutable. Movements applied through `POST benefits/{benefitId}/movements` get their id and date from the server.","$ref":"#/components/schemas/BenefitMovement"},"description":"Every change to the balance, oldest first; append-only. On write, accepted only while the benefit has no movement, to open it with a non-default balance (a `oneTime` benefit takes exactly one movement of 1). POST assigns new movement ids; PUT keeps the caller's."},"content":{"maxLength":500,"minLength":1,"type":["null","string"],"description":"Free text returned as sent, read by a pass template as `benefits.{processId}.data.content` (e.g. a discount rate). Omitted without `tenant.pass:read`. 1 to 500 characters."}},"additionalProperties":false,"description":"Balance, movements and provider payload of a benefit."},"BenefitMovement":{"required":["date","movementId","value"],"type":"object","properties":{"movementId":{"type":"string","description":"Movement id, unique within the benefit; replaced on POST."},"date":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When it happened; stored as sent, so an integration can replay its history. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"value":{"type":"number","description":"Change to the balance: positive credits, negative redeems.","format":"double"},"remarks":{"maxLength":500,"minLength":1,"type":["null","string"],"description":"Note about the movement, e.g. a till receipt number; never shown to the holder, omitted without `tenant.pass:read`. 1 to 500 characters."}},"additionalProperties":false,"description":"A change to a benefit's value (issuance, redemption, adjustment); immutable. Movements applied through `POST benefits/{benefitId}/movements` get their id and date from the server."},"BenefitBehavior":{"type":"object","properties":{"target":{"type":["null","number"],"description":"Uses a `multiUse` benefit started with (the 5 in \"3 of 5 left\", where 3 is `data.value`); null when there is no allowance.","format":"double"},"mode":{"type":"array","items":{"description":"Where a benefit is shown and what each surface does with it.","$ref":"#/components/schemas/BenefitBehaviorMode"},"description":"Where the benefit is surfaced and what each surface does with it; set from the template at issue, empty without `tenant.pass:read`."}},"additionalProperties":false,"description":"Behavior settings of a benefit."},"BenefitBehaviorMode":{"enum":["none","showOnBenefitListPage","showOnPassScanner","redeemOnScan","useMainImageAsWalletImage","addDescriptionAsBackField"],"type":"string","description":"Where a benefit is shown and what each surface does with it."},"BenefitCode":{"type":"object","properties":{"value":{"type":["null","string"],"description":"The code itself, as encoded in the barcode and shown under it."},"format":{"description":"How the code is rendered: a barcode symbology, or `raw` for text only. `ean13` needs 12 or 13 digits.","$ref":"#/components/schemas/BenefitCodeFormat"}},"additionalProperties":false,"description":"Barcode or reference code with its display format."},"BenefitCodeFormat":{"enum":["ean13","code128","qrCode","pdf417","aztec","dataMatrix","code39","raw"],"type":"string","description":"How a benefit's code is rendered to the holder, and so what the till or the Pass Scanner scans."},"BenefitStatus":{"enum":["scheduled","active","used","expired","removed"],"type":"string","description":"What a benefit is doing right now, in the terms an operator answers the holder in."},"ProblemDetails":{"type":"object","properties":{"type":{"type":["null","string"]},"title":{"type":["null","string"]},"status":{"type":["null","integer"],"format":"int32"},"detail":{"type":["null","string"]},"instance":{"type":["null","string"]}},"additionalProperties":{}},"HttpValidationProblemDetails":{"type":"object","allOf":[{"$ref":"#/components/schemas/ProblemDetails"}],"properties":{"errors":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}},"additionalProperties":{}}}},"paths":{"/api/{tenantId}/passes/{passId}/benefits/{benefitId}":{"get":{"tags":["Benefit"],"summary":"Get pass benefit","description":"Returns 404 when the pass or the benefit is missing, or the benefit belongs to another pass.","parameters":[{"name":"passId","in":"path","description":"Id of the pass carrying the benefit.","required":true,"schema":{"type":"string"}},{"name":"benefitId","in":"path","description":"Id of the benefit.","required":true,"schema":{"type":"string"}},{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Benefit returned.","content":{"text/plain":{"schema":{"description":"A stored benefit with its unique id.","$ref":"#/components/schemas/Benefit"}},"application/json":{"schema":{"description":"A stored benefit with its unique id.","$ref":"#/components/schemas/Benefit"}},"text/json":{"schema":{"description":"A stored benefit with its unique id.","$ref":"#/components/schemas/Benefit"}}}},"401":{"description":"The caller is not authenticated.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"403":{"description":"The caller lacks the <c>tenant.pass:read</c> scope.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"404":{"description":"Pass or benefit not found.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"429":{"description":"Over 1200 requests per minute to this endpoint for the tenant (default limit); retry after <c>Retry-After</c>.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent when the limiter knows it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}}}}}}}
```

## Update pass benefit

> Updates an existing benefit of the pass. Omitted fields keep their value; a field sent replaces that field as a whole (images, links, tags, behavior). \`passType\`: null keeps the current one, an empty string unlinks it.\
> \`data.movements\` is accepted only while the benefit has no movement; afterwards use \`POST /passes/benefits/{benefitId}/movements\`.\
> \`options.notification\` sends a push message once the benefit is written, and the pass is updated on the holder's devices.

```json
{"openapi":"3.1.1","info":{"title":"Neostore internal API","version":"v1"},"tags":[{"name":"Benefit"}],"servers":[{"url":"https://app.neostore.cloud","description":"Production Server"},{"url":"https://app-qa.neostore.cloud","description":"Staging Server"}],"security":[{"admin-bearer":["tenant.pass:write"]},{"apiKey":[]}],"components":{"securitySchemes":{"admin-bearer":{"type":"oauth2","flows":{"implicit":{"authorizationUrl":"https://auth.neostore.cloud/authorize?audience=https://app.neostore.cloud/api/","scopes":{}}}},"apiKey":{"type":"apiKey","name":"X-API-KEY","in":"header"}},"schemas":{"PatchBenefitRequest":{"type":"object","allOf":[{"$ref":"#/components/schemas/BenefitRequestBase"}],"properties":{"type":{"description":"How the benefit is consumed.","$ref":"#/components/schemas/BenefitType"},"category":{"description":"What the benefit gives the holder. `giftCard` is accepted for `multiUse` only.","$ref":"#/components/schemas/BenefitCategory"},"passType":{"type":["null","string"],"description":"Pass template of the standalone wallet pass: null keeps the current one, an empty string unlinks it (a pass already created stays in the holder's wallet)."},"options":{"description":"Processing options.","$ref":"#/components/schemas/PatchOptions"}},"additionalProperties":false,"description":"Request to update a benefit (PUT); omitted fields keep their current value."},"BenefitRequestBase":{"type":"object","properties":{"priority":{"type":["null","integer"],"description":"Rank among the pass's benefits: higher first, none last. Orders lists and wallet back fields, and picks whose main image becomes the pass artwork.","format":"int32"},"origin":{"description":"Where the benefit comes from in the caller's system. Set `externalId` or `processId` to make a POST idempotent: it updates the benefit it created instead of adding another.","$ref":"#/components/schemas/BenefitOrigin"},"tags":{"type":["null","array"],"items":{"type":"string"},"description":"Labels for segmenting and reporting, never shown to the holder. At most 20 tags of 50 characters; trimmed, blanks and case-insensitive duplicates dropped. An empty array clears them on update."},"content":{"description":"Title and description the holder reads.","$ref":"#/components/schemas/BenefitContent"},"appearance":{"description":"Images the benefit carries.","$ref":"#/components/schemas/BenefitAppearanceRequest"},"links":{"type":["null","object"],"properties":{"legalInformation":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"callToAction":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"termsAndConditions":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"privacyPolicy":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"howToUse":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"website":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"other":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"}},"additionalProperties":false,"description":"Links shown on the benefits page, keyed by purpose."},"data":{"description":"Opening movements and provider payload. Movements are accepted only while the benefit has none (afterwards use the movements endpoint); without them a `oneTime` benefit starts with one use and a `multiUse` one with `behavior.target`.","$ref":"#/components/schemas/BenefitData"},"activationDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When the benefit becomes redeemable; omitted on create means immediately. Before it, status is `scheduled` and redemption is refused. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"deactivationDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When redemption stops; omitted on create means never. Afterwards status is `expired` until the deletion date. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"deletionDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When the benefit is removed from the pass; omitted on create means never. When omitted on an update that extends Neo.Web.Api.Benefit.Controllers.BenefitPassController.BenefitRequestBase.DeactivationDate, it moves forward by the same amount. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"behavior":{"description":"Allowance of a `multiUse` benefit and where the benefit is surfaced.","$ref":"#/components/schemas/BenefitBehaviorRequest"},"code":{"description":"Code staff scan or type to redeem the benefit.","$ref":"#/components/schemas/BenefitCode"}},"additionalProperties":false,"description":"Fields shared by the create and upsert requests. On update, an omitted field keeps its current value."},"BenefitOrigin":{"type":"object","properties":{"externalId":{"maxLength":100,"minLength":1,"type":["null","string"],"description":"External id, unique within the tenant. On POST, upserts on it first (before `processId`): updates the existing benefit, or 409 if it belongs to another pass. Kept by later writes that omit it. At most 100 characters."},"generator":{"maxLength":100,"minLength":1,"type":["null","string"],"description":"Name of the upstream system, e.g. `crm`; `benefitTemplate` marks a benefit issued from a template. Informative only. At most 100 characters."},"processId":{"maxLength":100,"minLength":1,"type":["null","string"],"description":"Id of the upstream process or batch job (e.g. `campaign3`). On POST without `externalId`, upserts on (pass, processId); need not be unique across passes. A pass template reads the benefit as `benefits.{processId}`, each run of characters other than letters, digits and `_` becoming one `_`. At most 100 characters."}},"additionalProperties":false,"description":"Where a benefit comes from in an external system, for idempotency and traceability."},"BenefitContent":{"type":"object","properties":{"title":{"description":"Name the holder sees (\"Untitled benefit\" when absent). At most 200 characters per language.","$ref":"#/components/schemas/LocalizedString"},"description":{"description":"What the benefit gives and how to use it; shown on the benefits page and, when the behavior asks, on the back of the wallet pass. At most 2000 characters per language.","$ref":"#/components/schemas/LocalizedString"}},"additionalProperties":false,"description":"Holder-facing texts of a benefit."},"LocalizedString":{"type":"object","properties":{"value":{"type":["null","string"],"description":"Default-language text."},"values":{"type":["null","object"],"additionalProperties":{"type":"string"},"description":"Translations by language code (e.g. `en`, `fr-FR`); a missing culture falls back to `Value`.","propertyNames":{"pattern":"^[a-z]{2}(-[A-Z]{2})?$"}}},"additionalProperties":false,"description":"Text with a default value and optional per-language translations."},"BenefitAppearanceRequest":{"type":"object","properties":{"images":{"type":"array","items":{"description":"A benefit image and its slot.","$ref":"#/components/schemas/BenefitImage"},"description":"Images, at most one per slot. Each URL is a web address, or a benefit template image path exactly as a read with `tenant.pass:read` returns it."},"mainImageUrl":{"description":"Deprecated: use Neo.Web.Api.Benefit.Controllers.BenefitPassController.BenefitAppearanceRequest.Images. The main image.","$ref":"#/components/schemas/LocalizedString"},"thumbnailUrl":{"description":"Deprecated: use Neo.Web.Api.Benefit.Controllers.BenefitPassController.BenefitAppearanceRequest.Images. The thumbnail image.","$ref":"#/components/schemas/LocalizedString"}},"additionalProperties":false,"description":"Images of an incoming benefit. `mainImageUrl` and `thumbnailUrl` are deprecated single-slot fields folded into `images`; an explicit `images` entry wins for the same slot."},"BenefitImage":{"required":["slot","url"],"type":"object","properties":{"slot":{"description":"Where the image is used: `main` for the headline visual (and the pass artwork when the behavior asks), `thumbnail` for compact lists.","$ref":"#/components/schemas/BenefitImageSlot"},"url":{"description":"Localized image URL: a benefit template image path, or the absolute URL given by an integration.","$ref":"#/components/schemas/LocalizedString"}},"additionalProperties":false,"description":"A benefit image and its slot."},"BenefitImageSlot":{"enum":["main","thumbnail"],"type":"string","description":"The image slots a benefit can fill."},"BenefitLink":{"required":["link"],"type":"object","properties":{"title":{"description":"Button label on the benefits page. At most 100 characters per language.","$ref":"#/components/schemas/LocalizedString"},"link":{"type":"string","description":"Absolute `https` URI the button opens; any other scheme is refused with 400.","format":"uri"}},"additionalProperties":false,"description":"A link with a localized title and a target URI."},"BenefitData":{"type":"object","properties":{"value":{"type":["null","number"],"description":"Uses left: the sum of all movements. A `oneTime` or `multiUse` benefit at 0 or below is `used`; for `unlimited`, a tally of what was taken. Computed on read, ignored on write.","format":"double"},"movements":{"type":"array","items":{"description":"A change to a benefit's value (issuance, redemption, adjustment); immutable. Movements applied through `POST benefits/{benefitId}/movements` get their id and date from the server.","$ref":"#/components/schemas/BenefitMovement"},"description":"Every change to the balance, oldest first; append-only. On write, accepted only while the benefit has no movement, to open it with a non-default balance (a `oneTime` benefit takes exactly one movement of 1). POST assigns new movement ids; PUT keeps the caller's."},"content":{"maxLength":500,"minLength":1,"type":["null","string"],"description":"Free text returned as sent, read by a pass template as `benefits.{processId}.data.content` (e.g. a discount rate). Omitted without `tenant.pass:read`. 1 to 500 characters."}},"additionalProperties":false,"description":"Balance, movements and provider payload of a benefit."},"BenefitMovement":{"required":["date","movementId","value"],"type":"object","properties":{"movementId":{"type":"string","description":"Movement id, unique within the benefit; replaced on POST."},"date":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When it happened; stored as sent, so an integration can replay its history. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"value":{"type":"number","description":"Change to the balance: positive credits, negative redeems.","format":"double"},"remarks":{"maxLength":500,"minLength":1,"type":["null","string"],"description":"Note about the movement, e.g. a till receipt number; never shown to the holder, omitted without `tenant.pass:read`. 1 to 500 characters."}},"additionalProperties":false,"description":"A change to a benefit's value (issuance, redemption, adjustment); immutable. Movements applied through `POST benefits/{benefitId}/movements` get their id and date from the server."},"BenefitBehaviorRequest":{"type":"object","properties":{"target":{"type":["null","number"],"description":"Uses a `multiUse` benefit starts with: seeds the balance of a benefit created without movements and caps what a later credit can give back.","format":"double"},"mode":{"type":["null","array"],"items":{"description":"Where a benefit is shown and what each surface does with it.","$ref":"#/components/schemas/BenefitBehaviorMode"},"description":"Where the benefit is surfaced and what each surface does with it; omit for the default."},"autoRedeem":{"type":["null","boolean"],"description":"Deprecated: include Neo.Runtime.Benefit.Data.BenefitBehaviorMode.RedeemOnScan in Neo.Web.Api.Benefit.Controllers.BenefitPassController.BenefitBehaviorRequest.Mode instead."}},"additionalProperties":false,"description":"Behavior of an incoming benefit. Replaces the behavior as a whole: without `mode`, the default surfaces apply (listed everywhere, image on the pass, description on the back), not the previous ones; an empty array means none."},"BenefitBehaviorMode":{"enum":["none","showOnBenefitListPage","showOnPassScanner","redeemOnScan","useMainImageAsWalletImage","addDescriptionAsBackField"],"type":"string","description":"Where a benefit is shown and what each surface does with it."},"BenefitCode":{"type":"object","properties":{"value":{"type":["null","string"],"description":"The code itself, as encoded in the barcode and shown under it."},"format":{"description":"How the code is rendered: a barcode symbology, or `raw` for text only. `ean13` needs 12 or 13 digits.","$ref":"#/components/schemas/BenefitCodeFormat"}},"additionalProperties":false,"description":"Barcode or reference code with its display format."},"BenefitCodeFormat":{"enum":["ean13","code128","qrCode","pdf417","aztec","dataMatrix","code39","raw"],"type":"string","description":"How a benefit's code is rendered to the holder, and so what the till or the Pass Scanner scans."},"BenefitType":{"enum":["oneTime","unlimited","multiUse"],"type":"string","description":"How a benefit is consumed, which decides what its movements mean and when it is used up."},"BenefitCategory":{"enum":["other","discount","freeProduct","service","access","giftCard"],"type":"string","description":"What a benefit gives the holder, used to group and label it on the benefits page and in reports."},"PatchOptions":{"type":"object","properties":{"correlationId":{"maxLength":100,"minLength":10,"type":["null","string"],"description":"Caller's own call id (10-100 characters), e.g. a campaign run id, to trace a batch. Not stored."},"notification":{"description":"Push message sent to the pass once the benefit is written, same payload as the pass notification endpoints. Omit for no notification.","$ref":"#/components/schemas/NotifyRequest"}},"additionalProperties":false,"description":"Options of benefit update."},"NotifyRequest":{"type":"object","properties":{"content":{"type":["null","string"],"description":"Message for devices whose language has no entry in `localizedContent`. Optional when `localizedContent` has text; defaults to the tenant's default language entry, else the first language in ordinal order."},"localizedContent":{"type":["null","object"],"additionalProperties":{"type":"string"},"description":"Message per device language. Keys are a lowercase ISO 639-1 code, optionally with an uppercase\nregion (`en`, `fr-FR`). Entries with blank text are not sent.","propertyNames":{"pattern":"^[a-z]{2}(-[A-Z]{2})?$"}},"options":{"description":"Delivery options. Omitted, the notification is queued without refreshing pass data.","$ref":"#/components/schemas/NotifyRequestOptions"}},"additionalProperties":false,"description":"The message to show the holder, in one or several languages."},"NotifyRequestOptions":{"type":"object","properties":{"updateData":{"type":"boolean","description":"Also refreshes the Google Wallet pass content with the message. Apple always refreshes the whole pass.","default":false},"correlationId":{"maxLength":100,"minLength":10,"type":["null","string"],"description":"Caller-chosen id for tracing, such as a campaign or batch. 10 to 100 characters after trimming, otherwise 400."},"bypassQueue":{"type":"boolean","description":"Sends within the request (200) instead of queuing (202), for urgent low-volume messages.","default":false}},"additionalProperties":false,"description":"How the notification is delivered."},"ProblemDetails":{"type":"object","properties":{"type":{"type":["null","string"]},"title":{"type":["null","string"]},"status":{"type":["null","integer"],"format":"int32"},"detail":{"type":["null","string"]},"instance":{"type":["null","string"]}},"additionalProperties":{}},"HttpValidationProblemDetails":{"type":"object","allOf":[{"$ref":"#/components/schemas/ProblemDetails"}],"properties":{"errors":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}},"additionalProperties":{}}}},"paths":{"/api/{tenantId}/passes/{passId}/benefits/{benefitId}":{"put":{"tags":["Benefit"],"summary":"Update pass benefit","description":"Updates an existing benefit of the pass. Omitted fields keep their value; a field sent replaces that field as a whole (images, links, tags, behavior). `passType`: null keeps the current one, an empty string unlinks it.\n`data.movements` is accepted only while the benefit has no movement; afterwards use `POST /passes/benefits/{benefitId}/movements`.\n`options.notification` sends a push message once the benefit is written, and the pass is updated on the holder's devices.","parameters":[{"name":"passId","in":"path","description":"Id of the pass carrying the benefit.","required":true,"schema":{"type":"string"}},{"name":"benefitId","in":"path","description":"Id of the benefit to update.","required":true,"schema":{"type":"string"}},{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to change.","content":{"application/json":{"schema":{"description":"Request to update a benefit (PUT); omitted fields keep their current value.","$ref":"#/components/schemas/PatchBenefitRequest"}},"text/json":{"schema":{"description":"Request to update a benefit (PUT); omitted fields keep their current value.","$ref":"#/components/schemas/PatchBenefitRequest"}},"application/*+json":{"schema":{"description":"Request to update a benefit (PUT); omitted fields keep their current value.","$ref":"#/components/schemas/PatchBenefitRequest"}}}},"responses":{"200":{"description":"Benefit updated."},"400":{"description":"Invalid payload, category not allowed for the benefit type, or `data.movements` refused.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"401":{"description":"The caller is not authenticated.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"403":{"description":"The caller lacks the <c>tenant.pass:write</c> scope.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"404":{"description":"Pass not found, or the benefit belongs to another pass.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"429":{"description":"Over 1200 requests per minute to this endpoint for the tenant (default limit); retry after <c>Retry-After</c>.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent when the limiter knows it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}}}}}}}
```

## Delete pass benefit

> Permanent. The benefit must belong to the pass. The pass is updated on the holder's devices.

```json
{"openapi":"3.1.1","info":{"title":"Neostore internal API","version":"v1"},"tags":[{"name":"Benefit"}],"servers":[{"url":"https://app.neostore.cloud","description":"Production Server"},{"url":"https://app-qa.neostore.cloud","description":"Staging Server"}],"security":[{"admin-bearer":["tenant.pass:write"]},{"apiKey":[]}],"components":{"securitySchemes":{"admin-bearer":{"type":"oauth2","flows":{"implicit":{"authorizationUrl":"https://auth.neostore.cloud/authorize?audience=https://app.neostore.cloud/api/","scopes":{}}}},"apiKey":{"type":"apiKey","name":"X-API-KEY","in":"header"}},"schemas":{"ProblemDetails":{"type":"object","properties":{"type":{"type":["null","string"]},"title":{"type":["null","string"]},"status":{"type":["null","integer"],"format":"int32"},"detail":{"type":["null","string"]},"instance":{"type":["null","string"]}},"additionalProperties":{}},"HttpValidationProblemDetails":{"type":"object","allOf":[{"$ref":"#/components/schemas/ProblemDetails"}],"properties":{"errors":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}},"additionalProperties":{}}}},"paths":{"/api/{tenantId}/passes/{passId}/benefits/{benefitId}":{"delete":{"tags":["Benefit"],"summary":"Delete pass benefit","description":"Permanent. The benefit must belong to the pass. The pass is updated on the holder's devices.","parameters":[{"name":"passId","in":"path","description":"Id of the pass carrying the benefit.","required":true,"schema":{"type":"string"}},{"name":"benefitId","in":"path","description":"Id of the benefit to delete.","required":true,"schema":{"type":"string"}},{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Benefit deleted."},"401":{"description":"The caller is not authenticated.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"403":{"description":"The caller lacks the <c>tenant.pass:write</c> scope.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"404":{"description":"Pass or benefit not found.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"429":{"description":"Over 1200 requests per minute to this endpoint for the tenant (default limit); retry after <c>Retry-After</c>.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent when the limiter knows it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}}}}}}}
```

## Grant benefit template to pass

> Applies the template's own rules: code, images and the activation, end-of-validity and removal dates come from the template.\
> Re-granting a template the pass already carries refreshes that benefit from the template and keeps its movements.\
> The pass is updated on the holder's devices.

```json
{"openapi":"3.1.1","info":{"title":"Neostore internal API","version":"v1"},"tags":[{"name":"Benefit"}],"servers":[{"url":"https://app.neostore.cloud","description":"Production Server"},{"url":"https://app-qa.neostore.cloud","description":"Staging Server"}],"security":[{"admin-bearer":["tenant.pass:write"]},{"apiKey":[]}],"components":{"securitySchemes":{"admin-bearer":{"type":"oauth2","flows":{"implicit":{"authorizationUrl":"https://auth.neostore.cloud/authorize?audience=https://app.neostore.cloud/api/","scopes":{}}}},"apiKey":{"type":"apiKey","name":"X-API-KEY","in":"header"}},"schemas":{"ProblemDetails":{"type":"object","properties":{"type":{"type":["null","string"]},"title":{"type":["null","string"]},"status":{"type":["null","integer"],"format":"int32"},"detail":{"type":["null","string"]},"instance":{"type":["null","string"]}},"additionalProperties":{}},"HttpValidationProblemDetails":{"type":"object","allOf":[{"$ref":"#/components/schemas/ProblemDetails"}],"properties":{"errors":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}},"additionalProperties":{}}}},"paths":{"/api/{tenantId}/passes/{passId}/benefitTemplates/{benefitTemplateId}":{"post":{"tags":["Benefit"],"summary":"Grant benefit template to pass","description":"Applies the template's own rules: code, images and the activation, end-of-validity and removal dates come from the template.\nRe-granting a template the pass already carries refreshes that benefit from the template and keeps its movements.\nThe pass is updated on the holder's devices.","parameters":[{"name":"passId","in":"path","description":"Id of the pass receiving the benefit.","required":true,"schema":{"type":"string"}},{"name":"benefitTemplateId","in":"path","description":"Id of an enabled benefit template.","required":true,"schema":{"type":"string"}},{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Granted, or the existing benefit refreshed."},"401":{"description":"The caller is not authenticated.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"403":{"description":"The caller lacks the <c>tenant.pass:write</c> scope.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"404":{"description":"Pass not found, or template unknown or disabled.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"409":{"description":"The pass already carries the maximum number of benefits.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"429":{"description":"Over 1200 requests per minute to this endpoint for the tenant (default limit); retry after <c>Retry-After</c>.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent when the limiter knows it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}}}}}}}
```

## List benefits by pass identifiers

> Every benefit of the pass with all fields, expired ones included. Sorted by priority, highest first; movements by date, oldest first.

```json
{"openapi":"3.1.1","info":{"title":"Neostore internal API","version":"v1"},"tags":[{"name":"Benefit"}],"servers":[{"url":"https://app.neostore.cloud","description":"Production Server"},{"url":"https://app-qa.neostore.cloud","description":"Staging Server"}],"security":[{"admin-bearer":["tenant.pass:read"]},{"apiKey":[]}],"components":{"securitySchemes":{"admin-bearer":{"type":"oauth2","flows":{"implicit":{"authorizationUrl":"https://auth.neostore.cloud/authorize?audience=https://app.neostore.cloud/api/","scopes":{}}}},"apiKey":{"type":"apiKey","name":"X-API-KEY","in":"header"}},"schemas":{"Benefit":{"required":["benefitId"],"type":"object","allOf":[{"description":"A benefit as read back.","$ref":"#/components/schemas/BenefitBase"}],"properties":{"benefitId":{"type":"string","description":"Benefit id, unique within the tenant."},"passType":{"type":["null","string"],"description":"Pass template of the linked standalone wallet pass; null if none, or without `tenant.pass:read`."},"linkedPassId":{"type":["null","string"],"description":"Id of the linked wallet pass; null until it is created."}},"additionalProperties":false,"description":"A stored benefit with its unique id."},"BenefitBase":{"required":["category","status","type"],"type":"object","properties":{"priority":{"type":["null","integer"],"description":"Rank among the pass's benefits: higher first, none last. Orders lists and wallet back fields, and picks whose main image becomes the pass artwork.","format":"int32"},"origin":{"description":"Where the benefit came from: a benefit template (`generator``benefitTemplate`, `processId` the template id) or an integration. Omitted without `tenant.pass:read`.","$ref":"#/components/schemas/BenefitOrigin"},"type":{"description":"How the benefit is consumed: `oneTime` once, `multiUse` counts down uses, `unlimited` is never used up.","$ref":"#/components/schemas/BenefitType"},"category":{"description":"What the benefit gives the holder; `other` when stored without one.","$ref":"#/components/schemas/BenefitCategory"},"tags":{"type":"array","items":{"type":"string"},"description":"Labels for segmenting and reporting, never shown to the holder; omitted without `tenant.pass:read`."},"content":{"description":"Title and description the holder reads.","$ref":"#/components/schemas/BenefitContent"},"appearance":{"description":"Images, at most one per slot. With `tenant.pass:read`, a benefit template image is returned as its image path (the value to send back on write); otherwise as a loadable URL.","$ref":"#/components/schemas/BenefitAppearance"},"links":{"type":["null","object"],"properties":{"legalInformation":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"callToAction":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"termsAndConditions":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"privacyPolicy":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"howToUse":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"website":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"other":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"}},"additionalProperties":false,"description":"Links shown on the benefits page, keyed by purpose."},"data":{"description":"Balance, movement history and provider payload.","$ref":"#/components/schemas/BenefitData"},"activationDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When the benefit becomes redeemable; null means immediately. Before it, status is `scheduled` and redemption is refused. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"deactivationDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When redemption stops; null means never. Afterwards status is `expired` until the deletion date. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"deletionDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When the benefit leaves the pass; null means never, and without `tenant.pass:read`. Until then it counts towards the 20 benefits a pass can carry, even if no longer redeemable. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"behavior":{"description":"Allowance of a `multiUse` benefit and where the benefit is surfaced.","$ref":"#/components/schemas/BenefitBehavior"},"code":{"description":"Code staff scan or type to redeem the benefit, with its rendering; null when none.","$ref":"#/components/schemas/BenefitCode"},"creationDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When the benefit was issued; server-set, ignored on write. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"lastModificationDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When the benefit last changed (update, movement, template refresh); server-set, null without `tenant.pass:read`. Editing a template does not move it unless the edit refreshes issued benefits. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"status":{"description":"Current state, derived from dates and balance. `active` means a movement would be accepted.","$ref":"#/components/schemas/BenefitStatus"}},"additionalProperties":false,"description":"A benefit as read back."},"BenefitOrigin":{"type":"object","properties":{"externalId":{"maxLength":100,"minLength":1,"type":["null","string"],"description":"External id, unique within the tenant. On POST, upserts on it first (before `processId`): updates the existing benefit, or 409 if it belongs to another pass. Kept by later writes that omit it. At most 100 characters."},"generator":{"maxLength":100,"minLength":1,"type":["null","string"],"description":"Name of the upstream system, e.g. `crm`; `benefitTemplate` marks a benefit issued from a template. Informative only. At most 100 characters."},"processId":{"maxLength":100,"minLength":1,"type":["null","string"],"description":"Id of the upstream process or batch job (e.g. `campaign3`). On POST without `externalId`, upserts on (pass, processId); need not be unique across passes. A pass template reads the benefit as `benefits.{processId}`, each run of characters other than letters, digits and `_` becoming one `_`. At most 100 characters."}},"additionalProperties":false,"description":"Where a benefit comes from in an external system, for idempotency and traceability."},"BenefitType":{"enum":["oneTime","unlimited","multiUse"],"type":"string","description":"How a benefit is consumed, which decides what its movements mean and when it is used up."},"BenefitCategory":{"enum":["other","discount","freeProduct","service","access","giftCard"],"type":"string","description":"What a benefit gives the holder, used to group and label it on the benefits page and in reports."},"BenefitContent":{"type":"object","properties":{"title":{"description":"Name the holder sees (\"Untitled benefit\" when absent). At most 200 characters per language.","$ref":"#/components/schemas/LocalizedString"},"description":{"description":"What the benefit gives and how to use it; shown on the benefits page and, when the behavior asks, on the back of the wallet pass. At most 2000 characters per language.","$ref":"#/components/schemas/LocalizedString"}},"additionalProperties":false,"description":"Holder-facing texts of a benefit."},"LocalizedString":{"type":"object","properties":{"value":{"type":["null","string"],"description":"Default-language text."},"values":{"type":["null","object"],"additionalProperties":{"type":"string"},"description":"Translations by language code (e.g. `en`, `fr-FR`); a missing culture falls back to `Value`.","propertyNames":{"pattern":"^[a-z]{2}(-[A-Z]{2})?$"}}},"additionalProperties":false,"description":"Text with a default value and optional per-language translations."},"BenefitAppearance":{"type":"object","properties":{"images":{"type":"array","items":{"description":"A benefit image and its slot.","$ref":"#/components/schemas/BenefitImage"},"description":"Images, at most one per slot."}},"additionalProperties":false,"description":"Images of a benefit, at most one per slot."},"BenefitImage":{"required":["slot","url"],"type":"object","properties":{"slot":{"description":"Where the image is used: `main` for the headline visual (and the pass artwork when the behavior asks), `thumbnail` for compact lists.","$ref":"#/components/schemas/BenefitImageSlot"},"url":{"description":"Localized image URL: a benefit template image path, or the absolute URL given by an integration.","$ref":"#/components/schemas/LocalizedString"}},"additionalProperties":false,"description":"A benefit image and its slot."},"BenefitImageSlot":{"enum":["main","thumbnail"],"type":"string","description":"The image slots a benefit can fill."},"BenefitLink":{"required":["link"],"type":"object","properties":{"title":{"description":"Button label on the benefits page. At most 100 characters per language.","$ref":"#/components/schemas/LocalizedString"},"link":{"type":"string","description":"Absolute `https` URI the button opens; any other scheme is refused with 400.","format":"uri"}},"additionalProperties":false,"description":"A link with a localized title and a target URI."},"BenefitData":{"type":"object","properties":{"value":{"type":["null","number"],"description":"Uses left: the sum of all movements. A `oneTime` or `multiUse` benefit at 0 or below is `used`; for `unlimited`, a tally of what was taken. Computed on read, ignored on write.","format":"double"},"movements":{"type":"array","items":{"description":"A change to a benefit's value (issuance, redemption, adjustment); immutable. Movements applied through `POST benefits/{benefitId}/movements` get their id and date from the server.","$ref":"#/components/schemas/BenefitMovement"},"description":"Every change to the balance, oldest first; append-only. On write, accepted only while the benefit has no movement, to open it with a non-default balance (a `oneTime` benefit takes exactly one movement of 1). POST assigns new movement ids; PUT keeps the caller's."},"content":{"maxLength":500,"minLength":1,"type":["null","string"],"description":"Free text returned as sent, read by a pass template as `benefits.{processId}.data.content` (e.g. a discount rate). Omitted without `tenant.pass:read`. 1 to 500 characters."}},"additionalProperties":false,"description":"Balance, movements and provider payload of a benefit."},"BenefitMovement":{"required":["date","movementId","value"],"type":"object","properties":{"movementId":{"type":"string","description":"Movement id, unique within the benefit; replaced on POST."},"date":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When it happened; stored as sent, so an integration can replay its history. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"value":{"type":"number","description":"Change to the balance: positive credits, negative redeems.","format":"double"},"remarks":{"maxLength":500,"minLength":1,"type":["null","string"],"description":"Note about the movement, e.g. a till receipt number; never shown to the holder, omitted without `tenant.pass:read`. 1 to 500 characters."}},"additionalProperties":false,"description":"A change to a benefit's value (issuance, redemption, adjustment); immutable. Movements applied through `POST benefits/{benefitId}/movements` get their id and date from the server."},"BenefitBehavior":{"type":"object","properties":{"target":{"type":["null","number"],"description":"Uses a `multiUse` benefit started with (the 5 in \"3 of 5 left\", where 3 is `data.value`); null when there is no allowance.","format":"double"},"mode":{"type":"array","items":{"description":"Where a benefit is shown and what each surface does with it.","$ref":"#/components/schemas/BenefitBehaviorMode"},"description":"Where the benefit is surfaced and what each surface does with it; set from the template at issue, empty without `tenant.pass:read`."}},"additionalProperties":false,"description":"Behavior settings of a benefit."},"BenefitBehaviorMode":{"enum":["none","showOnBenefitListPage","showOnPassScanner","redeemOnScan","useMainImageAsWalletImage","addDescriptionAsBackField"],"type":"string","description":"Where a benefit is shown and what each surface does with it."},"BenefitCode":{"type":"object","properties":{"value":{"type":["null","string"],"description":"The code itself, as encoded in the barcode and shown under it."},"format":{"description":"How the code is rendered: a barcode symbology, or `raw` for text only. `ean13` needs 12 or 13 digits.","$ref":"#/components/schemas/BenefitCodeFormat"}},"additionalProperties":false,"description":"Barcode or reference code with its display format."},"BenefitCodeFormat":{"enum":["ean13","code128","qrCode","pdf417","aztec","dataMatrix","code39","raw"],"type":"string","description":"How a benefit's code is rendered to the holder, and so what the till or the Pass Scanner scans."},"BenefitStatus":{"enum":["scheduled","active","used","expired","removed"],"type":"string","description":"What a benefit is doing right now, in the terms an operator answers the holder in."},"ProblemDetails":{"type":"object","properties":{"type":{"type":["null","string"]},"title":{"type":["null","string"]},"status":{"type":["null","integer"],"format":"int32"},"detail":{"type":["null","string"]},"instance":{"type":["null","string"]}},"additionalProperties":{}},"HttpValidationProblemDetails":{"type":"object","allOf":[{"$ref":"#/components/schemas/ProblemDetails"}],"properties":{"errors":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}},"additionalProperties":{}}}},"paths":{"/api/{tenantId}/passes/benefits":{"get":{"tags":["Benefit"],"summary":"List benefits by pass identifiers","description":"Every benefit of the pass with all fields, expired ones included. Sorted by priority, highest first; movements by date, oldest first.","parameters":[{"name":"identifiers","in":"query","description":"Query parameters naming the pass: `id` alone, or one or more `id.{name}` external identifiers that must all match (e.g. `id.crm=C-1042`).","schema":{"type":"object","additionalProperties":{"type":"string"}}},{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Benefits returned; an empty array is normal.","content":{"text/plain":{"schema":{"type":"array","items":{"description":"A stored benefit with its unique id.","$ref":"#/components/schemas/Benefit"}}},"application/json":{"schema":{"type":"array","items":{"description":"A stored benefit with its unique id.","$ref":"#/components/schemas/Benefit"}}},"text/json":{"schema":{"type":"array","items":{"description":"A stored benefit with its unique id.","$ref":"#/components/schemas/Benefit"}}}}},"400":{"description":"Invalid pass identifiers."},"401":{"description":"The caller is not authenticated.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"403":{"description":"The caller lacks the <c>tenant.pass:read</c> scope.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"404":{"description":"Pass not found.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"429":{"description":"Over 1200 requests per minute to this endpoint for the tenant (default limit); retry after <c>Retry-After</c>.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent when the limiter knows it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}}}}}}}
```

## Create benefit by pass identifiers

> Adds a benefit to the pass; the id is generated and returned.\
> When \`origin.externalId\` or \`origin.processId\` is set, the call is idempotent: the benefit they identify is updated instead of duplicated (see those fields for the exact key).\
> A pass carries at most 20 benefits; one past its expiry still counts until its deletion date.\
> \`options.notification\` sends a push message once the benefit is written, and the pass is updated on the holder's devices.\
> Example: \`{"type":"multiUse","content":{"title":{"value":"Free coffee"}},"behavior":{"target":5},"origin":{"processId":"summer-campaign"}}\`

```json
{"openapi":"3.1.1","info":{"title":"Neostore internal API","version":"v1"},"tags":[{"name":"Benefit"}],"servers":[{"url":"https://app.neostore.cloud","description":"Production Server"},{"url":"https://app-qa.neostore.cloud","description":"Staging Server"}],"security":[{"admin-bearer":["tenant.pass:write"]},{"apiKey":[]}],"components":{"securitySchemes":{"admin-bearer":{"type":"oauth2","flows":{"implicit":{"authorizationUrl":"https://auth.neostore.cloud/authorize?audience=https://app.neostore.cloud/api/","scopes":{}}}},"apiKey":{"type":"apiKey","name":"X-API-KEY","in":"header"}},"schemas":{"InsertBenefitRequest":{"required":["type"],"type":"object","allOf":[{"description":"Fields shared by the create and upsert requests. On update, an omitted field keeps its current value.","$ref":"#/components/schemas/BenefitRequestBase"}],"properties":{"type":{"description":"How the benefit is consumed: `oneTime` once, `multiUse` counts down uses, `unlimited` is never used up.","$ref":"#/components/schemas/BenefitType"},"category":{"description":"What the benefit gives the holder. `giftCard` is accepted for `multiUse` only; other combinations are refused with 400.","$ref":"#/components/schemas/BenefitCategory"},"passType":{"type":["null","string"],"description":"Pass template of a standalone wallet pass created for this benefit, alongside the holder's pass (e.g. a gift card); its id is returned as `linkedPassId`. Null: no linked pass."},"options":{"description":"Processing options.","$ref":"#/components/schemas/InsertOptions"}},"additionalProperties":false,"description":"Request to create a benefit; `type` is required."},"BenefitRequestBase":{"type":"object","properties":{"priority":{"type":["null","integer"],"description":"Rank among the pass's benefits: higher first, none last. Orders lists and wallet back fields, and picks whose main image becomes the pass artwork.","format":"int32"},"origin":{"description":"Where the benefit comes from in the caller's system. Set `externalId` or `processId` to make a POST idempotent: it updates the benefit it created instead of adding another.","$ref":"#/components/schemas/BenefitOrigin"},"tags":{"type":["null","array"],"items":{"type":"string"},"description":"Labels for segmenting and reporting, never shown to the holder. At most 20 tags of 50 characters; trimmed, blanks and case-insensitive duplicates dropped. An empty array clears them on update."},"content":{"description":"Title and description the holder reads.","$ref":"#/components/schemas/BenefitContent"},"appearance":{"description":"Images the benefit carries.","$ref":"#/components/schemas/BenefitAppearanceRequest"},"links":{"type":["null","object"],"properties":{"legalInformation":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"callToAction":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"termsAndConditions":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"privacyPolicy":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"howToUse":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"website":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"other":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"}},"additionalProperties":false,"description":"Links shown on the benefits page, keyed by purpose."},"data":{"description":"Opening movements and provider payload. Movements are accepted only while the benefit has none (afterwards use the movements endpoint); without them a `oneTime` benefit starts with one use and a `multiUse` one with `behavior.target`.","$ref":"#/components/schemas/BenefitData"},"activationDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When the benefit becomes redeemable; omitted on create means immediately. Before it, status is `scheduled` and redemption is refused. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"deactivationDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When redemption stops; omitted on create means never. Afterwards status is `expired` until the deletion date. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"deletionDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When the benefit is removed from the pass; omitted on create means never. When omitted on an update that extends Neo.Web.Api.Benefit.Controllers.BenefitPassController.BenefitRequestBase.DeactivationDate, it moves forward by the same amount. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"behavior":{"description":"Allowance of a `multiUse` benefit and where the benefit is surfaced.","$ref":"#/components/schemas/BenefitBehaviorRequest"},"code":{"description":"Code staff scan or type to redeem the benefit.","$ref":"#/components/schemas/BenefitCode"}},"additionalProperties":false,"description":"Fields shared by the create and upsert requests. On update, an omitted field keeps its current value."},"BenefitOrigin":{"type":"object","properties":{"externalId":{"maxLength":100,"minLength":1,"type":["null","string"],"description":"External id, unique within the tenant. On POST, upserts on it first (before `processId`): updates the existing benefit, or 409 if it belongs to another pass. Kept by later writes that omit it. At most 100 characters."},"generator":{"maxLength":100,"minLength":1,"type":["null","string"],"description":"Name of the upstream system, e.g. `crm`; `benefitTemplate` marks a benefit issued from a template. Informative only. At most 100 characters."},"processId":{"maxLength":100,"minLength":1,"type":["null","string"],"description":"Id of the upstream process or batch job (e.g. `campaign3`). On POST without `externalId`, upserts on (pass, processId); need not be unique across passes. A pass template reads the benefit as `benefits.{processId}`, each run of characters other than letters, digits and `_` becoming one `_`. At most 100 characters."}},"additionalProperties":false,"description":"Where a benefit comes from in an external system, for idempotency and traceability."},"BenefitContent":{"type":"object","properties":{"title":{"description":"Name the holder sees (\"Untitled benefit\" when absent). At most 200 characters per language.","$ref":"#/components/schemas/LocalizedString"},"description":{"description":"What the benefit gives and how to use it; shown on the benefits page and, when the behavior asks, on the back of the wallet pass. At most 2000 characters per language.","$ref":"#/components/schemas/LocalizedString"}},"additionalProperties":false,"description":"Holder-facing texts of a benefit."},"LocalizedString":{"type":"object","properties":{"value":{"type":["null","string"],"description":"Default-language text."},"values":{"type":["null","object"],"additionalProperties":{"type":"string"},"description":"Translations by language code (e.g. `en`, `fr-FR`); a missing culture falls back to `Value`.","propertyNames":{"pattern":"^[a-z]{2}(-[A-Z]{2})?$"}}},"additionalProperties":false,"description":"Text with a default value and optional per-language translations."},"BenefitAppearanceRequest":{"type":"object","properties":{"images":{"type":"array","items":{"description":"A benefit image and its slot.","$ref":"#/components/schemas/BenefitImage"},"description":"Images, at most one per slot. Each URL is a web address, or a benefit template image path exactly as a read with `tenant.pass:read` returns it."},"mainImageUrl":{"description":"Deprecated: use Neo.Web.Api.Benefit.Controllers.BenefitPassController.BenefitAppearanceRequest.Images. The main image.","$ref":"#/components/schemas/LocalizedString"},"thumbnailUrl":{"description":"Deprecated: use Neo.Web.Api.Benefit.Controllers.BenefitPassController.BenefitAppearanceRequest.Images. The thumbnail image.","$ref":"#/components/schemas/LocalizedString"}},"additionalProperties":false,"description":"Images of an incoming benefit. `mainImageUrl` and `thumbnailUrl` are deprecated single-slot fields folded into `images`; an explicit `images` entry wins for the same slot."},"BenefitImage":{"required":["slot","url"],"type":"object","properties":{"slot":{"description":"Where the image is used: `main` for the headline visual (and the pass artwork when the behavior asks), `thumbnail` for compact lists.","$ref":"#/components/schemas/BenefitImageSlot"},"url":{"description":"Localized image URL: a benefit template image path, or the absolute URL given by an integration.","$ref":"#/components/schemas/LocalizedString"}},"additionalProperties":false,"description":"A benefit image and its slot."},"BenefitImageSlot":{"enum":["main","thumbnail"],"type":"string","description":"The image slots a benefit can fill."},"BenefitLink":{"required":["link"],"type":"object","properties":{"title":{"description":"Button label on the benefits page. At most 100 characters per language.","$ref":"#/components/schemas/LocalizedString"},"link":{"type":"string","description":"Absolute `https` URI the button opens; any other scheme is refused with 400.","format":"uri"}},"additionalProperties":false,"description":"A link with a localized title and a target URI."},"BenefitData":{"type":"object","properties":{"value":{"type":["null","number"],"description":"Uses left: the sum of all movements. A `oneTime` or `multiUse` benefit at 0 or below is `used`; for `unlimited`, a tally of what was taken. Computed on read, ignored on write.","format":"double"},"movements":{"type":"array","items":{"description":"A change to a benefit's value (issuance, redemption, adjustment); immutable. Movements applied through `POST benefits/{benefitId}/movements` get their id and date from the server.","$ref":"#/components/schemas/BenefitMovement"},"description":"Every change to the balance, oldest first; append-only. On write, accepted only while the benefit has no movement, to open it with a non-default balance (a `oneTime` benefit takes exactly one movement of 1). POST assigns new movement ids; PUT keeps the caller's."},"content":{"maxLength":500,"minLength":1,"type":["null","string"],"description":"Free text returned as sent, read by a pass template as `benefits.{processId}.data.content` (e.g. a discount rate). Omitted without `tenant.pass:read`. 1 to 500 characters."}},"additionalProperties":false,"description":"Balance, movements and provider payload of a benefit."},"BenefitMovement":{"required":["date","movementId","value"],"type":"object","properties":{"movementId":{"type":"string","description":"Movement id, unique within the benefit; replaced on POST."},"date":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When it happened; stored as sent, so an integration can replay its history. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"value":{"type":"number","description":"Change to the balance: positive credits, negative redeems.","format":"double"},"remarks":{"maxLength":500,"minLength":1,"type":["null","string"],"description":"Note about the movement, e.g. a till receipt number; never shown to the holder, omitted without `tenant.pass:read`. 1 to 500 characters."}},"additionalProperties":false,"description":"A change to a benefit's value (issuance, redemption, adjustment); immutable. Movements applied through `POST benefits/{benefitId}/movements` get their id and date from the server."},"BenefitBehaviorRequest":{"type":"object","properties":{"target":{"type":["null","number"],"description":"Uses a `multiUse` benefit starts with: seeds the balance of a benefit created without movements and caps what a later credit can give back.","format":"double"},"mode":{"type":["null","array"],"items":{"description":"Where a benefit is shown and what each surface does with it.","$ref":"#/components/schemas/BenefitBehaviorMode"},"description":"Where the benefit is surfaced and what each surface does with it; omit for the default."},"autoRedeem":{"type":["null","boolean"],"description":"Deprecated: include Neo.Runtime.Benefit.Data.BenefitBehaviorMode.RedeemOnScan in Neo.Web.Api.Benefit.Controllers.BenefitPassController.BenefitBehaviorRequest.Mode instead."}},"additionalProperties":false,"description":"Behavior of an incoming benefit. Replaces the behavior as a whole: without `mode`, the default surfaces apply (listed everywhere, image on the pass, description on the back), not the previous ones; an empty array means none."},"BenefitBehaviorMode":{"enum":["none","showOnBenefitListPage","showOnPassScanner","redeemOnScan","useMainImageAsWalletImage","addDescriptionAsBackField"],"type":"string","description":"Where a benefit is shown and what each surface does with it."},"BenefitCode":{"type":"object","properties":{"value":{"type":["null","string"],"description":"The code itself, as encoded in the barcode and shown under it."},"format":{"description":"How the code is rendered: a barcode symbology, or `raw` for text only. `ean13` needs 12 or 13 digits.","$ref":"#/components/schemas/BenefitCodeFormat"}},"additionalProperties":false,"description":"Barcode or reference code with its display format."},"BenefitCodeFormat":{"enum":["ean13","code128","qrCode","pdf417","aztec","dataMatrix","code39","raw"],"type":"string","description":"How a benefit's code is rendered to the holder, and so what the till or the Pass Scanner scans."},"BenefitType":{"enum":["oneTime","unlimited","multiUse"],"type":"string","description":"How a benefit is consumed, which decides what its movements mean and when it is used up."},"BenefitCategory":{"enum":["other","discount","freeProduct","service","access","giftCard"],"type":"string","description":"What a benefit gives the holder, used to group and label it on the benefits page and in reports."},"InsertOptions":{"type":"object","properties":{"correlationId":{"maxLength":100,"minLength":10,"type":["null","string"],"description":"Caller's own call id (10-100 characters), e.g. a campaign run id, to trace a batch. Not stored."},"notification":{"description":"Push message sent to the pass once the benefit is written, same payload as the pass notification endpoints. Omit for no notification.","$ref":"#/components/schemas/NotifyRequest"}},"additionalProperties":false,"description":"Options of benefit creation."},"NotifyRequest":{"type":"object","properties":{"content":{"type":["null","string"],"description":"Message for devices whose language has no entry in `localizedContent`. Optional when `localizedContent` has text; defaults to the tenant's default language entry, else the first language in ordinal order."},"localizedContent":{"type":["null","object"],"additionalProperties":{"type":"string"},"description":"Message per device language. Keys are a lowercase ISO 639-1 code, optionally with an uppercase\nregion (`en`, `fr-FR`). Entries with blank text are not sent.","propertyNames":{"pattern":"^[a-z]{2}(-[A-Z]{2})?$"}},"options":{"description":"Delivery options. Omitted, the notification is queued without refreshing pass data.","$ref":"#/components/schemas/NotifyRequestOptions"}},"additionalProperties":false,"description":"The message to show the holder, in one or several languages."},"NotifyRequestOptions":{"type":"object","properties":{"updateData":{"type":"boolean","description":"Also refreshes the Google Wallet pass content with the message. Apple always refreshes the whole pass.","default":false},"correlationId":{"maxLength":100,"minLength":10,"type":["null","string"],"description":"Caller-chosen id for tracing, such as a campaign or batch. 10 to 100 characters after trimming, otherwise 400."},"bypassQueue":{"type":"boolean","description":"Sends within the request (200) instead of queuing (202), for urgent low-volume messages.","default":false}},"additionalProperties":false,"description":"How the notification is delivered."},"UpsertBenefitResponse":{"type":"object","properties":{"benefitId":{"type":"string","description":"Id of the benefit written (the existing one when a POST upserted on its origin)."}},"additionalProperties":false,"description":"Written benefit."},"ProblemDetails":{"type":"object","properties":{"type":{"type":["null","string"]},"title":{"type":["null","string"]},"status":{"type":["null","integer"],"format":"int32"},"detail":{"type":["null","string"]},"instance":{"type":["null","string"]}},"additionalProperties":{}},"HttpValidationProblemDetails":{"type":"object","allOf":[{"$ref":"#/components/schemas/ProblemDetails"}],"properties":{"errors":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}},"additionalProperties":{}}}},"paths":{"/api/{tenantId}/passes/benefits":{"post":{"tags":["Benefit"],"summary":"Create benefit by pass identifiers","description":"Adds a benefit to the pass; the id is generated and returned.\nWhen `origin.externalId` or `origin.processId` is set, the call is idempotent: the benefit they identify is updated instead of duplicated (see those fields for the exact key).\nA pass carries at most 20 benefits; one past its expiry still counts until its deletion date.\n`options.notification` sends a push message once the benefit is written, and the pass is updated on the holder's devices.\nExample: `{\"type\":\"multiUse\",\"content\":{\"title\":{\"value\":\"Free coffee\"}},\"behavior\":{\"target\":5},\"origin\":{\"processId\":\"summer-campaign\"}}`","parameters":[{"name":"identifiers","in":"query","description":"Query parameters naming the pass: `id` alone, or one or more `id.{name}` external identifiers that must all match (e.g. `id.crm=C-1042`).","schema":{"type":"object","additionalProperties":{"type":"string"}}},{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Benefit to create, or to update when its origin matches an existing benefit.","content":{"application/json":{"schema":{"description":"Request to create a benefit; `type` is required.","$ref":"#/components/schemas/InsertBenefitRequest"}},"text/json":{"schema":{"description":"Request to create a benefit; `type` is required.","$ref":"#/components/schemas/InsertBenefitRequest"}},"application/*+json":{"schema":{"description":"Request to create a benefit; `type` is required.","$ref":"#/components/schemas/InsertBenefitRequest"}}}},"responses":{"200":{"description":"Benefit created or updated; returns the benefit id.","content":{"text/plain":{"schema":{"description":"Written benefit.","$ref":"#/components/schemas/UpsertBenefitResponse"}},"application/json":{"schema":{"description":"Written benefit.","$ref":"#/components/schemas/UpsertBenefitResponse"}},"text/json":{"schema":{"description":"Written benefit.","$ref":"#/components/schemas/UpsertBenefitResponse"}}}},"400":{"description":"Invalid payload or pass identifiers, category not allowed for the benefit type, or `data.movements` refused.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"401":{"description":"The caller is not authenticated.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"403":{"description":"The caller lacks the <c>tenant.pass:write</c> scope.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"404":{"description":"Pass not found.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"409":{"description":"The externalId is bound to another pass, the pass is at its maximum number of benefits, or another request is writing the same benefit; retry shortly.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"429":{"description":"Over 1200 requests per minute to this endpoint for the tenant (default limit); retry after <c>Retry-After</c>.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent when the limiter knows it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}}}}}}}
```

## Get benefit

> Looked up across all passes of the tenant, so no pass id is needed.

```json
{"openapi":"3.1.1","info":{"title":"Neostore internal API","version":"v1"},"tags":[{"name":"Benefit"}],"servers":[{"url":"https://app.neostore.cloud","description":"Production Server"},{"url":"https://app-qa.neostore.cloud","description":"Staging Server"}],"security":[{"admin-bearer":["tenant.pass:read"]},{"apiKey":[]}],"components":{"securitySchemes":{"admin-bearer":{"type":"oauth2","flows":{"implicit":{"authorizationUrl":"https://auth.neostore.cloud/authorize?audience=https://app.neostore.cloud/api/","scopes":{}}}},"apiKey":{"type":"apiKey","name":"X-API-KEY","in":"header"}},"schemas":{"Benefit":{"required":["benefitId"],"type":"object","allOf":[{"description":"A benefit as read back.","$ref":"#/components/schemas/BenefitBase"}],"properties":{"benefitId":{"type":"string","description":"Benefit id, unique within the tenant."},"passType":{"type":["null","string"],"description":"Pass template of the linked standalone wallet pass; null if none, or without `tenant.pass:read`."},"linkedPassId":{"type":["null","string"],"description":"Id of the linked wallet pass; null until it is created."}},"additionalProperties":false,"description":"A stored benefit with its unique id."},"BenefitBase":{"required":["category","status","type"],"type":"object","properties":{"priority":{"type":["null","integer"],"description":"Rank among the pass's benefits: higher first, none last. Orders lists and wallet back fields, and picks whose main image becomes the pass artwork.","format":"int32"},"origin":{"description":"Where the benefit came from: a benefit template (`generator``benefitTemplate`, `processId` the template id) or an integration. Omitted without `tenant.pass:read`.","$ref":"#/components/schemas/BenefitOrigin"},"type":{"description":"How the benefit is consumed: `oneTime` once, `multiUse` counts down uses, `unlimited` is never used up.","$ref":"#/components/schemas/BenefitType"},"category":{"description":"What the benefit gives the holder; `other` when stored without one.","$ref":"#/components/schemas/BenefitCategory"},"tags":{"type":"array","items":{"type":"string"},"description":"Labels for segmenting and reporting, never shown to the holder; omitted without `tenant.pass:read`."},"content":{"description":"Title and description the holder reads.","$ref":"#/components/schemas/BenefitContent"},"appearance":{"description":"Images, at most one per slot. With `tenant.pass:read`, a benefit template image is returned as its image path (the value to send back on write); otherwise as a loadable URL.","$ref":"#/components/schemas/BenefitAppearance"},"links":{"type":["null","object"],"properties":{"legalInformation":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"callToAction":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"termsAndConditions":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"privacyPolicy":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"howToUse":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"website":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"other":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"}},"additionalProperties":false,"description":"Links shown on the benefits page, keyed by purpose."},"data":{"description":"Balance, movement history and provider payload.","$ref":"#/components/schemas/BenefitData"},"activationDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When the benefit becomes redeemable; null means immediately. Before it, status is `scheduled` and redemption is refused. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"deactivationDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When redemption stops; null means never. Afterwards status is `expired` until the deletion date. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"deletionDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When the benefit leaves the pass; null means never, and without `tenant.pass:read`. Until then it counts towards the 20 benefits a pass can carry, even if no longer redeemable. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"behavior":{"description":"Allowance of a `multiUse` benefit and where the benefit is surfaced.","$ref":"#/components/schemas/BenefitBehavior"},"code":{"description":"Code staff scan or type to redeem the benefit, with its rendering; null when none.","$ref":"#/components/schemas/BenefitCode"},"creationDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When the benefit was issued; server-set, ignored on write. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"lastModificationDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When the benefit last changed (update, movement, template refresh); server-set, null without `tenant.pass:read`. Editing a template does not move it unless the edit refreshes issued benefits. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"status":{"description":"Current state, derived from dates and balance. `active` means a movement would be accepted.","$ref":"#/components/schemas/BenefitStatus"}},"additionalProperties":false,"description":"A benefit as read back."},"BenefitOrigin":{"type":"object","properties":{"externalId":{"maxLength":100,"minLength":1,"type":["null","string"],"description":"External id, unique within the tenant. On POST, upserts on it first (before `processId`): updates the existing benefit, or 409 if it belongs to another pass. Kept by later writes that omit it. At most 100 characters."},"generator":{"maxLength":100,"minLength":1,"type":["null","string"],"description":"Name of the upstream system, e.g. `crm`; `benefitTemplate` marks a benefit issued from a template. Informative only. At most 100 characters."},"processId":{"maxLength":100,"minLength":1,"type":["null","string"],"description":"Id of the upstream process or batch job (e.g. `campaign3`). On POST without `externalId`, upserts on (pass, processId); need not be unique across passes. A pass template reads the benefit as `benefits.{processId}`, each run of characters other than letters, digits and `_` becoming one `_`. At most 100 characters."}},"additionalProperties":false,"description":"Where a benefit comes from in an external system, for idempotency and traceability."},"BenefitType":{"enum":["oneTime","unlimited","multiUse"],"type":"string","description":"How a benefit is consumed, which decides what its movements mean and when it is used up."},"BenefitCategory":{"enum":["other","discount","freeProduct","service","access","giftCard"],"type":"string","description":"What a benefit gives the holder, used to group and label it on the benefits page and in reports."},"BenefitContent":{"type":"object","properties":{"title":{"description":"Name the holder sees (\"Untitled benefit\" when absent). At most 200 characters per language.","$ref":"#/components/schemas/LocalizedString"},"description":{"description":"What the benefit gives and how to use it; shown on the benefits page and, when the behavior asks, on the back of the wallet pass. At most 2000 characters per language.","$ref":"#/components/schemas/LocalizedString"}},"additionalProperties":false,"description":"Holder-facing texts of a benefit."},"LocalizedString":{"type":"object","properties":{"value":{"type":["null","string"],"description":"Default-language text."},"values":{"type":["null","object"],"additionalProperties":{"type":"string"},"description":"Translations by language code (e.g. `en`, `fr-FR`); a missing culture falls back to `Value`.","propertyNames":{"pattern":"^[a-z]{2}(-[A-Z]{2})?$"}}},"additionalProperties":false,"description":"Text with a default value and optional per-language translations."},"BenefitAppearance":{"type":"object","properties":{"images":{"type":"array","items":{"description":"A benefit image and its slot.","$ref":"#/components/schemas/BenefitImage"},"description":"Images, at most one per slot."}},"additionalProperties":false,"description":"Images of a benefit, at most one per slot."},"BenefitImage":{"required":["slot","url"],"type":"object","properties":{"slot":{"description":"Where the image is used: `main` for the headline visual (and the pass artwork when the behavior asks), `thumbnail` for compact lists.","$ref":"#/components/schemas/BenefitImageSlot"},"url":{"description":"Localized image URL: a benefit template image path, or the absolute URL given by an integration.","$ref":"#/components/schemas/LocalizedString"}},"additionalProperties":false,"description":"A benefit image and its slot."},"BenefitImageSlot":{"enum":["main","thumbnail"],"type":"string","description":"The image slots a benefit can fill."},"BenefitLink":{"required":["link"],"type":"object","properties":{"title":{"description":"Button label on the benefits page. At most 100 characters per language.","$ref":"#/components/schemas/LocalizedString"},"link":{"type":"string","description":"Absolute `https` URI the button opens; any other scheme is refused with 400.","format":"uri"}},"additionalProperties":false,"description":"A link with a localized title and a target URI."},"BenefitData":{"type":"object","properties":{"value":{"type":["null","number"],"description":"Uses left: the sum of all movements. A `oneTime` or `multiUse` benefit at 0 or below is `used`; for `unlimited`, a tally of what was taken. Computed on read, ignored on write.","format":"double"},"movements":{"type":"array","items":{"description":"A change to a benefit's value (issuance, redemption, adjustment); immutable. Movements applied through `POST benefits/{benefitId}/movements` get their id and date from the server.","$ref":"#/components/schemas/BenefitMovement"},"description":"Every change to the balance, oldest first; append-only. On write, accepted only while the benefit has no movement, to open it with a non-default balance (a `oneTime` benefit takes exactly one movement of 1). POST assigns new movement ids; PUT keeps the caller's."},"content":{"maxLength":500,"minLength":1,"type":["null","string"],"description":"Free text returned as sent, read by a pass template as `benefits.{processId}.data.content` (e.g. a discount rate). Omitted without `tenant.pass:read`. 1 to 500 characters."}},"additionalProperties":false,"description":"Balance, movements and provider payload of a benefit."},"BenefitMovement":{"required":["date","movementId","value"],"type":"object","properties":{"movementId":{"type":"string","description":"Movement id, unique within the benefit; replaced on POST."},"date":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When it happened; stored as sent, so an integration can replay its history. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"value":{"type":"number","description":"Change to the balance: positive credits, negative redeems.","format":"double"},"remarks":{"maxLength":500,"minLength":1,"type":["null","string"],"description":"Note about the movement, e.g. a till receipt number; never shown to the holder, omitted without `tenant.pass:read`. 1 to 500 characters."}},"additionalProperties":false,"description":"A change to a benefit's value (issuance, redemption, adjustment); immutable. Movements applied through `POST benefits/{benefitId}/movements` get their id and date from the server."},"BenefitBehavior":{"type":"object","properties":{"target":{"type":["null","number"],"description":"Uses a `multiUse` benefit started with (the 5 in \"3 of 5 left\", where 3 is `data.value`); null when there is no allowance.","format":"double"},"mode":{"type":"array","items":{"description":"Where a benefit is shown and what each surface does with it.","$ref":"#/components/schemas/BenefitBehaviorMode"},"description":"Where the benefit is surfaced and what each surface does with it; set from the template at issue, empty without `tenant.pass:read`."}},"additionalProperties":false,"description":"Behavior settings of a benefit."},"BenefitBehaviorMode":{"enum":["none","showOnBenefitListPage","showOnPassScanner","redeemOnScan","useMainImageAsWalletImage","addDescriptionAsBackField"],"type":"string","description":"Where a benefit is shown and what each surface does with it."},"BenefitCode":{"type":"object","properties":{"value":{"type":["null","string"],"description":"The code itself, as encoded in the barcode and shown under it."},"format":{"description":"How the code is rendered: a barcode symbology, or `raw` for text only. `ean13` needs 12 or 13 digits.","$ref":"#/components/schemas/BenefitCodeFormat"}},"additionalProperties":false,"description":"Barcode or reference code with its display format."},"BenefitCodeFormat":{"enum":["ean13","code128","qrCode","pdf417","aztec","dataMatrix","code39","raw"],"type":"string","description":"How a benefit's code is rendered to the holder, and so what the till or the Pass Scanner scans."},"BenefitStatus":{"enum":["scheduled","active","used","expired","removed"],"type":"string","description":"What a benefit is doing right now, in the terms an operator answers the holder in."},"ProblemDetails":{"type":"object","properties":{"type":{"type":["null","string"]},"title":{"type":["null","string"]},"status":{"type":["null","integer"],"format":"int32"},"detail":{"type":["null","string"]},"instance":{"type":["null","string"]}},"additionalProperties":{}},"HttpValidationProblemDetails":{"type":"object","allOf":[{"$ref":"#/components/schemas/ProblemDetails"}],"properties":{"errors":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}},"additionalProperties":{}}}},"paths":{"/api/{tenantId}/passes/benefits/{benefitId}":{"get":{"tags":["Benefit"],"summary":"Get benefit","description":"Looked up across all passes of the tenant, so no pass id is needed.","parameters":[{"name":"benefitId","in":"path","description":"Id of the benefit.","required":true,"schema":{"type":"string"}},{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Benefit returned.","content":{"text/plain":{"schema":{"description":"A stored benefit with its unique id.","$ref":"#/components/schemas/Benefit"}},"application/json":{"schema":{"description":"A stored benefit with its unique id.","$ref":"#/components/schemas/Benefit"}},"text/json":{"schema":{"description":"A stored benefit with its unique id.","$ref":"#/components/schemas/Benefit"}}}},"401":{"description":"The caller is not authenticated.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"403":{"description":"The caller lacks the <c>tenant.pass:read</c> scope.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"404":{"description":"Benefit not found.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"429":{"description":"Over 1200 requests per minute to this endpoint for the tenant (default limit); retry after <c>Retry-After</c>.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent when the limiter knows it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}}}}}}}
```

## Update benefit by pass identifiers

> Updates an existing benefit of the pass. Omitted fields keep their value; a field sent replaces that field as a whole (images, links, tags, behavior). \`passType\`: null keeps the current one, an empty string unlinks it.\
> \`data.movements\` is accepted only while the benefit has no movement; afterwards use \`POST /passes/benefits/{benefitId}/movements\`.\
> \`options.notification\` sends a push message once the benefit is written, and the pass is updated on the holder's devices.

```json
{"openapi":"3.1.1","info":{"title":"Neostore internal API","version":"v1"},"tags":[{"name":"Benefit"}],"servers":[{"url":"https://app.neostore.cloud","description":"Production Server"},{"url":"https://app-qa.neostore.cloud","description":"Staging Server"}],"security":[{"admin-bearer":["tenant.pass:write"]},{"apiKey":[]}],"components":{"securitySchemes":{"admin-bearer":{"type":"oauth2","flows":{"implicit":{"authorizationUrl":"https://auth.neostore.cloud/authorize?audience=https://app.neostore.cloud/api/","scopes":{}}}},"apiKey":{"type":"apiKey","name":"X-API-KEY","in":"header"}},"schemas":{"PatchBenefitRequest":{"type":"object","allOf":[{"$ref":"#/components/schemas/BenefitRequestBase"}],"properties":{"type":{"description":"How the benefit is consumed.","$ref":"#/components/schemas/BenefitType"},"category":{"description":"What the benefit gives the holder. `giftCard` is accepted for `multiUse` only.","$ref":"#/components/schemas/BenefitCategory"},"passType":{"type":["null","string"],"description":"Pass template of the standalone wallet pass: null keeps the current one, an empty string unlinks it (a pass already created stays in the holder's wallet)."},"options":{"description":"Processing options.","$ref":"#/components/schemas/PatchOptions"}},"additionalProperties":false,"description":"Request to update a benefit (PUT); omitted fields keep their current value."},"BenefitRequestBase":{"type":"object","properties":{"priority":{"type":["null","integer"],"description":"Rank among the pass's benefits: higher first, none last. Orders lists and wallet back fields, and picks whose main image becomes the pass artwork.","format":"int32"},"origin":{"description":"Where the benefit comes from in the caller's system. Set `externalId` or `processId` to make a POST idempotent: it updates the benefit it created instead of adding another.","$ref":"#/components/schemas/BenefitOrigin"},"tags":{"type":["null","array"],"items":{"type":"string"},"description":"Labels for segmenting and reporting, never shown to the holder. At most 20 tags of 50 characters; trimmed, blanks and case-insensitive duplicates dropped. An empty array clears them on update."},"content":{"description":"Title and description the holder reads.","$ref":"#/components/schemas/BenefitContent"},"appearance":{"description":"Images the benefit carries.","$ref":"#/components/schemas/BenefitAppearanceRequest"},"links":{"type":["null","object"],"properties":{"legalInformation":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"callToAction":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"termsAndConditions":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"privacyPolicy":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"howToUse":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"website":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"},"other":{"description":"A link with a localized title and a target URI.","$ref":"#/components/schemas/BenefitLink"}},"additionalProperties":false,"description":"Links shown on the benefits page, keyed by purpose."},"data":{"description":"Opening movements and provider payload. Movements are accepted only while the benefit has none (afterwards use the movements endpoint); without them a `oneTime` benefit starts with one use and a `multiUse` one with `behavior.target`.","$ref":"#/components/schemas/BenefitData"},"activationDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When the benefit becomes redeemable; omitted on create means immediately. Before it, status is `scheduled` and redemption is refused. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"deactivationDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When redemption stops; omitted on create means never. Afterwards status is `expired` until the deletion date. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"deletionDate":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When the benefit is removed from the pass; omitted on create means never. When omitted on an update that extends Neo.Web.Api.Benefit.Controllers.BenefitPassController.BenefitRequestBase.DeactivationDate, it moves forward by the same amount. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"behavior":{"description":"Allowance of a `multiUse` benefit and where the benefit is surfaced.","$ref":"#/components/schemas/BenefitBehaviorRequest"},"code":{"description":"Code staff scan or type to redeem the benefit.","$ref":"#/components/schemas/BenefitCode"}},"additionalProperties":false,"description":"Fields shared by the create and upsert requests. On update, an omitted field keeps its current value."},"BenefitOrigin":{"type":"object","properties":{"externalId":{"maxLength":100,"minLength":1,"type":["null","string"],"description":"External id, unique within the tenant. On POST, upserts on it first (before `processId`): updates the existing benefit, or 409 if it belongs to another pass. Kept by later writes that omit it. At most 100 characters."},"generator":{"maxLength":100,"minLength":1,"type":["null","string"],"description":"Name of the upstream system, e.g. `crm`; `benefitTemplate` marks a benefit issued from a template. Informative only. At most 100 characters."},"processId":{"maxLength":100,"minLength":1,"type":["null","string"],"description":"Id of the upstream process or batch job (e.g. `campaign3`). On POST without `externalId`, upserts on (pass, processId); need not be unique across passes. A pass template reads the benefit as `benefits.{processId}`, each run of characters other than letters, digits and `_` becoming one `_`. At most 100 characters."}},"additionalProperties":false,"description":"Where a benefit comes from in an external system, for idempotency and traceability."},"BenefitContent":{"type":"object","properties":{"title":{"description":"Name the holder sees (\"Untitled benefit\" when absent). At most 200 characters per language.","$ref":"#/components/schemas/LocalizedString"},"description":{"description":"What the benefit gives and how to use it; shown on the benefits page and, when the behavior asks, on the back of the wallet pass. At most 2000 characters per language.","$ref":"#/components/schemas/LocalizedString"}},"additionalProperties":false,"description":"Holder-facing texts of a benefit."},"LocalizedString":{"type":"object","properties":{"value":{"type":["null","string"],"description":"Default-language text."},"values":{"type":["null","object"],"additionalProperties":{"type":"string"},"description":"Translations by language code (e.g. `en`, `fr-FR`); a missing culture falls back to `Value`.","propertyNames":{"pattern":"^[a-z]{2}(-[A-Z]{2})?$"}}},"additionalProperties":false,"description":"Text with a default value and optional per-language translations."},"BenefitAppearanceRequest":{"type":"object","properties":{"images":{"type":"array","items":{"description":"A benefit image and its slot.","$ref":"#/components/schemas/BenefitImage"},"description":"Images, at most one per slot. Each URL is a web address, or a benefit template image path exactly as a read with `tenant.pass:read` returns it."},"mainImageUrl":{"description":"Deprecated: use Neo.Web.Api.Benefit.Controllers.BenefitPassController.BenefitAppearanceRequest.Images. The main image.","$ref":"#/components/schemas/LocalizedString"},"thumbnailUrl":{"description":"Deprecated: use Neo.Web.Api.Benefit.Controllers.BenefitPassController.BenefitAppearanceRequest.Images. The thumbnail image.","$ref":"#/components/schemas/LocalizedString"}},"additionalProperties":false,"description":"Images of an incoming benefit. `mainImageUrl` and `thumbnailUrl` are deprecated single-slot fields folded into `images`; an explicit `images` entry wins for the same slot."},"BenefitImage":{"required":["slot","url"],"type":"object","properties":{"slot":{"description":"Where the image is used: `main` for the headline visual (and the pass artwork when the behavior asks), `thumbnail` for compact lists.","$ref":"#/components/schemas/BenefitImageSlot"},"url":{"description":"Localized image URL: a benefit template image path, or the absolute URL given by an integration.","$ref":"#/components/schemas/LocalizedString"}},"additionalProperties":false,"description":"A benefit image and its slot."},"BenefitImageSlot":{"enum":["main","thumbnail"],"type":"string","description":"The image slots a benefit can fill."},"BenefitLink":{"required":["link"],"type":"object","properties":{"title":{"description":"Button label on the benefits page. At most 100 characters per language.","$ref":"#/components/schemas/LocalizedString"},"link":{"type":"string","description":"Absolute `https` URI the button opens; any other scheme is refused with 400.","format":"uri"}},"additionalProperties":false,"description":"A link with a localized title and a target URI."},"BenefitData":{"type":"object","properties":{"value":{"type":["null","number"],"description":"Uses left: the sum of all movements. A `oneTime` or `multiUse` benefit at 0 or below is `used`; for `unlimited`, a tally of what was taken. Computed on read, ignored on write.","format":"double"},"movements":{"type":"array","items":{"description":"A change to a benefit's value (issuance, redemption, adjustment); immutable. Movements applied through `POST benefits/{benefitId}/movements` get their id and date from the server.","$ref":"#/components/schemas/BenefitMovement"},"description":"Every change to the balance, oldest first; append-only. On write, accepted only while the benefit has no movement, to open it with a non-default balance (a `oneTime` benefit takes exactly one movement of 1). POST assigns new movement ids; PUT keeps the caller's."},"content":{"maxLength":500,"minLength":1,"type":["null","string"],"description":"Free text returned as sent, read by a pass template as `benefits.{processId}.data.content` (e.g. a discount rate). Omitted without `tenant.pass:read`. 1 to 500 characters."}},"additionalProperties":false,"description":"Balance, movements and provider payload of a benefit."},"BenefitMovement":{"required":["date","movementId","value"],"type":"object","properties":{"movementId":{"type":"string","description":"Movement id, unique within the benefit; replaced on POST."},"date":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","format":"int64"}],"description":"When it happened; stored as sent, so an integration can replay its history. Accepts ISO 8601 date-time string or Unix epoch seconds in request inputs. Responses are serialized as ISO 8601 date-time strings."},"value":{"type":"number","description":"Change to the balance: positive credits, negative redeems.","format":"double"},"remarks":{"maxLength":500,"minLength":1,"type":["null","string"],"description":"Note about the movement, e.g. a till receipt number; never shown to the holder, omitted without `tenant.pass:read`. 1 to 500 characters."}},"additionalProperties":false,"description":"A change to a benefit's value (issuance, redemption, adjustment); immutable. Movements applied through `POST benefits/{benefitId}/movements` get their id and date from the server."},"BenefitBehaviorRequest":{"type":"object","properties":{"target":{"type":["null","number"],"description":"Uses a `multiUse` benefit starts with: seeds the balance of a benefit created without movements and caps what a later credit can give back.","format":"double"},"mode":{"type":["null","array"],"items":{"description":"Where a benefit is shown and what each surface does with it.","$ref":"#/components/schemas/BenefitBehaviorMode"},"description":"Where the benefit is surfaced and what each surface does with it; omit for the default."},"autoRedeem":{"type":["null","boolean"],"description":"Deprecated: include Neo.Runtime.Benefit.Data.BenefitBehaviorMode.RedeemOnScan in Neo.Web.Api.Benefit.Controllers.BenefitPassController.BenefitBehaviorRequest.Mode instead."}},"additionalProperties":false,"description":"Behavior of an incoming benefit. Replaces the behavior as a whole: without `mode`, the default surfaces apply (listed everywhere, image on the pass, description on the back), not the previous ones; an empty array means none."},"BenefitBehaviorMode":{"enum":["none","showOnBenefitListPage","showOnPassScanner","redeemOnScan","useMainImageAsWalletImage","addDescriptionAsBackField"],"type":"string","description":"Where a benefit is shown and what each surface does with it."},"BenefitCode":{"type":"object","properties":{"value":{"type":["null","string"],"description":"The code itself, as encoded in the barcode and shown under it."},"format":{"description":"How the code is rendered: a barcode symbology, or `raw` for text only. `ean13` needs 12 or 13 digits.","$ref":"#/components/schemas/BenefitCodeFormat"}},"additionalProperties":false,"description":"Barcode or reference code with its display format."},"BenefitCodeFormat":{"enum":["ean13","code128","qrCode","pdf417","aztec","dataMatrix","code39","raw"],"type":"string","description":"How a benefit's code is rendered to the holder, and so what the till or the Pass Scanner scans."},"BenefitType":{"enum":["oneTime","unlimited","multiUse"],"type":"string","description":"How a benefit is consumed, which decides what its movements mean and when it is used up."},"BenefitCategory":{"enum":["other","discount","freeProduct","service","access","giftCard"],"type":"string","description":"What a benefit gives the holder, used to group and label it on the benefits page and in reports."},"PatchOptions":{"type":"object","properties":{"correlationId":{"maxLength":100,"minLength":10,"type":["null","string"],"description":"Caller's own call id (10-100 characters), e.g. a campaign run id, to trace a batch. Not stored."},"notification":{"description":"Push message sent to the pass once the benefit is written, same payload as the pass notification endpoints. Omit for no notification.","$ref":"#/components/schemas/NotifyRequest"}},"additionalProperties":false,"description":"Options of benefit update."},"NotifyRequest":{"type":"object","properties":{"content":{"type":["null","string"],"description":"Message for devices whose language has no entry in `localizedContent`. Optional when `localizedContent` has text; defaults to the tenant's default language entry, else the first language in ordinal order."},"localizedContent":{"type":["null","object"],"additionalProperties":{"type":"string"},"description":"Message per device language. Keys are a lowercase ISO 639-1 code, optionally with an uppercase\nregion (`en`, `fr-FR`). Entries with blank text are not sent.","propertyNames":{"pattern":"^[a-z]{2}(-[A-Z]{2})?$"}},"options":{"description":"Delivery options. Omitted, the notification is queued without refreshing pass data.","$ref":"#/components/schemas/NotifyRequestOptions"}},"additionalProperties":false,"description":"The message to show the holder, in one or several languages."},"NotifyRequestOptions":{"type":"object","properties":{"updateData":{"type":"boolean","description":"Also refreshes the Google Wallet pass content with the message. Apple always refreshes the whole pass.","default":false},"correlationId":{"maxLength":100,"minLength":10,"type":["null","string"],"description":"Caller-chosen id for tracing, such as a campaign or batch. 10 to 100 characters after trimming, otherwise 400."},"bypassQueue":{"type":"boolean","description":"Sends within the request (200) instead of queuing (202), for urgent low-volume messages.","default":false}},"additionalProperties":false,"description":"How the notification is delivered."},"ProblemDetails":{"type":"object","properties":{"type":{"type":["null","string"]},"title":{"type":["null","string"]},"status":{"type":["null","integer"],"format":"int32"},"detail":{"type":["null","string"]},"instance":{"type":["null","string"]}},"additionalProperties":{}},"HttpValidationProblemDetails":{"type":"object","allOf":[{"$ref":"#/components/schemas/ProblemDetails"}],"properties":{"errors":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}},"additionalProperties":{}}}},"paths":{"/api/{tenantId}/passes/benefits/{benefitId}":{"put":{"tags":["Benefit"],"summary":"Update benefit by pass identifiers","description":"Updates an existing benefit of the pass. Omitted fields keep their value; a field sent replaces that field as a whole (images, links, tags, behavior). `passType`: null keeps the current one, an empty string unlinks it.\n`data.movements` is accepted only while the benefit has no movement; afterwards use `POST /passes/benefits/{benefitId}/movements`.\n`options.notification` sends a push message once the benefit is written, and the pass is updated on the holder's devices.","parameters":[{"name":"identifiers","in":"query","description":"Query parameters naming the pass: `id` alone, or one or more `id.{name}` external identifiers that must all match (e.g. `id.crm=C-1042`).","schema":{"type":"object","additionalProperties":{"type":"string"}}},{"name":"benefitId","in":"path","description":"Id of the benefit to update.","required":true,"schema":{"type":"string"}},{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to change.","content":{"application/json":{"schema":{"description":"Request to update a benefit (PUT); omitted fields keep their current value.","$ref":"#/components/schemas/PatchBenefitRequest"}},"text/json":{"schema":{"description":"Request to update a benefit (PUT); omitted fields keep their current value.","$ref":"#/components/schemas/PatchBenefitRequest"}},"application/*+json":{"schema":{"description":"Request to update a benefit (PUT); omitted fields keep their current value.","$ref":"#/components/schemas/PatchBenefitRequest"}}}},"responses":{"200":{"description":"Benefit updated."},"400":{"description":"Invalid payload or pass identifiers, category not allowed for the benefit type, or `data.movements` refused.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"401":{"description":"The caller is not authenticated.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"403":{"description":"The caller lacks the <c>tenant.pass:write</c> scope.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"404":{"description":"Pass not found, or the benefit belongs to another pass.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"429":{"description":"Over 1200 requests per minute to this endpoint for the tenant (default limit); retry after <c>Retry-After</c>.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent when the limiter knows it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}}}}}}}
```

## Delete benefit

> Permanent. The pass is updated on the holder's devices.

```json
{"openapi":"3.1.1","info":{"title":"Neostore internal API","version":"v1"},"tags":[{"name":"Benefit"}],"servers":[{"url":"https://app.neostore.cloud","description":"Production Server"},{"url":"https://app-qa.neostore.cloud","description":"Staging Server"}],"security":[{"admin-bearer":["tenant.pass:write"]},{"apiKey":[]}],"components":{"securitySchemes":{"admin-bearer":{"type":"oauth2","flows":{"implicit":{"authorizationUrl":"https://auth.neostore.cloud/authorize?audience=https://app.neostore.cloud/api/","scopes":{}}}},"apiKey":{"type":"apiKey","name":"X-API-KEY","in":"header"}},"schemas":{"ProblemDetails":{"type":"object","properties":{"type":{"type":["null","string"]},"title":{"type":["null","string"]},"status":{"type":["null","integer"],"format":"int32"},"detail":{"type":["null","string"]},"instance":{"type":["null","string"]}},"additionalProperties":{}},"HttpValidationProblemDetails":{"type":"object","allOf":[{"$ref":"#/components/schemas/ProblemDetails"}],"properties":{"errors":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}},"additionalProperties":{}}}},"paths":{"/api/{tenantId}/passes/benefits/{benefitId}":{"delete":{"tags":["Benefit"],"summary":"Delete benefit","description":"Permanent. The pass is updated on the holder's devices.","parameters":[{"name":"benefitId","in":"path","description":"Id of the benefit to delete.","required":true,"schema":{"type":"string"}},{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Benefit deleted."},"401":{"description":"The caller is not authenticated.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"403":{"description":"The caller lacks the <c>tenant.pass:write</c> scope.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"404":{"description":"Benefit not found.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"429":{"description":"Over 1200 requests per minute to this endpoint for the tenant (default limit); retry after <c>Retry-After</c>.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent when the limiter knows it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}}}}}}}
```

## Apply benefit movement

> Appends a change of the benefit's value (redemption, adjustment, issuance) to its history and updates the pass on the holder's devices. The movement id and date are set by the server.\
> Send the value last read in \`oldValue\`: the movement is applied only while the benefit still has it, so a retried request is not applied twice.\
> Refused outside the benefit's activation and deactivation dates or once it is removed, when a redemption would take a \`oneTime\` or \`multiUse\` balance below zero (\`unlimited\` benefits are exempt), and when a credit would take the balance above what the benefit was issued with (1 for \`oneTime\`, \`behavior.target\` for \`multiUse\`).\
> Example: \`{"oldValue":3,"value":-1,"remarks":"Receipt 4512"}\`

```json
{"openapi":"3.1.1","info":{"title":"Neostore internal API","version":"v1"},"tags":[{"name":"Benefit"}],"servers":[{"url":"https://app.neostore.cloud","description":"Production Server"},{"url":"https://app-qa.neostore.cloud","description":"Staging Server"}],"security":[{"admin-bearer":["tenant.pass:write"]},{"apiKey":[]}],"components":{"securitySchemes":{"admin-bearer":{"type":"oauth2","flows":{"implicit":{"authorizationUrl":"https://auth.neostore.cloud/authorize?audience=https://app.neostore.cloud/api/","scopes":{}}}},"apiKey":{"type":"apiKey","name":"X-API-KEY","in":"header"}},"schemas":{"ApplyMovementRequest":{"required":["oldValue","value"],"type":"object","properties":{"oldValue":{"type":"number","description":"Sum of the benefit's movements as last read. Optimistic concurrency and retry safety: the movement is applied only while the benefit is still at this value, otherwise 409.","format":"double"},"value":{"type":"number","description":"Signed change in whole uses: positive credits, negative redeems. Not zero; a fraction is refused with 400.","format":"double"},"remarks":{"maxLength":500,"minLength":1,"type":["null","string"],"description":"Note about the movement, e.g. a till receipt number; never shown to the holder. 1 to 500 characters, trimmed."}},"additionalProperties":false,"description":"Movement to apply to a benefit's value."},"MovementResponse":{"type":"object","properties":{"movementId":{"type":"string","description":"Id of the movement, as listed in the benefit's `data.movements`."}},"additionalProperties":false,"description":"Created movement."},"ProblemDetails":{"type":"object","properties":{"type":{"type":["null","string"]},"title":{"type":["null","string"]},"status":{"type":["null","integer"],"format":"int32"},"detail":{"type":["null","string"]},"instance":{"type":["null","string"]}},"additionalProperties":{}},"HttpValidationProblemDetails":{"type":"object","allOf":[{"$ref":"#/components/schemas/ProblemDetails"}],"properties":{"errors":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}},"additionalProperties":{}}}},"paths":{"/api/{tenantId}/passes/benefits/{benefitId}/movements":{"post":{"tags":["Benefit"],"summary":"Apply benefit movement","description":"Appends a change of the benefit's value (redemption, adjustment, issuance) to its history and updates the pass on the holder's devices. The movement id and date are set by the server.\nSend the value last read in `oldValue`: the movement is applied only while the benefit still has it, so a retried request is not applied twice.\nRefused outside the benefit's activation and deactivation dates or once it is removed, when a redemption would take a `oneTime` or `multiUse` balance below zero (`unlimited` benefits are exempt), and when a credit would take the balance above what the benefit was issued with (1 for `oneTime`, `behavior.target` for `multiUse`).\nExample: `{\"oldValue\":3,\"value\":-1,\"remarks\":\"Receipt 4512\"}`","parameters":[{"name":"benefitId","in":"path","description":"Id of the benefit to apply the movement to.","required":true,"schema":{"type":"string"}},{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Movement and the balance expected before it.","content":{"application/json":{"schema":{"description":"Movement to apply to a benefit's value.","$ref":"#/components/schemas/ApplyMovementRequest"}},"text/json":{"schema":{"description":"Movement to apply to a benefit's value.","$ref":"#/components/schemas/ApplyMovementRequest"}},"application/*+json":{"schema":{"description":"Movement to apply to a benefit's value.","$ref":"#/components/schemas/ApplyMovementRequest"}}}},"responses":{"200":{"description":"Movement applied; returns the movement id.","content":{"text/plain":{"schema":{"description":"Created movement.","$ref":"#/components/schemas/MovementResponse"}},"application/json":{"schema":{"description":"Created movement.","$ref":"#/components/schemas/MovementResponse"}},"text/json":{"schema":{"description":"Created movement.","$ref":"#/components/schemas/MovementResponse"}}}},"400":{"description":"Value is zero or not a whole number, or invalid payload.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"401":{"description":"The caller is not authenticated.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"403":{"description":"The caller lacks the <c>tenant.pass:write</c> scope.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"404":{"description":"Benefit not found.","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"409":{"description":"`concurrency-conflict`: value differs from `oldValue` (problem carries `currentValue`, `expectedValue`).\n            `benefit-not-redeemable`: outside its validity dates, removed, or the balance is too low.\n            `benefit-ceiling-exceeded`: the credit would exceed the issued value (problem carries `ceiling`, `attemptedValue`).","content":{"text/plain":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}},"text/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"429":{"description":"Over 1200 requests per minute to this endpoint for the tenant (default limit); retry after <c>Retry-After</c>.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent when the limiter knows it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}}}}}}}
```

## List benefit origins

> One entry per distinct \`origin.processId\` in the tenant (benefit templates and integrations), with the count of benefits not yet past their deletion date.\
> Meant for filter lists, not live figures: counts can lag by up to a minute.

```json
{"openapi":"3.1.1","info":{"title":"Neostore internal API","version":"v1"},"tags":[{"name":"Benefit"}],"servers":[{"url":"https://app.neostore.cloud","description":"Production Server"},{"url":"https://app-qa.neostore.cloud","description":"Staging Server"}],"security":[{"admin-bearer":["tenant.pass:read"]},{"apiKey":[]}],"components":{"securitySchemes":{"admin-bearer":{"type":"oauth2","flows":{"implicit":{"authorizationUrl":"https://auth.neostore.cloud/authorize?audience=https://app.neostore.cloud/api/","scopes":{}}}},"apiKey":{"type":"apiKey","name":"X-API-KEY","in":"header"}},"schemas":{"BenefitOriginSummary":{"required":["count","processId"],"type":"object","properties":{"processId":{"type":"string","description":"Origin process id (e.g. benefit template id or external job id)."},"count":{"type":"integer","description":"Benefits with this processId across the tenant, not yet past their deletion date.","format":"int32"}},"additionalProperties":false,"description":"Live benefit count of an origin process id."},"ProblemDetails":{"type":"object","properties":{"type":{"type":["null","string"]},"title":{"type":["null","string"]},"status":{"type":["null","integer"],"format":"int32"},"detail":{"type":["null","string"]},"instance":{"type":["null","string"]}},"additionalProperties":{}},"HttpValidationProblemDetails":{"type":"object","allOf":[{"$ref":"#/components/schemas/ProblemDetails"}],"properties":{"errors":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}},"additionalProperties":{}}}},"paths":{"/api/{tenantId}/passes/benefits/origins":{"get":{"tags":["Benefit"],"summary":"List benefit origins","description":"One entry per distinct `origin.processId` in the tenant (benefit templates and integrations), with the count of benefits not yet past their deletion date.\nMeant for filter lists, not live figures: counts can lag by up to a minute.","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Origins returned; an empty array when no benefit has a process id.","content":{"text/plain":{"schema":{"type":"array","items":{"description":"Live benefit count of an origin process id.","$ref":"#/components/schemas/BenefitOriginSummary"}}},"application/json":{"schema":{"type":"array","items":{"description":"Live benefit count of an origin process id.","$ref":"#/components/schemas/BenefitOriginSummary"}}},"text/json":{"schema":{"type":"array","items":{"description":"Live benefit count of an origin process id.","$ref":"#/components/schemas/BenefitOriginSummary"}}}}},"401":{"description":"The caller is not authenticated.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"403":{"description":"The caller lacks the <c>tenant.pass:read</c> scope.","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}},"429":{"description":"Over 1200 requests per minute to this endpoint for the tenant (default limit); retry after <c>Retry-After</c>.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Sent when the limiter knows it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetails"},{"$ref":"#/components/schemas/HttpValidationProblemDetails"}]}}}}}}}}}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.thewalletcrew.io/api-reference/advanced/benefit.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
