{"openapi":"3.0.0","info":{"title":"Litecard API","version":"1.0.0"},"servers":[{"url":"https://bff-api.enterprise.litecard.io","description":"Live URL"},{"url":"https://bff-api-green.enterprise.litecard.io","description":"Gateway Version URL"},{"url":"http://localhost:4001/","description":"Localhost"}],"paths":{"/api/v1/ping":{"get":{"tags":["Health Check"],"operationId":"healthCheck","summary":"Health check (GET)","description":"**Is the API up?** Returns `{ \"status\": \"ok\" }` with a `200`. No auth, no side effects, safe to poll.\n\nIt says nothing about your credentials: an expired token still gets `200` here. To check those, call `POST /api/v1/token`.","responses":{"200":{"description":"Returns successfully if server is up","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","description":"Health Check Response","example":"ok"}}}}}}}},"head":{"tags":["Health Check"],"operationId":"healthCheckHead","summary":"Health check (HEAD)","description":"**The same probe, headers only.** Status line, no body, same handler as the `GET`.\n\nIt exists because load balancers and wait-for-it scripts often default to `HEAD`. Without it they would fall through to the unmatched-route path and read a healthy service as down.","responses":{"200":{"description":"Returns successfully if server is up"}}}},"/api/v1/docs":{"get":{"tags":["Documentation"],"operationId":"APIDocs_v1","summary":"Get the OpenAPI document","description":"**The contract this page is generated from.** Plain OpenAPI 3.0, no narrative layer, no auth required.\n\nThis is the one to feed into a code generator or an API client. `GET /api/v1.0/docs` returns the same paths and schemas with the prose added, which makes it larger and noisier for tooling.","responses":{"200":{"description":"swagger.json"}}}},"/api/v1/queue/notification":{"post":{"operationId":"sendNotificationQueue_v1","summary":"Send a notification","description":"**Queue a push, email or SMS to pass owners.** The call returns once the send is queued, not once anybody's phone has buzzed.\n\nThe body has three parts, each covered in full in the section above:\n\n1. **The message.** `notification.title`, `message` and `dataFieldKey` are all required, and `dataFieldKey` is always `\"notificationKey\"`. Write the `message` first: Apple shows it on the lock screen on its own, while Google shows only \"New message\" until the owner opens the pass.\n2. **The audience.** One of five mutually exclusive strategies, checked in this order and stopping at the first you set: `cardIds`, `templateIds`, `templateTier`, `segments`, `options.sendAll`.\n3. **Personalisation.** `{{placeholders}}` are resolved per pass, and each needs its fallback.\n\n**`cardIds` is a required property.** Send `\"cardIds\": []` whenever you are choosing the audience another way — the schema rejects a body without it.\n\n**Nothing checks that you selected anyone.** Leave all five strategies empty and the call still returns 200. The group is created, resolves to zero passes, and no one hears anything. This endpoint will not tell you.\n\n### When it fails\n\n| Response | Cause |\n| --- | --- |\n| `400 INVALID_NOTIFICATION_PAYLOAD` | a `{{placeholder}}` in `title` or `message` without its `fallbackTitle` / `fallbackMessage` |\n| `400 INVALID_NOTIFICATION_PAYLOAD` | a fallback that itself contains a placeholder |\n| `400 INVALID_NOTIFICATION_PAYLOAD` | `cardIds` combined with `notification.recurringConfig` |\n\nAll three are rejected before anything is queued.\n\n### After it returns\n\nEvery send is recorded as a [notification group](#tag/notification-groups), which is how you audit what went out and how you stop a scheduled one.\n\nA large audience can take up to a minute to queue. Treat a slow response as normal and **do not retry** — you will queue the send twice, and there is no unsend. A scheduled send can be cancelled before it fires; a delivered one cannot be recalled.","tags":["Push Notifications"],"parameters":[{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationV1"}}},"required":true},"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Notification sent successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","description":"Successful Response","example":true}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/swagger":{"get":{"tags":["Documentation"],"operationId":"swaggerUI","summary":"Swagger UI Endpoint","responses":{"200":{"description":"Swagger Web UI of API documentation"}}}},"/api/v1/token":{"post":{"operationId":"authenticate","summary":"Get an access token","description":"**Exchange credentials for a bearer token.** Every authenticated endpoint needs one.\n\n**Use username and password**, the dashboard credentials for your account. It is the recommended route and needs no setup. The response carries `access_token` and `expires_in`, 86400 seconds (24 hours) by default.\n\nOAuth2 `client_credentials` is also accepted, for systems that require that spec. Those credentials are issued on request rather than self-service, so contact the Litecard team if you need them.\n\nCache the token and refresh on `401`. Minting one per request is the most common performance mistake we see in integrations.","tags":["Authentication"],"parameters":[{"name":"x-user-tenant","in":"header","description":"Optional tenant identifier (litecard or shakewell)","schema":{"type":"string","enum":["litecard","shakewell"]},"required":false}],"requestBody":{"description":"Supports username/password authentication or OAuth2 client credentials authentication","required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/authenticationRequestBody"},{"$ref":"#/components/schemas/oAuth2ClientCredentialsRequestBody"}]}}}},"responses":{"200":{"description":"Success access token granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/authenticationResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/order-templates":{"get":{"operationId":"listOrderTemplates_v1","summary":"List order templates","description":"List your Apple Wallet order templates.","tags":["Order Templates"],"parameters":[{"$ref":"#/components/parameters/limitPagination"},{"$ref":"#/components/parameters/nextPagination"},{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Templates listed","content":{"application/json":{"schema":{"type":"object","properties":{"orderTemplates":{"type":"array","items":{"$ref":"#/components/schemas/OrderTemplateV1"}},"next":{"type":"string","description":"Key used to next set of results"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"post":{"operationId":"createOrderTemplate_v1","summary":"Create an order template","description":"**Blueprint for Apple Wallet order passes**, the package-tracking passes that show a delivery status in the wallet.\n\nA distinct product from loyalty or ticket passes, with its own template type. Create the template once, then one order per purchase.","tags":["Order Templates"],"parameters":[{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrderTemplateV1"}}}},"responses":{"201":{"description":"Order template created","content":{"application/json":{"schema":{"type":"object","properties":{"orderTemplateId":{"type":"string"},"success":{"type":"boolean"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/order-templates/{orderTemplateId}":{"get":{"operationId":"getOrderTemplateById_v1","summary":"Get an order template","description":"Fetch one order template.","tags":["Order Templates"],"parameters":[{"$ref":"#/components/parameters/orderTemplateId"},{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Order template retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderTemplateV1"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"put":{"operationId":"updateOrderTemplate_v1","summary":"Update an order template","description":"Update an order template. Live orders built from it follow.","tags":["Order Templates"],"parameters":[{"$ref":"#/components/parameters/orderTemplateId"},{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateOrderTemplateV1"}}},"required":true},"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Order template updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"orderTemplateId":{"type":"string"},"orderTemplate":{"$ref":"#/components/schemas/OrderTemplateV1"},"success":{"type":"boolean"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"delete":{"operationId":"deleteOrderTemplate_v1","summary":"Delete an order template","description":"Delete an order template. Existing orders are unaffected.","tags":["Order Templates"],"parameters":[{"$ref":"#/components/parameters/orderTemplateId"},{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Order template deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/orders":{"get":{"operationId":"listWalletOrders_v1","summary":"List orders","description":"List wallet orders for the business.","tags":["Orders"],"parameters":[{"$ref":"#/components/parameters/limitPagination"},{"$ref":"#/components/parameters/nextPagination"},{"$ref":"#/components/parameters/orderTemplateIdQuery"},{"$ref":"#/components/parameters/orderNumberQuery"},{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Orders listed","content":{"application/json":{"schema":{"type":"object","properties":{"orders":{"type":"array","items":{"$ref":"#/components/schemas/WalletOrderV1"}},"next":{"type":"string","nullable":true,"description":"Key used to next set of results"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"post":{"operationId":"createWalletOrder_v1","summary":"Create an order","description":"**Issue an order pass for one purchase**, from an order template.","tags":["Orders"],"parameters":[{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWalletOrderV1"}}}},"responses":{"201":{"description":"Order created (merged `order.json` fields)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWalletOrderResponseV1"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/orders/{orderId}":{"get":{"operationId":"getWalletOrder_v1","summary":"Get an order","description":"Fetch one wallet order and its current fulfilment state.","tags":["Orders"],"parameters":[{"$ref":"#/components/parameters/orderId"},{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Order retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtendedWalletOrderV1"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"patch":{"operationId":"patchWalletOrder_v1","summary":"Update order fulfilment","description":"**Advance an order's fulfilment state and push it to the wallet.**\n\nThis is what makes the delivery status change on the customer's lock screen as the parcel moves. Call it at each milestone rather than only on delivery.","tags":["Orders"],"parameters":[{"$ref":"#/components/parameters/orderId"},{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchWalletOrderV1"}}}},"responses":{"200":{"description":"Order updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchWalletOrderResponseV1"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/template":{"post":{"operationId":"createTemplate_v1","summary":"Create a template","description":"**Create the design your passes are stamped from.** Do this once per pass type, not once per customer.\n\n**We recommend the dashboard for your first template**, under Templates › Create Pass Template. This endpoint works, but a template is a large, fiddly document and the dashboard validates every part of it as you go and previews the result. Reach for the API once you are generating templates programmatically.\n\nEverything visual lives here: colours, images, barcode format, the data fields a pass carries, expiry rules, and which wallets you target. The response `id` is the `templateId` you pass when issuing.\n\nSizing your images correctly is the difference between a pass that looks designed and one that looks broken. The `images` schema below lists the exact pixel dimensions each wallet expects.\n\n**Next:** issue a pass with `POST /api/v1/card`, or do both at once with `POST /api/v1/card/template`.","tags":["Pass Templates CRUD"],"parameters":[{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTemplateV1"}}},"required":true},"security":[{"BearerAuth":[]}],"responses":{"201":{"description":"User has successfully created the template","content":{"application/json":{"schema":{"properties":{"templateId":{"type":"string"},"formId":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"put":{"operationId":"updateTemplate_v1","summary":"Update a template","description":"**Edit the design in place.** Whether the change reaches passes already in wallets is up to you.\n\n**By default it does not.** The template and its signup form are saved and the Google class behind existing passes is refreshed, but the data fields on passes already issued keep the values they have.\n\n**Send `updateExistingCards: true` for the over-the-air push.** That is what walks the active passes and updates them to match. The work is queued rather than done inline, so the call returns as soon as the batch is created, with a `groupId` in the response you can track it by.\n\nWhat the push actually touches:\n\n| Field kind | On the template | What happens to live passes |\n| --- | --- | --- |\n| Static | `userCanEdit: false` | Updated where you changed `defaultValue` or `label`, added where new |\n| Form | `userCanEdit: true` | **Added only, never overwritten.** Each owner has their own value there |\n\nThat asymmetry is deliberate: overwriting a form field would trample the points balance or member number belonging to every individual owner.\n\n**One guard.** If the template already has passes and you add or change a field without a `defaultValue`, the call fails with `400 INVALID_TEMPLATE_UPDATE` rather than pushing a blank into every wallet.\n\nThis is the most far-reaching call in the API. Fixing a typo in your business name corrects thousands of wallets at once, and a mistake travels just as fast, so use a throwaway template as a staging area before editing one with passes in circulation. The dashboard will not let you edit a template at all for this reason.","tags":["Pass Templates CRUD"],"parameters":[{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTemplateV1"}}},"required":true},"security":[{"BearerAuth":[]}],"responses":{"201":{"description":"User has successfully updated the template","content":{"application/json":{"schema":{"properties":{"templateId":{"type":"string"},"formId":{"type":"string"},"groupId":{"type":"string","description":"Will only appear if updateExistingCards is set to true"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/template/{templateId}":{"get":{"operationId":"getTemplate_v1","summary":"Get a template","description":"Fetch one template, including its live counters: `cardCount`, `downloadsCount`, and the Apple/Google download split.","tags":["Pass Templates CRUD"],"parameters":[{"$ref":"#/components/parameters/templateId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Template retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"body":{"$ref":"#/components/schemas/TemplateV1"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/templates":{"get":{"operationId":"listTemplates_v1","summary":"List templates","description":"List every template for the active business.\n\nCursor-paginated: pass the returned `next` value to fetch the following page.","tags":["Pass Templates CRUD"],"parameters":[{"$ref":"#/components/parameters/limitPagination"},{"$ref":"#/components/parameters/nextPagination"},{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Templates listed","content":{"application/json":{"schema":{"type":"object","properties":{"templates":{"$ref":"#/components/schemas/ListTemplatesV1"},"next":{"type":"string","description":"Key used to next set of results"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/template/status/{templateId}":{"post":{"operationId":"setTemplateStatus","summary":"Archive or delete a template","description":"**Archive or delete a template.** There is no `DELETE /api/v1/template/{id}`; status changes go through here with `{ \"status\": \"DELETED\" }`.\n\nDeleting a template does not delete the passes issued from it. Retire the passes separately with `POST /api/v1/card/status` if that is what you mean.","tags":["Pass Templates CRUD"],"security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/templateId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","description":"Template status","example":"ACTIVE","enum":["ACTIVE","INACTIVE","DELETED"]}},"required":["status"]}}},"required":true},"responses":{"200":{"description":"Successfully change template status","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/template/{templateId}/samsung/launch":{"post":{"operationId":"launchSamsungTemplate_v1","summary":"Activate a READY_TO_LAUNCH Samsung card template","tags":["Card Template"],"security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/templateId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"responses":{"200":{"description":"Successfully launched the Samsung card template","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/certificates":{"get":{"operationId":"getCertificates_v1","summary":"List Certificates Endpoint","tags":["Apple Certificate"],"security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/limitPagination"},{"$ref":"#/components/parameters/nextPagination"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"properties":{"certificates":{"type":"array","items":{"$ref":"#/components/schemas/ListCertificatesResponseV1"},"description":"Certificate metadata"},"next":{"type":"string","description":"Key to continue pagination"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/certificate/upload":{"post":{"operationId":"certificateUpload_v1","summary":"Upload Certificate Endpoint","tags":["Apple Certificate"],"parameters":[{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CertificateUploadV1"}}},"required":true},"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"User has successfully uploaded the certificate","content":{"application/json":{"schema":{"properties":{"certificateId":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/certificate/csr":{"get":{"operationId":"getCSR_v1","summary":"Get Certificate Signing Request Endpoint","tags":["Apple Certificate"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"properties":{"csr":{"type":"string","description":"base64 encoded csr"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}},"parameters":[{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}]}},"/api/v1/card-owner":{"post":{"operationId":"getCardsOfCardOwner","summary":"List passes by email or phone","description":"**Every pass one person holds**, keyed on email or phone.\n\nA pass owner is Litecard's identity for a person across templates, so this returns their loyalty pass, their event ticket and their coupon together. The right call for a \"my passes\" screen or a support agent's view.","tags":["Pass Owner"],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListPassOfCardOwnerRequestV1"}}},"required":true},"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Retrieved List of Passes","content":{"application/json":{"schema":{"type":"object","properties":{"results":{"$ref":"#/components/schemas/ListPassesResult"},"next":{"type":"string","description":"Key used to next set of results"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}},"parameters":[{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}]}},"/api/v1/card-owner/delete":{"delete":{"operationId":"deleteCardsByContact","summary":"Delete all passes for one owner","description":"**Delete every pass held by one person**, addressed by email or phone rather than by pass id.\n\nBuilt for data-deletion requests, where you know the customer but not their pass ids.","tags":["Pass Owner"],"security":[{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteCardsByContactRequestBody"}}},"required":true},"responses":{"200":{"description":"Cards Deleted","content":{"application/json":{"schema":{"properties":{"success":{"type":"boolean"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}},"parameters":[{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}]}},"/api/v1/card":{"patch":{"operationId":"updateCard","summary":"Update a pass","description":"**Change a pass that is already in someone's wallet.**\n\nThis is why wallet passes beat a PDF. Send the changed `cardPayload` keys here and Apple and Google push the new version to the device. The owner does nothing: no email, no re-download. Their points balance simply changes on the lock screen.\n\nCommon uses: award points after a purchase, move someone to a new tier, update a seat or gate, mark a coupon as used.\n\nOnly the fields you send change. Everything else on the pass is left alone.\n\n### Get a ready-made payload from the eye icon\n\n**A key you spell wrong is not an error, it is a field that never updates.** The names have to match the template's data fields exactly, and the dashboard will tell you them.\n\nOn **Templates › View Pass Templates**, each row ends with an **Actions** column. Click the **eye** icon and you get a complete, copy-ready payload for that template, with its own data fields already filled in with sample values.\n\n![Finding a template's sample payload in the dashboard](/api/v1.0/assets/template-payload.svg)\n\nKeep the keys you want to change, delete the rest, and send that. It is the same list of names [`POST /api/v1/card`](#tag/pass/POST/api/v1/card) accepts, so one look serves both calls.","tags":["Pass"],"parameters":[{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCardRequestBody"}}},"required":true},"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Update completed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCardResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"post":{"operationId":"createCard","summary":"Create a pass","description":"**Issue one pass to one person.** The workhorse of the API.\n\nSupply a `templateId` and a `cardPayload` whose keys are the data fields that template defines (name, points, member number).\n\n### Get a ready-made payload from the eye icon\n\n**You do not have to guess what `cardPayload` should contain.** On **Templates › View Pass Templates**, each row ends with an **Actions** column. Click the **eye** icon and the dashboard shows a complete, copy-ready body for creating a pass from *that* template, with its own data fields already filled in with sample values.\n\n![Finding a template's sample payload in the dashboard](/api/v1.0/assets/template-payload.svg)\n\nPaste it here, swap the sample values for real ones, and you have a valid request. Two templates rarely take the same keys, so check the eye again whenever you point at a new `templateId`.\n\nUse `templateOverrides` when the *design* must differ for this one customer too, such as the stores they visit or a link to their own account.\n\nYou get back a `downloadId`. Send the owner to its download page and one tap adds the pass to their wallet, with no app install.\n\n> **Tip.** Issuing more than a few hundred at once? Do not loop this endpoint. Use the CSV upload flow or `POST /api/v1/card/bulk-fast-create`.","tags":["Pass"],"parameters":[{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrivateSignUpRequestBody"}}},"required":true},"responses":{"201":{"description":"User Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignUpResponseSchema"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"409":{"$ref":"#/components/responses/AlreadyExists"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/card/retrieve":{"post":{"operationId":"privateRetrieveDeviceCardLink","summary":"Get a device-specific download link","description":"Get the download link for a pass, resolved for the requesting device so the owner lands on the right wallet.\n\nUse it to re-send a pass to someone who lost the original link.","tags":["Access Passes by Download ID"],"security":[{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatFastCardRequestBody"}}},"required":true},"responses":{"200":{"description":"Created device specific pass","content":{"application/json":{"schema":{"type":"object","properties":{"appleLink":{"type":"string","description":"Link to apple pass","example":"https://litecard-demo-pkpass.s3-ap-southeast-2.amazonaws.com/coldbrew.pkpass"},"googleLink":{"type":"string","description":"Link to google pass","example":"https://pay.google.com/gp/v/save/eyJhbGciOiJSU"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/card/status":{"post":{"operationId":"setCardStatus","summary":"Deactivate, reactivate or delete a pass","description":"**Deactivate, reactivate or delete a pass.**\n\n- **Deactivate** leaves the pass in the wallet but makes it void when scanned. This is the right choice for a lost pass, a cancelled membership, or a refunded ticket, because the owner can see it was withdrawn.\n- **Reactivate** reverses that.\n- **Delete** removes the pass entirely.\n\nDeactivating is reversible and auditable; deleting is not. Prefer deactivation unless you have a data-retention reason to erase.","tags":["Pass"],"security":[{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"cardId":{"type":"string","description":"Unique identifier for the card","example":"7048582966"},"status":{"type":"string","description":"Card status","example":"ACTIVE","enum":["ACTIVE","INACTIVE","DELETED"]}},"required":["status"]}}},"required":true},"responses":{"200":{"description":"Successfully set card status","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}},"parameters":[{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}]}},"/api/v1/card/{cardId}":{"get":{"operationId":"getCard","summary":"Get a pass by ID","description":"Fetch a single pass by id, including its current data-field values, status, and download state.","tags":["Pass"],"parameters":[{"$ref":"#/components/parameters/cardId"},{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Successful Get Card Response","content":{"application/json":{"schema":{"type":"object","properties":{"body":{"$ref":"#/components/schemas/Card"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/card/template":{"post":{"operationId":"createPassTemplate_v1","summary":"Create a template and a pass","description":"**Create a template and issue its first pass in one call.**\n\nA convenience for one-off passes where a reusable design would be overhead: a single event badge, a one-off gift pass. If you plan to issue more against the same design, create the template separately so you can edit it later.","tags":["Pass"],"parameters":[{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/createPassTemplateRequestBody"}}},"required":true},"responses":{"200":{"description":"Template and Pass have been successfully created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/createPassTemplateResponseBody"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/card/template/{templateId}":{"get":{"operationId":"listCardsByTemplateId","summary":"List passes by template","description":"Every pass issued from one template. The natural query for \"who holds this campaign's pass\".\n\nCursor-paginated via `next`.","tags":["Pass"],"parameters":[{"$ref":"#/components/parameters/templateId"},{"$ref":"#/components/parameters/limitPagination"},{"$ref":"#/components/parameters/nextPagination"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"List of card by TemplateId","content":{"application/json":{"schema":{"type":"object","properties":{"passes":{"$ref":"#/components/schemas/ListPassesResult"},"next":{"type":"string","description":"Key used to next set of results"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/uiconfig/{downloadId}":{"get":{"operationId":"getUIConfig","summary":"Get download page branding","description":"Branding for a download page, so the page around the Add-to-Wallet button matches the business.","tags":["Design"],"parameters":[{"$ref":"#/components/parameters/downloadId"},{"$ref":"#/components/parameters/passType"},{"$ref":"#/components/parameters/txnId"}],"responses":{"200":{"description":"Successful get download response","content":{"application/json":{"schema":{"type":"object","properties":{"body":{"$ref":"#/components/schemas/UIConfigResponse"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/downloadId/{downloadId}":{"get":{"operationId":"getWelcome_v1","summary":"Get Add-to-Wallet links","description":"**Resolve a `downloadId` into Add-to-Wallet links.** Public, because the owner's browser calls it.\n\nBacks the page an owner lands on after you send them their pass.","tags":["Access Passes by Download ID"],"parameters":[{"$ref":"#/components/parameters/downloadId"},{"$ref":"#/components/parameters/passType"},{"$ref":"#/components/parameters/txnId"}],"responses":{"200":{"description":"Successful get download response","content":{"application/json":{"schema":{"type":"object","properties":{"body":{"$ref":"#/components/schemas/WelcomeDetailsResponse"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/backlink/{backlinkId}":{"get":{"operationId":"getBacklink_v1","summary":"Resolve a backlink","description":"Resolve a short link back to its pass. Use backlinks in SMS and printed QR codes where a full download URL is unwieldy.","tags":["Backlinks"],"parameters":[{"$ref":"#/components/parameters/backlinkId"},{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful get download response","content":{"application/json":{"schema":{"type":"object","properties":{"body":{"$ref":"#/components/schemas/getBacklinksResponse"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/private/scans":{"post":{"operationId":"scan_v1","summary":"Resolve scan detail","description":"**Turn a scanned barcode into the pass, its owner, and what they qualify for.**\n\n```json\n{\n  \"card\": { \"id\": \"c_7f2\", \"numOfScans\": 9, \"status\": \"ACTIVE\", \"dataFields\": { \"points\": 90 } },\n  \"cardOwner\": { \"firstName\": \"John\", \"lastName\": \"Doe\", \"email\": \"john.doe@example.com\" },\n  \"eligibleScanRules\": [ { \"ruleName\": \"Earn 10 points on each scan\" } ],\n  \"eligibleBusinessRules\": [],\n  \"nextAvailableScanRules\": [\n    { \"ruleName\": \"Deactivate membership after 100 visits\", \"executeAfter\": 91 }\n  ]\n}\n```\n\nThe eligible arrays are what applies right now. `executeAfter` counts the scans still to go, so a till can say \"one more visit and the next one is free\". It is read from a rule's `$.numOfScans` condition, which is why a reward gated on a points balance never appears in `nextAvailableBusinessRules`.\n\n**This call changes nothing.** No points, no scan count, no push. Apply the outcome with `POST /api/v1/scans/actions`, or hand over a reward with `POST /api/v1/scans/business-rules/actions`.\n\nAn expired pass is rejected with `401 PASS_EXPIRED` before any of this is returned.","tags":["Scan"],"parameters":[{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanBarcodeV1RequestBody"}}},"required":true},"responses":{"200":{"description":"Successful Scan","content":{"application/json":{"schema":{"type":"object","properties":{"body":{"$ref":"#/components/schemas/ScanV1ResponseSchema"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"get":{"operationId":"listScans_v1","summary":"List scans","description":"Scan history for the business: who was scanned, when, by which user, and what the pass looked like at the time. The raw material for redemption reporting.\n\nFilter with `cardId` to trace one pass, or `startDateTime` and `endDateTime` for a period, and page with `limit` and `next`. For the same data in bulk, use `POST /api/v1/exportCsv` with `\"tableName\": \"SCAN_TABLE\"`.\n\n**Only `POST /api/v1/scans/actions` writes here.** Rewards handed over through `POST /api/v1/scans/business-rules/actions` apply the rule but record no scan, so they will not appear in this history.","tags":["Scan"],"parameters":[{"$ref":"#/components/parameters/limitPagination"},{"$ref":"#/components/parameters/nextPagination"},{"$ref":"#/components/parameters/cardIdQuery"},{"$ref":"#/components/parameters/startDateTime"},{"$ref":"#/components/parameters/endDateTime"},{"$ref":"#/components/parameters/includeDeleted"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"$ref":"#/components/responses/ListScansV1"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/scans/actions":{"post":{"operationId":"templateActions","summary":"Apply scan rules to a pass","description":"**The call that makes a scan count.** It increments `numOfScans`, applies **every** eligible scan rule, pushes the updated pass to the owner's device, and records a row in scan history. The balance changes while the customer is still at the counter.\n\n```json\n{ \"cardId\": \"c_7f2\", \"scanFrequency\": 3 }\n```\n\n`scanFrequency` advances several steps at once, for a purchase worth more than one visit. It is not a multiplier: rules are re-evaluated once per step, from `numOfScans + 1` through `numOfScans + scanFrequency`, so a rule that fires only at the fifth scan still fires exactly once when one call carries the customer past it.\n\nFor an outcome that needs staff approval, use `POST /api/v1/scans/business-rules/actions` instead.","tags":["Scan"],"parameters":[{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateActionsRequestBody"}}},"required":true},"responses":{"200":{"description":"Successful Action Complete","content":{"application/json":{"schema":{"type":"object","properties":{"body":{"$ref":"#/components/schemas/TemplateActionsResponseSchema"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/scans/business-rules/actions":{"post":{"operationId":"templateBusinessRuleActions","summary":"Apply business rules to a pass","description":"**Apply one staff-approved business rule**, such as handing over an earned reward. Unlike `/scans/actions`, which applies every eligible rule, this applies only the `businessRuleId` you name.\n\n```json\n{ \"cardId\": \"c_7f2\", \"businessRuleId\": 1784703999001 }\n```\n\n**Eligibility is re-checked against the pass as it stands**, not against what an earlier scan returned. That is what stops a reward being claimed twice: the first claim spends the points, and the second call fails.\n\n| Error | Meaning |\n| --- | --- |\n| `400 UNKNOWN_BUSINESS_RULE_ID` | No rule with that id on the template |\n| `400 INELIGIBLE_CLAIMED_BUSINESS_RULE_ID` | The rule exists, but this pass does not qualify |\n| `400 INVALID_DATA_UPDATE_DECREMENT` | The subtraction would take the field below zero |\n\n**This call does not scan.** `numOfScans` is untouched and no scan history row is written, so call `/scans/actions` as well if a redemption should also count as a visit.","tags":["Scan"],"parameters":[{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateBusinessRuleActionsRequestBody"}}},"required":true},"responses":{"200":{"description":"Successful Action Complete","content":{"application/json":{"schema":{"type":"object","properties":{"body":{"$ref":"#/components/schemas/TemplateActionsResponseSchema"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/private/scans/{scanId}":{"delete":{"operationId":"delete_scan_v1","summary":"Delete scan history","description":"Delete one scan record, for correcting a mis-scan rather than for routine cleanup.\n\n**The record is marked deleted, not removed.** It stays out of `GET /api/v1/private/scans` unless you ask for it with `includeDeleted`, so a correction is auditable rather than silent.\n\nDeleting a scan also **decrements the pass's `numOfScans`** by one, so rule eligibility moves back with it: a reward that a mistaken scan had unlocked stops being offered. A pass already at zero is left alone rather than going negative.\n\nIt does not undo what the scan already did. Points added by a `DATA_UPDATE` rule stay on the pass, and a tier or status the scan changed stays changed, so correct those with `PATCH /api/v1/card` if they matter.","tags":["Scan"],"parameters":[{"$ref":"#/components/parameters/scanId"},{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Deleted Scan","content":{"application/json":{"schema":{"type":"object","properties":{"body":{"$ref":"#/components/schemas/DeleteScanV1ResponseSchema"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/scans":{"post":{"operationId":"publicScan_v1","summary":"Resolve scan detail (public)","deprecated":true,"description":"**Deprecated. Use `POST /api/v1/private/scans` instead.**\n\nThe unauthenticated variant, kept for consumer-facing scanners that cannot hold a token. It takes a `businessId` in the body and returns **only `card` and `cardOwner`**, with no `eligibleScanRules`, no `eligibleBusinessRules`, and no `nextAvailableBusinessRules`, so a scanner using it cannot tell staff what the customer has earned.\n\nIt also skips the expiry check that the private endpoint performs, so an expired pass resolves normally here rather than failing with `401 PASS_EXPIRED`.\n\nAnywhere you control the scanner, authenticate and use `POST /api/v1/private/scans`.","tags":["Scan"],"parameters":[{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicScanBarcodeV1RequestBody"}}},"required":true},"responses":{"200":{"description":"Successful Scan","content":{"application/json":{"schema":{"type":"object","properties":{"body":{"$ref":"#/components/schemas/ScanV1ResponseSchema"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/card/business":{"post":{"operationId":"business_card","summary":"Generate a digital business card","description":"Generate a digital business card pass. A self-contained pass type: no loyalty template required.","tags":["Pass"],"parameters":[{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BusinessCardRequestBody"}}},"required":true},"responses":{"200":{"description":"Successful Business Card Creation","content":{"application/json":{"schema":{"type":"object","properties":{"body":{"$ref":"#/components/schemas/SignUpResponseSchema"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/card/bulk-fast-create":{"post":{"operationId":"bulkFastCreateCards","summary":"Create passes in bulk","description":"**Issue up to 100 passes in one call**, and get every result back in the response.\n\nThe `cards` array comes back in the order you sent it, each entry carrying its own `success` flag plus `cardId`, `downloadId` and the wallet links. Failures also collect in `errors`, pairing the original payload with a code and message, and a partly-successful batch returns `207` rather than failing wholesale, so you can retry exactly the rows that broke.\n\n\"Fast\" refers to lazy creation, not to skipping any reporting: the wallet file is built when the owner first opens their link rather than at issue time.\n\nPast a few hundred passes, move to the CSV upload flow. It takes a whole file instead of 100 at a time and runs in the background, so nothing holds a connection open.","security":[{"BearerAuth":[]}],"tags":["Pass"],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkFastCreateCardsRequestBody"}}},"required":true},"responses":{"201":{"description":"All cards created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkFastCreateCardsResponse"}}}},"207":{"description":"Partial success - some cards failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkFastCreateCardsResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}},"parameters":[{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}]}},"/api/v1/exportCsv":{"post":{"operationId":"v1_exportCsv","summary":"Trigger a CSV export","description":"**Trigger an export.** What comes back depends on `tableName`, so read the status code before the body.\n\n```\n202  cardsTable, notificationsTable          -> { exportId, status: \"PENDING\", description }\n200  scansTable, notificationGroupsTable     -> { signedUrl, expiryTime }\n```\n\nOn a `202`, poll `GET /api/v1/csv-exports/{exportId}` until `status` is `COMPLETED` and take `presignedUrl`. On a `200` the file is already yours.\n\n[The section above](#tag/csv) lists the tables, the filters and which columns you get.\n\n**Nothing here is validated against your data.** A `templateId` that matches nothing, or a date range with no passes in it, produces an empty export rather than an error. The only request that is rejected outright is a bad date pair: one date without the other, a value that is not ISO 8601, or a start after the end, each returning `400 BAD_REQUEST` with the reason.\n\nThe export records the filters you sent, so `description` on the result reads back as something like `Passes for Template: \"Bronze Card\" with status: ACTIVE`. Useful when you come back to a list of exports later and need to tell them apart.","tags":["CSV"],"parameters":[{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportCsvV1RequestBody"}}},"required":true},"responses":{"200":{"description":"Successfully get the csv download url","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DownloadCsvResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/pass":{"post":{"operationId":"listPasses_v1","summary":"List passes","description":"**List passes for the business, with filters.**\n\n`POST` rather than `GET` because the filter payload is richer than a query string can carry. Cursor-paginated: send the returned `next` back to page forward.\n\nLooking for one specific owner? `POST /api/v1/pass/search` filters on data-field values and will be faster.","tags":["Pass"],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListPassRequestV1"}}},"required":true},"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Retrieved List of Passes","content":{"application/json":{"schema":{"type":"object","properties":{"results":{"$ref":"#/components/schemas/ListPassesResult"},"next":{"type":"string","description":"Key used to next set of results"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}},"parameters":[{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}]}},"/api/v1/pass/search":{"post":{"operationId":"searchPasses_v1","summary":"Search passes","description":"**Find passes by what is written on them.** Searches data-field values, so you can look someone up by member number, email or name.\n\nThe endpoint to back a support tool or a till lookup, where staff have a customer detail rather than a pass id.","tags":["Pass"],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchPassV1RequestBody"}}},"required":true},"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Retrieved List of Passes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchPassesResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}},"parameters":[{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}]}},"/api/v1/notification-groups":{"get":{"operationId":"listNotificationGroups_v1","summary":"List notification groups","description":"**The audit trail.** Every send your business has made, newest first, with its wording, audience size, delivery counts and status.\n\n`status` tells you where a group is: `PENDING` and `QUEUED` are still stoppable, `IN_PROGRESS` and `SENT` are not, and `CANCELLED` means someone got there in time.\n\n**Read `sentVia` per platform, not as a total.** Its entries carry `count`, `successCount` and `failedCount` for apple, google and email separately. Adding them up gives more than `totalEligiblePasses` whenever an owner holds the pass in both wallets, which is normal; [the section above](#tag/notification-groups) works through the arithmetic.","tags":["Notification Groups"],"parameters":[{"$ref":"#/components/parameters/limitPagination"},{"$ref":"#/components/parameters/nextPagination"},{"$ref":"#/components/parameters/startDateTime"},{"$ref":"#/components/parameters/endDateTime"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"$ref":"#/components/responses/ListNotificationGroupsV1"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/external-call-batches":{"get":{"operationId":"listExternalCallBatches_v1","summary":"List micro-batch groups for a template","tags":["External Call Batches"],"parameters":[{"name":"templateId","in":"query","required":true,"schema":{"type":"string"},"description":"The card template whose batch groups to list"},{"$ref":"#/components/parameters/limitPagination"},{"$ref":"#/components/parameters/nextPagination"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Batch groups for the template, newest first","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/external-call-batches/{groupId}":{"get":{"operationId":"getExternalCallBatch_v1","summary":"Get one micro-batch group and its contact rows","tags":["External Call Batches"],"parameters":[{"name":"groupId","in":"path","required":true,"schema":{"type":"string"},"description":"The batch group id (GRP#...)"},{"$ref":"#/components/parameters/limitPagination"},{"$ref":"#/components/parameters/nextPagination"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"The group row and its contact (REC#) rows","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Batch group not found","content":{"application/json":{"schema":{"type":"object"}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/notification-groups/{notificationGroupId}/cancel":{"put":{"operationId":"cancelNotificationsByGroupId_v1","summary":"Cancel a scheduled send","description":"**Stop a scheduled send before it goes out.**\n\n**You have until ten minutes before `sendTime`.** Inside that window the send is already being assembled and the call is refused with `400 ALREADY_PROCESSED`. There is no later remedy: a delivered notification cannot be recalled from anyone's lock screen.\n\nCancelling sets the group's `status` to `CANCELLED` and, for a send addressed by `cardIds`, releases its share of the shared batch it was queued into.\n\nThis is also how you stop a [template notification](#tag/pass-template-notifications) that a pass has already queued. Editing the template will not reach it; cancelling its group will.\n\n| Response | Cause |\n| --- | --- |\n| `400 ALREADY_PROCESSED` | Less than ten minutes to `sendTime`, or it has already gone |\n| `401 UNAUTHORIZED` | The group belongs to another business |","tags":["Notification Groups"],"parameters":[{"$ref":"#/components/parameters/notificationGroupId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Cancelled Notifications","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","description":"Successfully sent email reminders","example":true}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/subBusiness":{"post":{"operationId":"createSubBusiness_v1","summary":"Create a sub-business","description":"**Create a child business under a master account.**\n\n**Two conditions, both enforced.** The calling account must be a master business, and the caller must hold the `Admin` role.\n\nThe new id is `<masterId>#<subId>`, so the parent is readable from the child. Put it in `x-active-business-id` to act as that business from then on; [the section above](#tag/business) covers what that header does.\n\nSupplying `email` and `password` in the body creates the sub-business's first user at the same time. That user's token is scoped to the sub-business and needs no header.\n\nThe new business starts empty. Templates, users and groups are all its own, so nothing is inherited from the master.","tags":["Business"],"security":[{"BearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSubBusinessRequestBody"}}},"required":true},"responses":{"200":{"description":"Successfully created sub business","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubBusinessSummary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/pass/downloads/count":{"get":{"operationId":"passDownloadsCount_v1","summary":"Get pass downloads","description":"**Totals for the business, one row per business.** Passes created, downloads split by Apple and Google, and how many are active.\n\n**The row count depends on who is asking**, and this is the part worth planning for:\n\n| Caller | `businesses` contains |\n| --- | --- |\n| A master account | **One row per sub-business**, each named and with its own logo |\n| Any other business | **Exactly one row**, its own |\n\nSo a master account gets the whole portfolio broken down in a single call, without iterating its sub-businesses. The shape is the same either way, an array, so a client written for one works for the other.\n\n**Send `x-active-business-id` to narrow a master to one sub-business.** It then answers as that business would, with a single row.\n\n`duration` is required, and `400 MISSING_DURATION` comes back without it. `ALL` reads the running lifetime counters; any other value aggregates the daily records from that point up to today, which costs more and is the reason `ALL` exists.\n\nFor one template rather than the business, use `GET /api/v1/template/downloads/count`. For the daily curve, use the trend endpoint below.","tags":["Business Analytics"],"security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/passCountsDuration"}],"responses":{"200":{"description":"Successfully retrieved pass downloads count","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PassCountsHistory"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/pass/downloads/trend":{"get":{"operationId":"passDownloadTrend_v1","summary":"Get pass download trend","description":"**One row per day, oldest first, ready to plot.**\n\n**Always a single series, never a breakdown.** Unlike the count endpoint, this reports one business: whichever the request resolves to. A master account gets the master's own figures, and a master sending `x-active-business-id` gets that sub-business. To chart several, call it once per business.\n\nTwo query parameters shape it:\n\n| Parameter | Default | Does |\n| --- | --- | --- |\n| `days` | `7` | How many days back, inclusive of today |\n| `reference` | `CREATION` | Count by when a pass was **created**, or by `DOWNLOADS` for when it was added to a wallet |\n\n**`reference` changes what a day's row is counting**, and the two are not interchangeable. `CREATION` follows the passes created that day and reports how many of that cohort have been downloaded. `DOWNLOADS` counts the downloads that happened that day, whichever day those passes were created.\n\nOn a day with 20 passes created and 12 of them downloaded, plus 10 older passes downloaded the same day, `CREATION` reports 20 and 12 while `DOWNLOADS` reports 22. [The section above](#tag/business-analytics) works through it.\n\nThe series also carries the running total from the day before the window starts, so a chart has somewhere to begin rather than jumping from zero.\n\nFor the summed figures instead of the curve, use the count endpoint above.","tags":["Business Analytics"],"security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/passCountsTrendsDuration"},{"$ref":"#/components/parameters/passDownloadsTrendReference"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"responses":{"200":{"description":"Successfully retrieved pass download trend","content":{"application/json":{"schema":{"type":"object","properties":{"trends":{"type":"array","items":{"$ref":"#/components/schemas/PassTrendDetails"}},"totalSummary":{"$ref":"#/components/schemas/PassCountDetails"},"initialData":{"$ref":"#/components/schemas/PassTrendDetails"}},"description":"Pass download trends"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/cards/upload/presigned-url":{"post":{"operationId":"getPresignedUploadUrl_v1","summary":"Get a bulk upload URL","description":"**Step one of a bulk upload.** Returns an `uploadUrl` and the `groupId` that everything afterwards is keyed on.\n\n`PUT` your CSV straight to that URL. Nothing else is needed: the upload itself starts the work, and the passes are created after the `PUT` has finished rather than while you wait.\n\n```\nPOST /api/v1/cards/upload/presigned-url    -> { groupId, uploadUrl, status }\nPUT  <uploadUrl>                           -> processing begins\nGET  /api/v1/cards/upload/groups/{groupId} -> row-by-row results\n```\n\n**Everything about the file is decided here, not in the file.** `headerMappings` is required, and so are `templateId` and `fileName`; the flags default as listed in [the section above](#tag/pass-uploads), which also covers how to derive the mappings and which keys you are allowed to use.\n\nThe URL is signed and expires, so ask for it when you are ready to upload rather than ahead of time. Requesting one creates the group immediately, so a `groupId` you never upload against simply sits there with nothing in it.\n\nReturns `400 INVALID_TEMPLATE_ID` if the template does not exist or belongs to another business.","tags":["Pass Uploads"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProcessCardUploadGroupAndPresignedUrlRequestBody"}}}},"responses":{"200":{"description":"Presigned upload URL generated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProcessCardUploadGroupAndPresignedUrlResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}},"parameters":[{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}]}},"/api/v1/scheduled-jobs":{"post":{"operationId":"registerScheduledJob_v1","tags":["Scheduler"],"summary":"Register a scheduled job","security":[{"BearerAuth":[]}],"description":"Creates the recurring configuration and arms it. Polling is one job type a scheduled job can run; the sweep dispatches whatever is due.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterScheduledJobRequestBody"}}}},"responses":{"201":{"description":"Scheduled job registered and armed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScheduledJobResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}},"parameters":[{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}]}},"/api/v1/scheduled-jobs/{templateId}/{providerName}/{type}/stop":{"post":{"operationId":"stopScheduledJob_v1","tags":["Scheduler"],"summary":"Stop a scheduled job so its next run does not happen","security":[{"BearerAuth":[]}],"description":"Pauses the job. A run already in flight is left to finish.","parameters":[{"name":"templateId","in":"path","required":true,"schema":{"type":"string"}},{"name":"providerName","in":"path","required":true,"schema":{"type":"string","enum":["TALONONE"]}},{"name":"type","in":"path","required":true,"schema":{"type":"string","enum":["EXPORT_CUSTOMER_BALANCES"]}},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"responses":{"200":{"description":"Scheduled job stopped","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScheduledJobResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/cards/upload/groups":{"get":{"operationId":"listCardsUploadGroups_v1","summary":"List upload groups","description":"**Every bulk upload for the business, newest first.** One row per upload, for a \"recent imports\" screen or to find a `groupId` you have lost.\n\n`status` is the first thing to read:\n\n| Status | Means |\n| --- | --- |\n| `PENDING` | The group exists; the file has not arrived or not been read yet |\n| `VALIDATION_COMPLETE` | The file parsed; rows are being classified and created |\n| `QUEUED`, `IN_PROGRESS` | Working through the rows |\n| `COMPLETED` | Finished. Read `count` for what happened |\n| `PARSING_ERROR` | The file could not be read as a CSV. Nothing was processed |\n| `RECORD_COUNT_EXCEEDED` | Over the 500,000-row limit. **Nothing was processed** |\n| `FAILED` | Processing broke |\n\n**`count` adds up two ways**, and both are worth checking:\n\n```\nvalid + invalid = total\ncreated + updated + failed + skipped = valid\n```\n\n`invalid` is rows that failed validation before anything was attempted. `skipped` is rows whose owner already had a pass while `overrideExisting` was false, which is expected rather than wrong.\n\n`type` separates a `FILE_UPLOAD` you started from a `TEMPLATE_CARD_SYNC`, which is the sweep that runs when a template is updated with `updateExistingCards`. Both use these groups, so filter if you only want your own imports.\n\nFor which rows in particular, use `GET /api/v1/cards/upload/groups/{cardUploadGroupId}`.\n\n`limit` is capped at 100 per page, whatever you ask for. Each row carries presigned links to its results files, and an upload large enough to be loaded in rounds has one per round, so the work of a page grows with both the number of uploads and their size. Page with `next` rather than asking for a larger `limit`.","parameters":[{"$ref":"#/components/parameters/limitPagination"},{"$ref":"#/components/parameters/nextPagination"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"tags":["Pass Uploads"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"List of card upload groups","content":{"application/json":{"schema":{"type":"object","properties":{"groups":{"type":"array","items":{"$ref":"#/components/schemas/CardUploadGroupV1"}},"next":{"type":"string","description":"Key used to next set of results"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/cards/upload/groups/{cardUploadGroupId}":{"get":{"operationId":"listCardUploads_v1","summary":"List rows in an upload group","description":"**One entry per line of your CSV.** This is what makes a bulk upload worth using over a loop: a partial failure names the rows, so you fix those and re-send rather than reconciling the whole file by hand.\n\nEach entry carries `recordIndex`, the row's position in your file, its `status`, the `cardPayload` that was built from it, and `errors` when there are any.\n\n| Row status | Means |\n| --- | --- |\n| `CREATED` | A new pass was issued |\n| `UPDATED` | An existing pass was changed, which needs `overrideExisting: true` |\n| `SKIPPED` | The owner already had a pass and `overrideExisting` was false |\n| `VALIDATION_FAILED` | The row was rejected before anything was attempted. `errors` says why |\n| `FAILED` | Validation passed but creating the pass did not |\n| `QUEUED`, `PROCESSING` | Not finished yet |\n\n**`errors` is per row and human-readable**, along the lines of \"abc is not a valid email\" or \"Ana is required\", so you can hand it back to whoever produced the file.\n\nThe response also carries the parent `group`, so a single call gives you both the totals and the detail. Results are paginated; follow `next` until it stops coming back.","tags":["Pass Uploads"],"parameters":[{"$ref":"#/components/parameters/cardUploadGroupId"},{"$ref":"#/components/parameters/limitPagination"},{"$ref":"#/components/parameters/nextPagination"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"List of card uploads","content":{"application/json":{"schema":{"type":"object","properties":{"uploads":{"type":"array","items":{"$ref":"#/components/schemas/CardUploadV1"}},"group":{"$ref":"#/components/schemas/CardUploadGroupV1"},"next":{"type":"string","description":"Key used to next set of results"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/cards/upload/groups/{cardUploadGroupId}/retry":{"post":{"operationId":"retryCardUpload_v1","summary":"Re-run the rows an upload could not land","description":"**Re-runs only the rows that failed**, reading the failure export the upload wrote per batch. It never re-runs rows that already landed, and it does not change how the upload treats existing passes: `overrideExisting` stays whatever the upload was created with.\n\nRows that failed the template's own validation are **left out by default**, because they fail the same way every time. Set `includeValidationFailed` when the template has since been fixed and you want them tried again.\n\nA retry is allowed only when there is something to retry, and only so many times:\n\n| Condition | Result |\n| --- | --- |\n| The group is not a `FILE_UPLOAD` (a `TEMPLATE_CARD_SYNC`) | `400 RETRY_NOT_SUPPORTED` |\n| `status` is already `RETRY_PENDING` or `RETRY_IN_PROGRESS` | `200`, nothing started, current state returned |\n| `status` is anything other than `COMPLETED`, `FAILED` or `RETRY_COMPLETED` | `409 UPLOAD_IN_PROGRESS`, the upload's own run has not finished |\n| Created by an integration that retries its own failed rows (Talon.One) | `400 RETRY_OWNED_BY_PRODUCER` |\n| `count.failed` is 0 (and `count.invalid` is 0 when including validation failures) | `400 NOTHING_TO_RETRY` |\n| `retryCount` has reached the limit | `400 RETRY_LIMIT_REACHED` |\n| No recorded batches: an older multi-file upload, or a run that broke before its first round was saved | `400 RETRY_NOT_SUPPORTED` |\n\nProgress is on the group's `status` and `retryCount`, and each attempt appends to `retryDetails` with what it retried and what landed. The results links are rewritten in place, so after a retry they show every row's latest outcome. As retried rows land, the group's `count.failed` comes down.","security":[{"BearerAuth":[]}],"tags":["Card Uploads"],"parameters":[{"in":"path","name":"cardUploadGroupId","required":true,"schema":{"type":"string"}},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"includeValidationFailed":{"type":"boolean","default":false,"description":"Re-run rows that failed the template's validation as well. Use after fixing the template."}}}}}},"responses":{"200":{"description":"Retry accepted, or already running","content":{"application/json":{"schema":{"type":"object","properties":{"groupId":{"type":"string"},"status":{"type":"string","example":"RETRY_PENDING"},"retryCount":{"type":"number"},"started":{"type":"boolean","description":"False when a retry was already in flight and this request changed nothing."}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/external/callback":{"post":{"operationId":"externalCallBack_v1","summary":"Receive a provider callback (legacy)","description":"**The older shape, kept for integrations that predate the provider-scoped route.** It carries no provider, business or template in the path, so it cannot be aimed at a particular template, and the handler cannot tell from the URL which payload format to expect.\n\nNew work should use `POST /api/v1/external/callback/{provider}/{businessIdPath}/{templateId}`. That is the route [registering a subscription](#tag/subscribe-to-an-external-system) creates for you, and the one the dashboard shows for the inbound listener.\n\nAuthentication is the same: an API key per business, form and provider, checked before anything is read.","tags":["Process Events from an External System"],"deprecated":true,"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExternalCallbackV1"}}},"required":true},"responses":{"200":{"description":"Callback received successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","description":"Callback received successfully","example":true}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/external/callback/{provider}/{businessIdPath}/{templateId}":{"post":{"operationId":"externalCallBack_v2","summary":"Receive a provider callback","description":"**Where an external system posts its events.** The path carries the routing: `{provider}` picks the handler that understands the payload, and `{businessId}` and `{templateId}` decide which pass is issued and from what.\n\nPercent-encode each path segment. A business id containing `#` has to arrive as `%23`.\n\n[The section above](#tag/process-events-from-an-external-system) lists the providers and covers the inbound listener, which is the one you configure yourself.\n\n### Authentication\n\n**Every provider needs an API key**, and it is per business, per form and per provider. Litecard compares what arrives against the key stored for that combination.\n\nMost send it as an `x-api-key` header. A few that can only be configured with a plain callback URL read it from a query parameter instead, which is why the parameter is documented on this operation.\n\n| Response | Cause |\n| --- | --- |\n| `401 MISSING_API_KEY` | No key on the request |\n| `401 INVALID_API_KEY` | Key does not match, or no integration is configured for that business and form |\n\n**Providers that sign their deliveries are verified as well.** Where a provider offers a signature, Litecard recomputes it over the raw body with the shared secret and compares it in constant time before reading anything. A delivery that fails is rejected and never queued, whatever the API key said. Some also bind a timestamp into the signature, so a replayed body fails even with the right digest.\n\nSignature checking is provider-specific and needs no configuration from you; it uses the secret established when the integration was set up.\n\n### After it is accepted\n\nEvents are deduplicated on `templateId#externalId`, so a vendor retrying a delivery will not issue a second pass to the same person.\n\n**The status code you get back depends on the provider.** Klaviyo and Yotpo receive real HTTP codes, so their own redelivery machinery can fire:\n\n| Code | Meaning |\n| --- | --- |\n| `400` | Payload rejected, for example a required template field missing or blank on the profile |\n| `401` | Key missing or invalid |\n| `404` | Integration not connected |\n| `500` | Transient fault, safe to retry |\n\nEvery other provider is answered with `200` whatever happens, and the failure is reported in the body as `{ \"success\": false, \"message\": \"...\" }`.","parameters":[{"name":"x-timestamp","in":"header","description":"Timestamp of when the callback is sent, in ISO 8601 format","schema":{"type":"string"}},{"name":"x-signature","in":"header","description":"HMAC signature of the request body using the shared secret, used for verifying authenticity","schema":{"type":"string"}},{"name":"x-client-id","in":"header","description":"Client ID of the caller, used for identifying the source","schema":{"type":"string"}},{"name":"x-api-key","in":"query","description":"API key for providers that can only be configured with a plain callback URL. Other key-authenticated providers send it as the x-api-key header.","schema":{"type":"string"}},{"$ref":"#/components/parameters/provider"},{"$ref":"#/components/parameters/businessIdPath"},{"$ref":"#/components/parameters/templateId"}],"tags":["Process Events from an External System"],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExternalCallbackV1"}}},"required":true},"responses":{"200":{"description":"Callback received successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","description":"Callback received successfully","example":true}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/form/{formId}/design":{"put":{"operationId":"customFormDesignDefinition_v1","summary":"Design a signup form","description":"Brand a signup form: the page where a customer enters their details to receive a pass.","tags":["Design"],"parameters":[{"$ref":"#/components/parameters/formId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomFormDesignSchema"}}},"required":true},"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Successful update of custom form design","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Form"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/templates/{templateId}/download/design":{"post":{"operationId":"designTemplateDownloadPage_v1","summary":"Design a template download page","description":"Brand the download page owners see for one template's passes.","tags":["Design"],"parameters":[{"$ref":"#/components/parameters/templateId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UIPageSchema"}}},"required":true},"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Successful design of download page","content":{"application/json":{"schema":{"type":"object","properties":{"templateId":{"type":"string","description":"The ID of the template"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/order-templates/{orderTemplateId}/download/design":{"post":{"operationId":"designOrderTemplateDownloadPage_v1","summary":"Design an order download page","description":"Brand the download page for an order template's passes.","tags":["Design"],"parameters":[{"$ref":"#/components/parameters/orderTemplateId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UIPageSchema"}}},"required":true},"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Successful design of order template download page","content":{"application/json":{"schema":{"type":"object","properties":{"orderTemplateId":{"type":"string","description":"The ID of the order template"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/businesses/{businessIdPath}/download/design":{"post":{"operationId":"designBusinessDownloadPage_v1","summary":"Design a business download page","description":"Set the business-wide default download page design, inherited where a template has none.","tags":["Design"],"parameters":[{"$ref":"#/components/parameters/businessIdPath"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UIPageSchema"}}},"required":true},"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Successful design of business download page","content":{"application/json":{"schema":{"type":"object","properties":{"businessId":{"type":"string","description":"The ID of the business"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/voucher/codes/upload/csv":{"post":{"operationId":"voucherCodesCSVUpload_v1","summary":"Upload voucher codes","description":"**Post a CSV and Litecard loads it.** The direct path, for files small enough to fit in a request body. For anything large, ask for an upload URL instead.\n\n`type` picks which of the three lists you are uploading, and defaults to `VOUCHER`. The header row has to match it, and [the section above](#tag/voucher-codes) shows all three side by side:\n\n| `type` | CSV header | Loads |\n| --- | --- | --- |\n| `VOUCHER` | `code` | a pool of codes |\n| `EMAIL` | `email` | a list of addresses allowed to sign up |\n| `EMAIL_MEMBER` | `email,voucherCode` | one reserved code per address |\n\n**Returns a `jobId`, not a result.** Loading runs afterwards, so a `200` here only means the file was accepted for processing. Watch the job with `GET /api/v1/voucher/codes/upload/groups/{templateId}` before issuing passes against it, and expect the header row to be judged there rather than here.\n\n**Re-uploading is safe and does nothing.** A row whose code or address already exists for the template is counted as a duplicate and discarded, used or not.\n\n### When it fails\n\n| Response | Cause |\n| --- | --- |\n| `400 INVALID_TEMPLATE_ID` | No such template, not yours, or its `settings.externalProvider` is not `LITECARD` |\n| `400 KEY_REQUIRED` | The template has more than one voucher field, so `key` has to name one |\n| `400 INVALID_KEY` | `key` names no voucher field on the template; the message lists the ones that exist |\n| `400 INVALID_TYPE_FOR_KEY` | `key` is `email` but `type` is not `EMAIL` or `EMAIL_MEMBER` |\n| `400 MISSING_EMAIL_FIELD` | `key` is `email` but no `email` field is mapped `VALID_EMAIL` |","tags":["Voucher Codes"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"file":{"type":"string","example":"data:text/csv;base64,YXNkLHdlZHcsd2VkZQoxLDIsMwo="},"type":{"type":"string","example":"VOUCHER","enum":["VOUCHER","EMAIL","EMAIL_MEMBER"]},"templateId":{"type":"string","example":"template-1"},"key":{"type":"string","description":"Optional. The dataField key this voucher set belongs to. Required when template has multiple VOUCHER_CODE fields.","example":"promoCode"}},"required":["file","templateId"]}}}},"responses":{"200":{"description":"CSV file uploaded successfully","content":{"application/json":{"schema":{"type":"object","properties":{"jobId":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}},"parameters":[{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}]}},"/api/v1/voucher/codes/upload/presigned":{"post":{"operationId":"voucherCodesPresignedUpload_v1","summary":"Get a voucher upload URL","description":"**Upload the CSV straight to storage**, past the request-size limit. The right path for a pool of any real size.\n\nTwo steps: call this, then `PUT` your CSV to the `uploadUrl` you get back with `Content-Type: text/csv`. Loading starts on its own once the file lands.\n\n```\nPOST /api/v1/voucher/codes/upload/presigned   -> { uploadUrl, jobId, key, expiry }\nPUT  <uploadUrl>   (Content-Type: text/csv)   -> loading begins\n```\n\n**Upload to the returned `key` and nothing else.** The type is encoded in that object name, which is how the loader knows whether to expect `code`, `email`, or `email,voucherCode`. Rename it and the file is read as the wrong shape.\n\n`expiry` is when the URL stops working, not when the upload has to finish. The same `type`, `key` and validation rules as the direct upload apply, and [the section above](#tag/voucher-codes) covers what each type expects.\n\nTrack the returned `jobId` with `GET /api/v1/voucher/codes/upload/groups/{templateId}`. Nothing tells you the file was well-formed until then.","tags":["Voucher Codes"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","example":"VOUCHER","enum":["VOUCHER","EMAIL","EMAIL_MEMBER"]},"templateId":{"type":"string","example":"template-1"},"key":{"type":"string","description":"Optional. The dataField key this voucher set belongs to. Required when template has multiple VOUCHER_CODE fields.","example":"promoCode"}},"required":["templateId"]}}}},"responses":{"200":{"description":"Presigned URL generated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"uploadUrl":{"type":"string","description":"Presigned URL for uploading CSV file"},"jobId":{"type":"string","description":"Unique job ID for tracking the upload"},"key":{"type":"string","description":"S3 key where file will be uploaded"},"expiry":{"type":"string","description":"ISO timestamp when the presigned URL expires"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}},"parameters":[{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}]}},"/api/v1/voucher/codes/upload/groups/{templateId}":{"get":{"operationId":"listVoucherUploadGroups_v1","summary":"List voucher upload groups","description":"**Every upload for a template, and what became of it.** This is where an upload is judged, so check it before issuing passes against a batch.\n\n`status` moves `WAITING` -> `IN_PROGRESS` -> `COMPLETED`. Two are dead ends: **`INVALID_UPLOAD` means the header row did not match the `type`**, and no row was written at all; `FAILED` means loading broke.\n\nThe counts on a `COMPLETED` job explain themselves, and they should add up to `totalCount`:\n\n| Count | Means |\n| --- | --- |\n| `successCount` | Loaded and available |\n| `duplicateCount` | That code or address already existed, so the row was discarded |\n| `invalidCount` | Written, but unusable: an empty code, or an address that is not an email |\n| `failedCount` | The write itself errored |\n\nA large `duplicateCount` on a file you expected to be new usually means the same CSV was uploaded twice. That is harmless: nothing was overwritten.","tags":["Voucher Codes"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"templateId","in":"path","required":true,"schema":{"type":"string"},"description":"The template ID to list voucher upload groups for"},{"$ref":"#/components/parameters/limitPagination"},{"$ref":"#/components/parameters/nextPagination"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"responses":{"200":{"description":"List of voucher upload groups","content":{"application/json":{"schema":{"type":"object","properties":{"groups":{"type":"array","items":{"$ref":"#/components/schemas/VoucherUploadGroupV1"}},"next":{"type":"string","description":"Key used to fetch next set of results"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/voucher/codes/counts/{templateId}":{"get":{"operationId":"getVoucherCounts_v1","summary":"Get remaining voucher counts","description":"**How many are left.** Monitor it: an empty pool fails pass creation with `404 NO_VOUCHER_CODE`, and the first sign is usually a customer complaining.\n\nOne row per voucher field, so a template with several pools reports each separately. `fieldKey` is `default` where the template has a single set.\n\n`remainingCount` is `totalCount` minus `usedCount`. It counts entries never handed out, so it never goes back up: a used entry cannot be returned to the pool, and re-uploading it is discarded as a duplicate. The only way the number rises is a fresh upload of codes that have never been seen.\n\nWorth an alert in your own system at a low-water mark, sized on how fast you issue.","tags":["Voucher Codes"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"templateId","in":"path","required":true,"schema":{"type":"string"},"description":"The template ID to get voucher counts for"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"responses":{"200":{"description":"Voucher counts per field key","content":{"application/json":{"schema":{"type":"object","properties":{"counts":{"type":"array","items":{"$ref":"#/components/schemas/VoucherCountV1"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/webhook/external":{"post":{"operationId":"register_webhook","summary":"Register a webhook","description":"**Subscribe an external system to Litecard's pass events.** One call, and that system starts receiving `CARD_CREATE`, `CARD_UPDATE`, `CARD_DOWNLOAD` and `CARD_REMOVE` as they happen.\n\n[The section above](#tag/outbound-webhooks) carries worked bodies for the three cases: a plain webhook with the key in a header, a provider that needs the key in the URL with `authType: NONE`, and Talon.One.\n\n### When it fails\n\n| Response | Cause |\n| --- | --- |\n| `400 INVALID_AUTH_TYPE` | `TALONONE` without `API_KEY` and a key |\n| `400 INVALID_EVENT_NAME_MAPPING` | A mapping that misses a subscribed event, names one you did not subscribe to, or maps `REGISTER_WEBHOOK` |\n| `400` on the template check | A `templateIds` entry that does not exist or is not yours |\n\nThat last one is deliberate: an unknown id would register cleanly and then never fire, with nothing to show why.","tags":["Outbound Webhooks"],"parameters":[{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookRegistrationRequestBody"}}},"required":true},"security":[{"BearerAuth":[]}],"responses":{"201":{"description":"User has successfully registered a webhook","content":{"application/json":{"schema":{"properties":{"success":{"type":"boolean"},"locationId":{"type":"string","description":"ID of the newly created location","example":"location-123"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"delete":{"operationId":"delete_Webhook","summary":"Delete a webhook","description":"Remove a webhook subscription. Deliveries stop immediately.","tags":["Outbound Webhooks"],"parameters":[{"$ref":"#/components/parameters/webhookId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Webhook Deleted","content":{"application/json":{"schema":{"properties":{"success":{"type":"boolean"},"webhookId":{"type":"string","description":"ID generated for the webhook for future usee"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"get":{"operationId":"listWebhooks","summary":"List webhooks","description":"List your registered webhook subscriptions and the events each one receives.","tags":["Outbound Webhooks"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Registered webhooks for the business","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListWebhooksResponse"}}}}},"parameters":[{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}]}},"/api/v1/inbound-webhooks":{"post":{"operationId":"register_inbound_webhook","summary":"Register an inbound webhook","description":"**Creates the subscriptions inside the provider, not just a record here.** One call, and Shopify starts calling Litecard whenever a customer changes.\n\nThere is no URL to supply. Litecard builds the callback from `provider`, your business and `templateId`, registers it through the provider's own API, and returns what it created.\n\n```json\n{\n  \"provider\": \"SHOPIFY\",\n  \"templateId\": \"tmpl_loyalty\",\n  \"topics\": [\"CUSTOMER_CREATE\", \"CUSTOMER_UPDATE\"],\n  \"config\": { \"storeName\": \"acme-store\", \"metafieldNamespaces\": [\"loyalty\"] }\n}\n```\n\nOmit `topics` to subscribe to everything the provider supports. The response carries one `subscriptionId` per topic, assigned by the provider, which is what `DELETE` takes.\n\n**Set `metafieldNamespaces` now if any mapped field comes from a metafield.** It cannot be changed later; [the section above](#tag/subscribe-to-an-external-system) explains why.\n\n### When it fails\n\n| Response | Cause |\n| --- | --- |\n| `404 SHOPIFY_STORE_NOT_FOUND` | The app is not installed on that store, so there is no token to register with |\n| `400 MISSING_STORE_NAME` | No `config.storeName`, and none on the form's `shopifySettings` |\n| `400 INVALID_METAFIELD_NAMESPACES` | Not an array of non-empty strings |\n| `400` on topics | A topic the provider does not support |\n\nRegistering the same template again adds subscriptions rather than replacing them. Delete the old ones first. To have Litecard reconcile instead, save `inboundWebhooks` on the form's Shopify integration config: it keeps matching subscriptions, creates missing ones and deletes the rest.","tags":["Subscribe to an External System"],"parameters":[{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterInboundWebhookRequestBody"}}},"required":true},"security":[{"BearerAuth":[]}],"responses":{"201":{"description":"Inbound webhook subscriptions registered","content":{"application/json":{"schema":{"properties":{"success":{"type":"boolean"},"subscriptions":{"type":"array","items":{"$ref":"#/components/schemas/ExternalSubscription"},"description":"The registered subscriptions"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"delete":{"operationId":"delete_inbound_webhook","summary":"Delete an inbound webhook","description":"**Removes the subscription from the provider as well as from Litecard**, so deliveries stop at the source rather than arriving and being ignored.\n\nTakes the `subscriptionId` from the register response or the listing, plus the `templateId` it belongs to.\n\nThis is also how you change `metafieldNamespaces`: delete the subscription, then register again with the namespaces you want.\n\nNothing already issued is affected. Passes created from earlier events stay as they are and simply stop tracking that customer.","tags":["Subscribe to an External System"],"parameters":[{"$ref":"#/components/parameters/templateIdQuery"},{"$ref":"#/components/parameters/subscriptionId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Inbound webhook subscription deleted","content":{"application/json":{"schema":{"properties":{"success":{"type":"boolean"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"get":{"operationId":"list_inbound_webhooks","summary":"List inbound webhooks","description":"**What is registered, and the provider-side id behind each.** One entry per topic, so a template subscribed to create and update shows two.\n\n`subscriptionId` is the provider's own identifier, such as `gid://shopify/WebhookSubscription/123456789`. It is what `DELETE` takes, and what to quote when checking the subscription in the provider's admin.\n\nPass `templateId` to narrow it to one template.\n\n**It reports what Litecard registered, not what the provider still holds.** A subscription deleted inside Shopify's own admin still appears here and stops delivering, so if events go quiet with entries listed, check the provider first.","tags":["Subscribe to an External System"],"parameters":[{"$ref":"#/components/parameters/templateIdQuery"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Registered inbound webhook subscriptions","content":{"application/json":{"schema":{"properties":{"subscriptions":{"type":"array","items":{"$ref":"#/components/schemas/ExternalSubscription"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/users":{"get":{"operationId":"listUsers","tags":["User Management"],"summary":"List users","description":"**Everyone who can log in to this business.** Staff, not pass owners; for those see [Pass Owner](#tag/pass-owner).\n\nEach entry carries the user's `userGroupId`, which is where their permissions live. To turn that into a list of what they can do, read the group in [User Groups](#tag/user-groups).\n\n**A user with no `userGroupId` has not finished being set up.** Treat it as incomplete rather than as restricted access.\n\nOn a master account this lists the master's own users. To see a sub-business's users, act as that business with `x-active-business-id`.","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"List of users","content":{"application/json":{"schema":{"type":"object","properties":{"users":{"type":"array","items":{"$ref":"#/components/schemas/ManagedUser"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}},"parameters":[{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}]},"post":{"operationId":"createUser","summary":"Create a user","description":"**Adds a user and puts them in a group in one call.** `userGroupId` is required, because permissions belong to the group rather than the user; see [User Groups](#tag/user-groups).\n\n```json\n{ \"email\": \"ana@acme.com\", \"userGroupId\": \"grp_7Kq2\", \"sendInvitation\": true }\n```\n\n`sendInvitation` emails the person a link to set their own password. Supply a `password` instead to create the account with one already set, which suits provisioning from your own system.\n\n**Up to 20 users per business.** The limit is per business, so each sub-business has its own allowance rather than sharing the master's.\n\nThe group has to exist already, and it has to belong to this business.","tags":["User Management"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserRequestBody"}}}},"responses":{"201":{"description":"User created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"userId":{"type":"string"},"email":{"type":"string"},"userGroupId":{"type":"string"},"invitationSent":{"type":"boolean"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"$ref":"#/components/responses/Conflict"},"500":{"$ref":"#/components/responses/InternalServerError"}},"parameters":[{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}]}},"/api/v1/users/{userId}":{"put":{"operationId":"updateUser","summary":"Update a user's group","description":"**Moves a user into a different group**, which is how one person's access is changed. There is nothing else on a user to edit for permissions.\n\nUse this when someone changes job. Use `PUT` on the group instead when the job itself should be able to do more, since that reaches everyone in it.\n\nThe change is immediate, taking effect on their next request rather than at their next sign-in.","tags":["User Management"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"},"description":"user_id in Auth0 user database"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserRequestBody"}}}},"responses":{"200":{"description":"User updated","content":{"application/json":{"schema":{"type":"object","properties":{"userId":{"type":"string"},"userGroupId":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"delete":{"operationId":"deleteUser","summary":"Delete a user","description":"**Removes the person's access entirely.** They can no longer sign in, and they leave the listing.\n\nThis does not touch the group they were in or anyone else in it, and it frees one of the business's 20 user slots.\n\nNothing they created is affected. Templates, passes and notifications carry no per-user ownership, so removing someone leaves the business's data as it was.","tags":["User Management"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"},"description":"user_id in Auth0 user database"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"responses":{"200":{"description":"User deleted","content":{"application/json":{"schema":{"type":"object","properties":{"userId":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/user-groups":{"get":{"operationId":"getUserGroups","summary":"List user groups","description":"**Every group this business has**, with the roles inside each.\n\nThis is the lookup behind a user listing: a user carries a `userGroupId`, and this turns it into what they can actually do.\n\nIt also answers the reverse question, which is usually the harder one: to find everyone holding a given role, find the groups carrying it, then the users pointing at those groups.\n\nGroups belong to one business. A master account sees its own; act as a sub-business with `x-active-business-id` to see that business's groups.","tags":["User Groups"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"List of user groups","content":{"application/json":{"schema":{"type":"object","properties":{"userGroups":{"type":"array","items":{"$ref":"#/components/schemas/UserGroup"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}},"parameters":[{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}]},"post":{"operationId":"createUserGroup","summary":"Create a user group","description":"**Defines a job, which users are then put into.** Name it after the role a person performs, \"Store Managers\", \"Read only\", rather than after the permissions inside it.\n\n```json\n{ \"name\": \"Store Managers\", \"roles\": [\"Business\", \"User Admin\"] }\n```\n\n**Create a second group rather than an exception.** Two people who need different access are two groups; that is the model working as intended, and it is what keeps a permission question answerable later.\n\nA new group has no members. Add people with `POST /api/v1/users`, or move them with `PUT /api/v1/users/{userId}`.","tags":["User Groups"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserGroupRequestBody"}}}},"responses":{"201":{"description":"User group created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserGroup"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}},"parameters":[{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}]}},"/api/v1/user-groups/{userGroupId}":{"put":{"operationId":"updateUserGroup","summary":"Update a user group","description":"**Changes what the job can do, for everybody in it at once.**\n\nThat reach is the reason to check the membership first. Adding `Admin` to a group of eight grants it to all eight, immediately, with nothing to confirm and no undo except another edit.\n\n**To change one person, move them instead**, with `PUT /api/v1/users/{userId}`. Editing the group is for when the job itself has changed.\n\nRenaming a group affects nobody's access. Only `roles` does that.","tags":["User Groups"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"userGroupId","in":"path","required":true,"schema":{"type":"string"},"description":"User group ID"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserGroupRequestBody"}}}},"responses":{"200":{"description":"User group updated","content":{"application/json":{"schema":{"type":"object","properties":{"userGroup":{"$ref":"#/components/schemas/UserGroup"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/template/{templateId}/downloads/count":{"get":{"operationId":"templatePassDownloadsCount_v1","summary":"Get totals for a period","description":"**One set of totals for a period.** Every counter summed into a single `passCounts` object, with no dates in the response. The shape you want behind a dashboard tile.\n\n`duration` is required: `PAST_7_DAYS`, `PAST_30_DAYS`, `PAST_90_DAYS` or `ALL`.\n\n**`ALL` is not just a longer window, and it changes the response.** It reads the running totals kept on the template record, so you get lifetime figures but only four counters: `activeCardsCount`, `downloadsCount`, `downloadedApplePassCount` and `downloadedGooglePassCount`.\n\nThe dated durations sum the daily records instead and return eleven, adding removals, inactive passes and `paymentsRequiredCount`. They also report `cardsCount` where `ALL` reports `activeCardsCount`, so check which key you are reading before switching between the two.\n\nThe template must belong to your active business; another business's id returns `403`.\n\nWant the same numbers plotted over time? Use `GET /api/v1/template/{templateId}/downloads/trend`.","tags":["Pass Templates Analytics"],"security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/passCountsDuration"},{"$ref":"#/components/parameters/templateId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"responses":{"200":{"description":"Successfully retrieved pass downloads count","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PassCountsForTemplate"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/template/{templateId}/downloads/trend":{"get":{"operationId":"templatePassDownloadTrend_v1","summary":"Get day-by-day totals","description":"**The same counters, one row per day.** Returns `trends`, an array with a `date` on every entry, sorted oldest first and ready to plot.\n\n`days` is required and accepts 1 to 100. Two extras come back with it:\n\n- `totalSummary`, the template's lifetime totals, so you can show a headline figure beside the chart without a second call.\n- `initialData`, a snapshot of the day *before* your window. That is the chart's starting point, so a cumulative line does not begin at zero.\n\n### reference: what a day's row is counting\n\n**The two options answer different questions about the same day**, and swapping one for the other changes the numbers rather than the presentation.\n\n[Pass Templates Analytics](#tag/pass-templates-analytics) draws it. Say 12 June saw **20 passes created**, 12 of them downloaded, while **10 passes created on earlier days** were also downloaded that day.\n\n| `reference` | The row for 12 June | Follows |\n| --- | --- | --- |\n| `CREATION` (default) | 20 created, 12 of those downloaded | the **cohort** created that day |\n| `DOWNLOADS` | 22 downloads | the **downloads** that happened that day |\n\n**`CREATION` keeps a pass with the day it was issued**, however long the download takes to arrive. If the remaining 8 are downloaded next week, they still count towards 12 June, which is what makes it the view for judging a send.\n\n**`DOWNLOADS` does not care when a pass was created.** It counts downloads as they happen, so it answers how busy a day was rather than how a batch performed.\n\nJust need a total? `GET /api/v1/template/{templateId}/downloads/count` is cheaper to consume.","tags":["Pass Templates Analytics"],"security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/passCountsTrendsDuration"},{"$ref":"#/components/parameters/passDownloadsTrendReference"},{"$ref":"#/components/parameters/templateId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"responses":{"200":{"description":"Successfully retrieved pass download trend","content":{"application/json":{"schema":{"type":"object","properties":{"trends":{"type":"array","items":{"$ref":"#/components/schemas/PassTrendDetails"}},"totalSummary":{"$ref":"#/components/schemas/PassCountDetailsForTemplate"},"initialData":{"$ref":"#/components/schemas/PassTrendDetails"}},"description":"Pass download trends for the template"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/locations":{"get":{"operationId":"listLocations","summary":"List locations","description":"List registered locations for the business, with their coordinates, `externalId` and default lock-screen wording.\n\nWhat those defaults mean is covered on [Register a location](#tag/location-based-notifications/POST/api/v1/locations).","tags":["Location Based Notifications"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"List of Locations","content":{"application/json":{"schema":{"type":"object","properties":{"locations":{"type":"array","items":{"$ref":"#/components/schemas/Location"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}},"parameters":[{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}]},"post":{"operationId":"add_location","summary":"Register a location","description":"**Register a physical place**, so passes can surface on the lock screen when an owner is near it.\n\nRegistering does nothing on its own. Attach the location to a template for every pass, or to a single pass through `templateOverrides.locations`, which is how one template serves an entire franchise.\n\n### The two fields worth setting now\n\n**`externalId` is the identifier your own systems already use**, the one on your POS or store system. It **cannot be added later**, so set it here or never. With it you can update the place and attach it to a pass without ever storing a Litecard id.\n\n**`notificationSettings.apple.lockScreenMessage` is a default, and it is optional.** A location entry on a template or pass carries its own `apple.lockScreenMessage`, and that always wins. The one stored here is used only when the entry leaves it out, so the same store can read one way on a loyalty pass and another on a coupon.\n\n### Together they make `{ \"externalId\": \"POS-042\" }` a complete entry\n\nRegister the place once:\n\n```json\n{\n  \"locationName\": \"Bondi store\",\n  \"externalId\": \"POS-042\",\n  \"lat\": \"-33.8908\",\n  \"lon\": \"151.2743\",\n  \"notificationSettings\": { \"apple\": { \"lockScreenMessage\": \"Your pass works here\" } }\n}\n```\n\nThen `template.locations`, or `templateOverrides.locations` on a single pass, needs nothing but the name:\n\n```json\n\"locations\": [{ \"externalId\": \"POS-042\" }]\n```\n\nCoordinates **and** wording both come from the record. Add `apple` to the entry only when that template or that one pass should say something different:\n\n```json\n\"locations\": [\n  { \"externalId\": \"POS-042\", \"apple\": { \"lockScreenMessage\": \"Bondi: your 10th coffee is free\" } }\n]\n```\n\n**Leave the default empty and that short entry resolves to coordinates with no wording**, so the geofence still fires and Apple has nothing to put on the lock screen. Nothing warns you.","tags":["Location Based Notifications"],"parameters":[{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddLocationRequestBody"}}},"required":true},"security":[{"BearerAuth":[]}],"responses":{"201":{"description":"User has successfully registered a webhook","content":{"application/json":{"schema":{"properties":{"success":{"type":"boolean"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/locations/{locationId}":{"get":{"operationId":"getLocation","summary":"Get a location","description":"Fetch one location by id.","tags":["Location Based Notifications"],"parameters":[{"$ref":"#/components/parameters/locationId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Location details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Location"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"put":{"operationId":"updateLocation","summary":"Update a location","description":"Update a registered location's `locationName`, `lat`, `lon` or default `notificationSettings`.\n\n`externalId` is not accepted: it is fixed at creation so other systems can key on it.\n\n### It changes the record, not the passes\n\n**Nothing already issued moves.** The registry is a convenience, not a live link. It exists so you never repeat the same `lat` and `lon` into every template and every pass that mentions the place. Those values are read out and copied when a template or a pass is written, and nothing reads back here afterwards.\n\nSo a store that moves takes two steps:\n\n1. `PUT` here, so anything created from now on resolves to the new coordinates.\n2. **Update the templates that use it.** Changing `template.locations` is what pushes new coordinates and wording to passes already in wallets.\n\nA pass carrying its own `templateOverrides.locations` follows the same rule: it changes when you update that pass, not when you update this record.","tags":["Location Based Notifications"],"parameters":[{"$ref":"#/components/parameters/locationId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateLocationRequestBody"}}},"required":true},"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Location updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Location"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"delete":{"operationId":"deleteLocation","summary":"Delete a location","description":"Delete a registered location. This removes the record and nothing else.\n\n**Passes already issued keep nudging at that spot.** Their coordinates and wording were copied in when the pass was written, so deleting the shorthand does not delete the geofence. To stop the nudges, take the entry out of `template.locations` (or the pass's `templateOverrides.locations`) and update the template or pass.\n\n**Prefer updating a record over deleting it.** Templates and passes name locations by `id` and `externalId`. Once the record is gone, the next create or update that names it fails with `404 LOCATION_NOT_FOUND`, which turns a tidy-up into a broken template.","tags":["Location Based Notifications"],"parameters":[{"$ref":"#/components/parameters/locationId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Location deleted successfully","content":{"application/json":{"schema":{"properties":{"success":{"type":"boolean"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/locations/external/{externalId}":{"put":{"operationId":"updateLocationByExternalId","summary":"Update a location by external ID","description":"**Address a location by your own identifier** instead of a Litecard id, so your POS or store system never has to store our ids.\n\nSame body, and the same rules, as the update by `locationId` above. Including the one that matters most: **this changes the record, not the passes already carrying those coordinates.** Update the templates that use the place to move what is already in someone's wallet.\n\nUseful when the place is already known to you as, say, `POS-042`, and you would otherwise need a mapping table to find our id first.","tags":["Location Based Notifications"],"parameters":[{"$ref":"#/components/parameters/externalId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateLocationRequestBody"}}},"required":true},"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Location updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Location"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/integrations/form/{formId}":{"get":{"operationId":"getIntegration_v1","summary":"Get a form's integration","description":"**Read one form's integration, and everything you need to configure it.** Call this before writing anything: the response carries both sides of the mapping, so you never have to guess a field name.\n\n| In the response | What it gives you |\n| --- | --- |\n| `litecardFieldNames` | Our side. Each entry has a `key`, a `label`, and whether it is one of the form's own fields or something Litecard appends |\n| `contactFieldNames`, `emailFieldNames`, `campaigns` | The provider's side, fetched live |\n| `validCredentials`, `hostValidated` | Whether the stored credential still works |\n| `apiKey` | The key an inbound provider posts with. This is where you get it |\n\n**Check `validCredentials` before you read anything else.** Until it is true, the configuration you get back is Litecard's defaults rather than what is stored, so a form nobody has finished setting up still answers with plausible-looking values.\n\n**`litecardFieldNames` flags two things worth honouring.** `external: true` marks a value the platform fills at pass creation, such as an allocated [voucher code](#tag/voucher-codes); it can only ever sync outward. `formField: false` marks something Litecard appends rather than a field the customer filled in, such as a pass link.\n\n[The section above](#tag/form-integrations) covers what each provider does with all this.","tags":["Form Integrations"],"parameters":[{"$ref":"#/components/parameters/formId"},{"$ref":"#/components/parameters/integrationProviderName"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Integration details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntegrationDetailsV1"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"post":{"operationId":"createOrUpdateIntegration_v1","summary":"Configure a form's integration","description":"**Write the configuration**, whether or not one exists yet. `providerName` in the body picks which provider you are configuring, and the rest of the body has to belong to that provider: fields from another are rejected rather than ignored.\n\n**Absent means unchanged, not cleared.** An omitted field keeps its stored value, and the seeded defaults apply only on the first attach. So a call carrying one field changes one field.\n\n**It refuses to save against a broken credential.** If the stored credential no longer validates, the call fails with `400 INVALID_CREDENTIALS` rather than storing configuration that could never run. Fix the connection first: reconnect through [OAuth Connections](#tag/oauth-connections), or re-supply credentials for the providers that use them.\n\nTwo flags decide more than they look:\n\n- `enabled: false` is the off switch for this form's sync. Nothing else disables an integration, since connections are only connected or removed.\n- `overridesLitecardInvitation: true` hands the pass invite to the provider, and Litecard then sends **neither** its invitation email **nor** its SMS.\n\nMap fields using the `key` values from the `GET`, not labels. Klaviyo takes a richer per-field contract instead of a flat mapping, which is the part the dashboard is most worth using for.","tags":["Form Integrations"],"parameters":[{"$ref":"#/components/parameters/formId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateIntegrationConfig"}}},"required":true},"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Integration details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntegrationDetailsV1"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/integrations/form/{formId}/validate":{"post":{"operationId":"validateIntegration_v1","summary":"Validate a form's integration","description":"**Ask the provider, not our schema.** A configuration can be perfectly well-formed and still not work, and this is what tells the difference.\n\nIt catches what validation on the way in cannot:\n\n- a credential revoked or expired at the provider since you attached it\n- a list, campaign or audience that has been deleted\n- a field renamed or removed on their side, leaving a mapping pointing at nothing\n\n**Worth running on a schedule, not only after editing.** Nothing on the Litecard side changes when a provider's admin deletes a list, so the first symptom is otherwise a sync that quietly stops matching. [List connections](#tag/oauth-connections) tells you about the credential; this tells you about the configuration built on it.","tags":["Form Integrations"],"parameters":[{"$ref":"#/components/parameters/formId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntegrationValidationRequestBody"}}},"required":true},"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Integration details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntegrationDetailsV1"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/integrations/{provider}/oauth/start":{"get":{"operationId":"startIntegrationOAuth_v1","summary":"Start an OAuth connection","description":"**Step one of the flow [the section above](#tag/oauth-connections) walks through.** Returns `{ authorizeUrl }` and stops there.\n\n**It answers with JSON, not a `302`.** Your own code navigates the browser to `authorizeUrl`; nothing is redirected for you. That is deliberate, because the caller is usually an admin screen that wants to open the provider in a popup or a new tab.\n\nThe two optional query parameters change what you end up with:\n\n| Send | Result |\n| --- | --- |\n| neither | An account-level connection, attached to nothing yet |\n| `formId` | Connect and attach to that form in one trip |\n| `connectionId` | **Reconnect**: re-authorise that exact connection rather than minting another |\n\n**Omit `connectionId` and Litecard still looks for an existing active connection** for the business and re-authorises that one. You only end up with a second connection when the first has been disconnected.\n\nAn unrecognised `{provider}` returns `404 UNKNOWN_PROVIDER`.","tags":["OAuth Connections"],"parameters":[{"$ref":"#/components/parameters/provider"},{"name":"formId","in":"query","required":false,"description":"Form the connection is being configured for. Omitted for an account-level connect.","schema":{"type":"string"}},{"name":"connectionId","in":"query","required":false,"description":"Reconnect an existing connection: re-authorizes that exact connection instead of minting a new one. Omitted for a fresh connect.","schema":{"type":"string"}},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Authorization URL for the browser to navigate to","content":{"application/json":{"schema":{"type":"object","properties":{"authorizeUrl":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/integrations/{provider}/oauth/callback":{"get":{"operationId":"integrationOAuthCallback_v1","summary":"OAuth redirect callback","description":"**You never call this.** The provider redirects a browser here after someone approves on its consent screen, using the `redirect_uri` registered with that provider.\n\n**It always answers with a redirect, never JSON**, because a person is what lands on it. Litecard swaps the `code` for tokens, stores or re-authorises the connection, runs any provider onboarding, then sends the browser back where the flow began.\n\nRead the outcome from the query string on that return:\n\n| On the return URL | Meaning |\n| --- | --- |\n| `connected=<provider>` | Done. The connection is stored |\n| `error=invalid_request` | The provider came back without a `code` or `state` |\n| `error=invalid_state` | The `state` was unknown or already used. Start again |\n| `error=exchange_failed` | The code could not be traded for tokens |\n\n**`state` is single-use**, so a refresh of the callback URL fails with `invalid_state` rather than making a second connection. That is the protection working, not a bug.\n\nIt is listed here so the round trip is legible. There is nothing to build against.","tags":["OAuth Connections"],"parameters":[{"$ref":"#/components/parameters/provider"},{"name":"code","in":"query","required":false,"description":"Authorization code issued by the provider","schema":{"type":"string"}},{"name":"state","in":"query","required":false,"description":"Opaque state value correlating the request","schema":{"type":"string"}},{"name":"scope","in":"query","required":false,"description":"Granted scopes the provider appends to the success redirect","schema":{"type":"string"}},{"name":"error","in":"query","required":false,"description":"Error code appended when the user denies/aborts","schema":{"type":"string"}},{"name":"error_description","in":"query","required":false,"description":"Human-readable error detail from the provider","schema":{"type":"string"}},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[],"responses":{"302":{"description":"Redirect back to the dashboard"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/integrations/{provider}/connections":{"get":{"operationId":"listIntegrationConnections_v1","summary":"List connections","description":"**Every connection the business holds for one provider**, with enough to decide whether it still works. Never returns tokens.\n\nThe dashboard shows this behind **Manage integrations**; the section above covers when to trust it.\n\n| Field | Use it for |\n| --- | --- |\n| `connectionId` | Attaching, disconnecting, or reconnecting through `oauth/start` |\n| `status` | `ACTIVE` or `REVOKED`. Revoked means the grant was invalidated, often uninstalled at the provider |\n| `needsReauth` | Send the person back through the flow |\n| `missingScopes` | Permissions you asked for and did not get. Empty while `needsReauth` is true, since re-auth supersedes it |\n\n**`missingScopes` is the quiet one.** A connection can be perfectly `ACTIVE` and still not do what you expect, because the person approving dropped a permission on the way through. Nothing fails loudly; the sync that needed that scope just does not happen.\n\n**This reads stored state and calls nobody**, so it tells you what Litecard has already noticed rather than what is true at the provider right now.\n\nReturns an empty array, not a `404`, when the business has never connected.","tags":["OAuth Connections"],"parameters":[{"$ref":"#/components/parameters/provider"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Connections for the business (empty array when none)","content":{"application/json":{"schema":{"type":"object","required":["connections"],"properties":{"connections":{"type":"array","items":{"type":"object","required":["connectionId","status","needsReauth","missingScopes"],"properties":{"connectionId":{"type":"string","description":"Stable connection id — pass to disconnect or to oauth/start for a reconnect."},"status":{"type":"string","enum":["ACTIVE","REVOKED"],"description":"Stored connection state. REVOKED means the grant was invalidated (e.g. uninstalled at the provider) and re-auth is required."},"needsReauth":{"type":"boolean","description":"True when the connection must be re-authorized (status REVOKED). Derived from stored state; a grant revoked upstream but not yet observed by a token refresh still reads false here."},"missingScopes":{"type":"array","items":{"type":"string"},"description":"Requested scopes the merchant did not grant. Empty when the grant is complete or the connection needs re-auth (re-auth supersedes scope warnings)."},"createdAt":{"type":"number","description":"Epoch ms the connection was first authorized. Absent for providers that do not capture it (currently never populated)."},"accountLabel":{"type":"string","description":"Human-readable provider-account label. Absent for providers that do not capture it (currently never populated)."}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/integrations/{provider}/connections/{connectionId}":{"delete":{"operationId":"disconnectIntegration_v1","summary":"Disconnect a connection","description":"**Delete a connection and the credentials behind it.**\n\n**Every form still attached stops syncing**, silently. Litecard un-stamps those forms so nothing is left pointing at a dead id, but it does not warn you first and does not move them to another connection. Check **List connections** above and re-point what you need before calling this.\n\nThis removes Litecard's copy of the permission. Revoking the grant at the provider's own end, if you want that too, is done there.\n\nAn id the business does not own returns `404 CONNECTION_NOT_FOUND`. Reconnecting afterwards mints a new connection with a new id, so anything holding the old one has to be updated.","tags":["OAuth Connections"],"parameters":[{"$ref":"#/components/parameters/provider"},{"name":"connectionId","in":"path","required":true,"description":"Connection to disconnect (from the connections listing).","schema":{"type":"string"}},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Disconnected","content":{"application/json":{"schema":{"type":"object","properties":{"disconnected":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/integrations/{provider}/connections/{connectionId}/attach":{"post":{"operationId":"attachIntegrationConnection_v1","summary":"Attach a connection to a form","description":"**Point a form at a connection you already have**, with no second trip to the provider. This is what makes one Klaviyo authorisation serve every form you run.\n\nTake `connectionId` from **List connections** above, and name the form in the body. Attaching only says *which* credential the form uses; what it then syncs is configured in [Form Integrations](#tag/form-integrations).\n\nYou can skip this on a first connect by passing `formId` to `oauth/start`, which connects and attaches together. Come here for every form after that.\n\nAttaching a second connection to a form replaces the first rather than adding to it: a form uses one connection per provider.","tags":["OAuth Connections"],"parameters":[{"$ref":"#/components/parameters/provider"},{"name":"connectionId","in":"path","required":true,"description":"Connection to attach (from the connections listing).","schema":{"type":"string"}},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["formId"],"properties":{"formId":{"type":"string","description":"Form to attach the connection to"}}}}}},"responses":{"200":{"description":"Connection attached to the form","content":{"application/json":{"schema":{"type":"object","required":["attached"],"properties":{"attached":{"type":"boolean"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/integrations/{provider}/setup":{"post":{"operationId":"setupIntegration_v1","summary":"Run provider setup","description":"**Provider-side onboarding, after the connection exists.** Some providers need more than a credential before anything useful happens, such as having default flows installed in their account.\n\nWhat it does is entirely provider-specific, which is why the body names a `formId` and nothing else: the provider decides what to build from that form's configuration.\n\n**Not every provider needs it.** Where one does, connecting alone leaves the integration authorised but inert, and the symptom is a sync that reports success and produces nothing at the far end. The dashboard runs this for you as part of setting a provider up, which is another reason to start there.","tags":["Form Integrations"],"parameters":[{"$ref":"#/components/parameters/provider"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["formId"],"properties":{"formId":{"type":"string","description":"Form whose integration config to use"},"flow":{"type":"string","description":"Provider-specific setup variant (Klaviyo: 'create' | 'deliver' | 'followUp'; omit for the provider's default)."}}}}}},"responses":{"201":{"description":"Provider-specific setup result","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Connection lacks a required scope — re-authorize"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Provider-specific setup conflict"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/csv-exports":{"get":{"operationId":"listCsvExports_v1","summary":"List CSV exports","description":"**Every export you have started, newest first.** The place to pick up a file you asked for earlier and never collected.\n\nOnly the asynchronous exports appear here. A `scansTable` or `notificationGroupsTable` request hands you the file on the spot and leaves no record behind, so if you did not keep that `signedUrl`, run it again.\n\nEach entry carries the filters it was created with and a `description` built from them, which is how you tell two exports of the same table apart. `presignedUrl` is present only on a `COMPLETED` one.\n\nPaginated: follow `next` until it stops coming back.","tags":["CSV"],"parameters":[{"$ref":"#/components/parameters/limitPagination"},{"$ref":"#/components/parameters/nextPagination"},{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"CSV Exports Retrieved","content":{"application/json":{"schema":{"type":"object","properties":{"exports":{"type":"array","items":{"$ref":"#/components/schemas/CSVExportRecord"}},"next":{"type":"string","description":"Pagination token","nullable":true}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/csv-exports/{exportId}":{"get":{"operationId":"getCsvExport_v1","summary":"Get a CSV export","description":"**Poll here after a `202`.** `status` walks `PENDING` -> `PROCESSING` -> `COMPLETED`, and `presignedUrl` appears only on the last of those.\n\n| `status` | What to do |\n| --- | --- |\n| `PENDING`, `PROCESSING` | Keep polling |\n| `COMPLETED` | Download `presignedUrl` within `expiresIn` seconds |\n| `FAILED` | Read `errorMessage` and start again |\n\n**The URL expires**, and `expiresIn` says how long you have. Fetch the record again for a fresh one rather than storing the link.\n\nA synchronous export has no record to fetch, so this only ever answers for an `exportId` you were given a `202` for. Anything else returns `404`.","tags":["CSV"],"parameters":[{"name":"exportId","in":"path","required":true,"schema":{"type":"string"},"description":"CSV export ID"},{"$ref":"#/components/parameters/txnId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"CSV Export Retrieved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CSVExportRecord"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/template/{templateId}/notification":{"post":{"operationId":"createTemplateNotification","summary":"Add an automated notification","description":"**Attach an automated message to a template**, so every pass issued from it inherits the behaviour.\n\nUse it for lifecycle messaging that should apply to all owners: a welcome push on download, a reminder before expiry. For a one-off blast to a chosen audience, use `POST /api/v1/queue/notification` instead.","tags":["Pass Template Notifications"],"security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/templateId"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTemplateNotification"}}}},"responses":{"201":{"description":"Notification setting created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"notification":{"$ref":"#/components/schemas/NotificationSettings"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/api/v1/template/{templateId}/notification/{notificationId}":{"put":{"operationId":"updateTemplateNotification","summary":"Update an automated notification","description":"Edit an automated template notification.\n\n**Takes effect from here on, never backwards.** A pass copies the wording and works out its send time the moment its trigger fires, so anything already queued keeps the old text, keeps its original time, and still fires.\n\nWhich passes an edit reaches depends on the trigger. `AFTER_CREATION` and `BEFORE_EXPIRY` are settled at pass creation, so the edit reaches passes created from now on. `AFTER_DOWNLOAD` is settled at download, so it reaches every pass not yet downloaded, however long ago it was created. The section above walks through a dated example.\n\nTo change one that is already queued, cancel its [notification group](#tag/notification-groups) before it fires and send a replacement.","tags":["Pass Template Notifications"],"security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/templateId"},{"name":"notificationId","in":"path","required":true,"schema":{"type":"string"},"description":"The ID of the notification setting to update"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTemplateNotification"}}}},"responses":{"200":{"description":"Notification setting updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"notification":{"$ref":"#/components/schemas/NotificationSettings"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"delete":{"operationId":"deleteTemplateNotification","summary":"Remove an automated notification","description":"Remove an automated notification from a template.\n\n**Sends already queued are not cancelled and still fire**, because each was settled the moment its trigger fired.\n\nWhat stops depends on the trigger. Remove an `AFTER_CREATION` or `BEFORE_EXPIRY` notification and passes created from now on stop inheriting it. Remove an `AFTER_DOWNLOAD` one and downloads from now on queue nothing, while a send queued by a download that has already happened still goes out. The section above walks through a dated example.\n\nTo stop one that is already queued, cancel its [notification group](#tag/notification-groups) before it fires.","tags":["Pass Template Notifications"],"security":[{"BearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/templateId"},{"name":"notificationId","in":"path","required":true,"schema":{"type":"string"},"description":"The ID of the notification setting to delete"},{"name":"x-active-business-id","in":"header","description":"Optional. Required for master users. Business ID for which the request is being made.","schema":{"type":"string"}}],"responses":{"200":{"description":"Notification setting deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"parameters":{"backlinkId":{"name":"backlinkId","in":"path","description":"Unique identifier that is used to identify the Backlink URL","required":true,"schema":{"type":"string"}},"barcodeValue":{"name":"barcodeValue","in":"path","description":"Unique identifier for the card","required":true,"schema":{"type":"string"}},"businessId":{"name":"businessId","in":"query","description":"Unique identifier for a business","schema":{"type":"string","example":"abcd"},"required":true},"businessIdPath":{"name":"businessIdPath","in":"path","description":"Unique identifier for a business","schema":{"type":"string","example":"abcd"},"required":true},"cardId":{"name":"cardId","in":"path","description":"Unique identifier for the card","required":true,"schema":{"type":"string"}},"cardIdQuery":{"name":"cardId","in":"query","description":"Unique identifier for a card","required":false,"schema":{"type":"string","example":"1234567890"}},"cardOwnerEmail":{"name":"cardOwnerEmail","in":"query","description":"Email of card owner","required":false,"schema":{"type":"string","example":"john@litecard.com.au"}},"cardOwnerId":{"name":"cardOwnerId","in":"path","description":"Unique identifier for a card owner","required":true,"schema":{"type":"string","example":"a0gvLNmkj1J7KaoPmUo5F"}},"cardOwnerPhone":{"name":"cardOwnerPhone","in":"query","description":"Phone number of card owner","required":false,"schema":{"type":"string","example":"1234567890"}},"cardUploadGroupId":{"name":"cardUploadGroupId","in":"path","description":"Unique identifier that is used to identify a card upload group","required":true,"schema":{"type":"string"}},"couponId":{"name":"couponId","in":"path","description":"Unique identifier that is used to identify a business","required":true,"schema":{"type":"string"}},"downloadId":{"name":"downloadId","in":"path","description":"Unique identifier that is used to identify the download urls for a card","required":true,"schema":{"type":"string"}},"endDateTime":{"name":"endDateTime","in":"query","description":"End datetime (in ISO 8601 format) to filter results","required":false,"schema":{"type":"string","example":"2000-01-01T12:34:56.789Z"}},"externalId":{"name":"externalId","in":"path","description":"External identifier for a Location","required":true,"schema":{"type":"string"}},"formId":{"name":"formId","in":"path","description":"Unique identifier that is used to identify a form used by a business","required":true,"schema":{"type":"string"}},"includeDeleted":{"name":"includeDeleted","in":"query","description":"Include records marked as deleted in the response","schema":{"type":"boolean","example":false,"default":false},"required":false},"integrationProviderName":{"name":"providerName","in":"query","description":"Integration Provider Name","schema":{"type":"string","example":"DOTDIGITAL","enum":["LC_INBOUND_LISTENER","DOTDIGITAL","TALONONE","KLAVIYO","SQUARE","SHOPIFY","BLOOMREACH"]},"required":true},"isShort":{"name":"isShort","in":"query","description":"Short projection","schema":{"type":"boolean","example":true},"required":false},"limitPagination":{"name":"limit","in":"query","description":"Maximum number of response items in a request","schema":{"type":"string","example":"10"},"required":true},"locationId":{"name":"locationId","in":"path","description":"Unique identifier for a Location","required":true,"schema":{"type":"string"}},"nextPagination":{"name":"next","in":"query","description":"Key to be used to retrieve next set of results","schema":{"type":"string"}},"notificationGroupId":{"name":"notificationGroupId","in":"path","description":"Unique identifier that is used to identify a notification group","required":true,"schema":{"type":"string"}},"orderId":{"name":"orderId","in":"path","description":"Unique identifier for a Wallet order","required":true,"schema":{"type":"string"}},"orderNumberQuery":{"name":"orderNumber","in":"query","description":"Merchant-visible order number to search by","required":false,"schema":{"type":"string"}},"orderTemplateId":{"name":"orderTemplateId","in":"path","description":"Id of the Apple Wallet order template (reusable static merchant / web service configuration)","required":true,"schema":{"type":"string","example":"ot_V1StGXR8_Z5jdHi6B-myT"}},"orderTemplateIdQuery":{"name":"orderTemplateId","in":"query","description":"Id of the Apple Wallet order template (reusable static merchant / web service configuration)","required":false,"schema":{"type":"string"}},"passCountsDuration":{"name":"duration","in":"query","description":"Duration for which pass counts are required. Choose from 'PAST_7_DAYS' for the past week or 'PAST_30_DAYS' for the past 30 days.","required":true,"schema":{"type":"string","enum":["PAST_7_DAYS","PAST_30_DAYS","PAST_90_DAYS","ALL"],"example":"PAST_7_DAYS"}},"passCountsTrendsDuration":{"name":"days","in":"query","description":"Number of days for which pass counts are required. Acceptable values: 7 (past week) or 30 (past 30 days).","required":true,"schema":{"type":"integer","minimum":1,"maximum":100,"example":7}},"passDownloadsTrendReference":{"name":"reference","in":"query","description":"Trend's date reference. Choose from 'CREATION' for the card creation date or 'DOWNLOADS' for the download dates.","required":false,"schema":{"type":"string","enum":["CREATION","DOWNLOADS"],"example":"DOWNLOADS"}},"passType":{"name":"type","in":"query","description":"Resource type. Defaults to PASS when omitted or empty.","required":false,"schema":{"type":"string","enum":["PASS","ORDER"],"example":"PASS"}},"provider":{"name":"provider","in":"path","description":"Name of External Provider calling Litecard APO","required":true,"schema":{"type":"string"}},"scanId":{"name":"scanId","in":"path","description":"Unique identifier for a scan","required":true,"schema":{"type":"string","example":"-jJWhjZ1a"}},"startDateTime":{"name":"startDateTime","in":"query","description":"Start datetime (in ISO 8601 format) to filter results","required":false,"schema":{"type":"string","example":"2000-01-01T12:34:56.789Z"}},"subscriptionId":{"name":"subscriptionId","in":"query","description":"Unique identifier for a registered external subscription","required":true,"schema":{"type":"string"}},"templateId":{"name":"templateId","in":"path","description":"Id of the template","required":true,"schema":{"type":"string","example":"V1StGXR8_Z5jdHi6B-myT"}},"templateIdQuery":{"name":"templateId","in":"query","description":"Template whose external subscriptions are being accessed","required":true,"schema":{"type":"string"}},"txnId":{"name":"x-transaction-id","in":"header","description":"Optional. Id used for distributed tracing","required":false,"schema":{"type":"string"}},"usageKey":{"name":"x-api-key","in":"header","description":"Usage key used to manage endpoint rate limits. If required ask the Litecard team to setup a key","required":true,"schema":{"type":"string"}},"webhookId":{"name":"webhookId","in":"query","description":"Unique identifier for a customer registered webhook","required":true,"schema":{"type":"string"}}},"responses":{"AlreadyExists":{"description":"Already Exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"errorCode":"ALREADY_EXISTS","message":"Already Exists"}}}},"BadRequest":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"errorCode":"BAD_REQUEST","message":"Bad Request"}}}},"Conflict":{"description":"Conflict – resource already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"errorCode":"CONFLICT","message":"Resource already exists"}}}},"InternalServerError":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"errorCode":"INTERNAL_SERVICE_ERROR","message":"Something Failed"}}}},"NotFound":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"errorCode":"NOT_FOUND","message":"Not Found"}}}},"Unauthorized":{"description":"Unauthorized Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"errorCode":"UNAUTHORIZED","message":"Unauthorized"}}}},"ListNotificationGroupsV1":{"description":"Notification Groups Listed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListNotificationGroupsResponse"}}}},"ListScansV1":{"description":"Scans Listed","content":{"application/json":{"schema":{"type":"object","properties":{"scans":{"type":"array","items":{"$ref":"#/components/schemas/Scan"}},"next":{"type":"string","description":"Key used to next set of results"}}}}}},"ProcessCardUploadGroupAndPresignedUrlResponse":{"description":"Card Upload Group Processed and Presigned URL Generated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProcessCardUploadGroupAndPresignedUrlResponse"}}}},"SearchPassesV1":{"description":"Passes Searched","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchPassesResponse"}}}}},"schemas":{"AggregatedBillingStatistics":{"type":"object","description":"Aggregated Scans Statistics","properties":{"date":{"type":"string","example":"2021-11-09T00:00:00.000Z"},"members":{"type":"number","example":422},"signups":{"type":"number","example":300},"coupons":{"type":"number","example":56}},"required":["date","members","signups","coupons"]},"AggregatedNotificationsStatistics":{"type":"object","description":"Aggregated Notifications Statistics","properties":{"date":{"type":"string","example":"2021-11-09T00:00:00.000Z"},"errorAPN":{"type":"number","example":0},"errorEmail":{"type":"number","example":0},"errorSMS":{"type":"number","example":0},"queuedAPN":{"type":"number","example":0},"queuedEmail":{"type":"number","example":0},"queuedSMS":{"type":"number","example":0},"sentAPN":{"type":"number","example":0},"sentEmail":{"type":"number","example":0},"sentSMS":{"type":"number","example":0}}},"AggregatedScansStatistics":{"type":"object","description":"Aggregated Scans Statistics","properties":{"date":{"type":"string","example":"2021-11-09T00:00:00.000Z"},"templates":{"type":"array","items":{"type":"object","additionalProperties":{"type":"object","properties":{"total":{"type":"number","description":"Total number of scans on a given timestamp for this template","example":0}}}}},"total":{"type":"number","description":"Total number of scans on a given timestamp","example":0}}},"AnalyticsBillingResultsSchema":{"type":"object","properties":{"results":{"type":"object","properties":{"businessId":{"type":"string","example":"litecard"},"queryParameters":{"type":"object","properties":{"startDateTime":{"type":"string","example":"2020-12-01"},"endDateTime":{"type":"string","example":"2020-12-01"}}},"numResults":{"type":"number","example":8},"analytics":{"type":"array","items":{"$ref":"#/components/schemas/AggregatedBillingStatistics"}}},"required":["businessId","queryParameters","numResults","analytics"]}},"required":["results"]},"AnalyticsNotificationsResultsSchema":{"type":"object","properties":{"results":{"type":"object","properties":{"businessId":{"type":"string","example":"litecard"},"queryParameters":{"type":"object","properties":{"startDateTime":{"type":"string","example":"2020-12-01"},"endDateTime":{"type":"string","example":"2020-12-01"}}},"numResults":{"type":"number","example":8},"analytics":{"type":"array","items":{"$ref":"#/components/schemas/AggregatedNotificationsStatistics"}}}}}},"AnalyticsScansResultsSchema":{"type":"object","properties":{"results":{"type":"object","properties":{"businessId":{"type":"string","example":"litecard"},"queryParameters":{"type":"object","properties":{"startDateTime":{"type":"string","example":"2020-12-01"},"endDateTime":{"type":"string","example":"2020-12-01"}}},"numResults":{"type":"number","example":8},"analytics":{"type":"array","items":{"$ref":"#/components/schemas/AggregatedScansStatistics"}}}}}},"BaseCardPayload":{"type":"object","description":"Datafields used by the card","properties":{"email":{"type":"string","description":"Example field for email address","example":"john@litecard.com.au"},"phone":{"type":"string","description":"Users mobile number for text notifications","example":"+61401234567"},"exampleProperty":{"oneOf":[{"type":"string","description":"Example card property as a plain string value","example":"Sample string property"},{"$ref":"#/components/schemas/StandardAddress"}],"description":"This property can be either a simple string or a structured StandardAddress object."}},"additionalProperties":true},"BaseCustomFormDesignSchema":{"type":"object","description":"Sign-up page configuration object","properties":{"designType":{"type":"number","description":"design type id","default":1},"isPublic":{"type":"boolean","description":"Flag to indicate if the form is public","default":false},"documentTitle":{"type":"string","description":"Title of the document","example":"Please enter your title"},"faviconPath":{"type":"string","description":"Path to the favicon image","example":"/path/to/favicon.ico"},"facebookURL":{"type":"string","description":"URL to the Facebook page","example":"https://www.facebook.com/"},"socialLinksText":{"type":"string","description":"Text for the social links section","example":"SHARE"},"instagramURL":{"type":"string","description":"URL to the Instagram page","example":"https://www.instagram.com/"},"redirectionURL":{"type":"string","description":"URL to redirect to after sign up","example":"https://www.example.com"},"termsAndConditionsURL":{"type":"string","description":"URL to the terms and conditions","example":"https://www.example.com/terms"},"termsLinkText":{"type":"string","description":"Text for the terms and conditions link","example":"Terms and Conditions"},"homePageURL":{"type":"string","description":"URL to the home page","example":"https://www.example.com"},"successSubmissionMessage":{"type":"string","description":"Message displayed on successful form submission","example":"Thank you for signing up!"},"disableSuccessSubmissionMessage":{"type":"boolean","description":"Flag to disable the success submission message","example":false},"backgroundStyles":{"$ref":"#/components/schemas/CustomFormDesignBackgroundStyles"},"leftSection":{"$ref":"#/components/schemas/CustomFormDesignLeftSection"},"rightSection":{"$ref":"#/components/schemas/CustomFormDesignRightSection"},"commonStyles":{"$ref":"#/components/schemas/CustomFormDesignCommonStyles"},"footerSection":{"$ref":"#/components/schemas/CustomFormDesignFooterSection"},"signUpSection":{"$ref":"#/components/schemas/CustomFormDesignSignUpSection"}},"required":["backgroundStyles","leftSection"]},"BusinessSettings":{"type":"object","description":"Configurable settings in business level","properties":{"locationNotificationsIndependentOfTemplates":{"type":"boolean","description":"Flag to determine whether location notifications are independent of templates. Enabling this flag allows location notifications to be managed separately from template updates.","example":false},"samsungEnabled":{"type":"boolean","description":"Enables Samsung Wallet pass creation for this business. Only affects new card creation — existing Samsung passes continue to function.","example":false}}},"Card":{"type":"object","properties":{"id":{"type":"string","description":"ID of the Pass","example":"7048582966"},"downloadId":{"type":"string","description":"DownloadId used for landing page","example":"V1StGXR8_Z5jdHi6B-myT"},"appleLink":{"type":"string","description":"Url for the apple pass","example":"https://litecard-demo-pkpass.s3-ap-southeast-2.amazonaws.com/coldbrew.pkpass"},"businessId":{"type":"string","description":"ID of the Business","example":"business123"},"userType":{"type":"string","description":"Type of User","example":"employee"},"createdAt":{"type":"string","description":"Date Time card was created","example":"2021-11-05T04:25:42.676Z"},"googlePassId":{"type":"string","description":"Id of google pass","example":"1231244"},"authToken":{"type":"string","description":"Internal Use Only. Apple Device Auth Token","example":"dscxdsf234"},"passType":{"type":"string","description":"Mobile wallet pass type","example":"VISIT"},"googleLink":{"type":"string","description":"Url of google pass","example":"https://pay.google.com..."},"samsungLink":{"type":"string","description":"Url of samsung pass","example":"https://a.swallet.link/atw/656147182764415319#Clip?pdata=sIgHCzIwM9g"},"templateId":{"type":"string","description":"Id for the template used to create the card","example":"test_business"},"dataFields":{"type":"object","properties":{"exampleDataField":{"type":"string","description":"Example Value","example":"example value"},"fullName":{"type":"string","description":"Full name of owner of card","example":"John Doe"}}},"formId":{"type":"string","description":"Id for field input form","example":"00000001"},"updatedAt":{"type":"string","description":"","example":"2021-11-05T04:25:42.676Z"},"pkPassId":{"type":"string","description":"Id of Apple Pass","example":"pass.au.com.litecard.dev.q124234"},"barcodeValue":{"type":"string","description":"what the barcode value is","example":"5434435"},"appleStatus":{"type":"string","description":"Status of card inside of Apple Wallet","example":"ACTIVE"},"googleStatus":{"type":"string","description":"Status of card inside of Google Wallet","example":"INACTIVE"},"samsungStatus":{"type":"string","description":"Status of card inside of Samsung Wallet","example":"ACTIVE"},"cardOwnerId":{"type":"string","description":"Id of the card owner","example":"asb123"},"status":{"type":"string","description":"Activation Status of the card","enum":["ACTIVE","INACTIVE","DELETED","RESERVED","ERROR","AVAILABLE"],"example":"INACTIVE"},"expiry":{"type":"string","description":"Expiry of card, in ISO-8601 format","example":"2021-08-11T03:15:56.860Z"},"barcodeLink":{"type":"string","description":"Link to the barcode image","example":"https://example.com/blah.png"},"cardOwnerCopy":{"type":"object","description":"A copy of the card owner's information","properties":{"id":{"type":"string","description":"Unique identifier for the card owner","example":"JUJCSVGnLihP6xcTRElpJ"},"businessId":{"type":"string","description":"Identifier for the business associated with the card owner","example":"LgAcJKiMP5qJf8mKv9a9S"},"name":{"type":"string","description":"Full name of the card owner","example":"John Doe"},"membershipNumber":{"type":"string","description":"Membership number associated with the card","example":"M123456789"}}},"disableQR":{"type":"boolean","description":"Disable QR code on Welcome Page","example":true},"stripeCustomerId":{"type":"string","description":"stripeCustomerId for subscription passes","example":"str_123"},"disableDownload":{"type":"boolean","description":"Disable download link if pass is already activated. Default: false","example":true},"tier":{"type":"string","description":"Membership tier of the card","example":"SILVER"},"numOfScans":{"type":"integer","description":"Number of times the card has been scanned","example":0,"default":0}},"required":["formId","templateId"]},"CardDataFieldHistory":{"type":"object","description":"Past values of the card's data fields, keyed the same way as dataFields, newest first. Only present for fields the template opted into with historyViewEnabled. The current value is not in here, it stays in dataFields","additionalProperties":{"type":"array","items":{"type":"object","properties":{"value":{"description":"The value this field held before it was replaced","example":"WEB 15$ DSFSF","oneOf":[{"type":"string"},{"type":"number"}]},"updatedAt":{"type":"string","format":"date-time","description":"When the value was replaced and moved into history","example":"2026-08-10T07:00:15.075Z"}},"required":["value","updatedAt"]}},"example":{"offers":[{"value":"WEB 155$ DSFSF","updatedAt":"2026-08-10T07:00:15.075Z"},{"value":"WEB 15$ DSFSF","updatedAt":"2026-08-09T04:22:11.001Z"}]}},"CardMappingPayload":{"type":"object","properties":{"formId":{"type":"string","description":"Id to define the schema for field inputs used to create/update the card.","example":"kSwoChd"},"templateId":{"type":"string","description":"Id for the apple/google pass template used to create the card.","example":"test_business"},"cardPayload":{"$ref":"#/components/schemas/BaseCardPayload"}},"additionalProperties":true},"CardOwner":{"type":"object","description":"Information about the card owner","properties":{"id":{"type":"string","description":"Unique identifier for the card owner","example":"JUJCSVGnLihP6xcTRElpJ"},"businessId":{"type":"string","description":"Identifier for the business associated with the card owner","example":"L3ZrB748fLxlFD8ZjFVgX"},"phone":{"type":"string","description":"Phone number of the card owner","example":"+61402221795"},"email":{"type":"string","format":"email","description":"Email address of the card owner","example":"dzuy.pham+test@corplite.com"},"accountName":{"type":"string","description":"Owner of the card","example":"Ankus Fang"},"userType":{"type":"string","description":"User Type","example":"basic"},"createdAt":{"type":"string","format":"date-time","description":"Timestamp of when the card owner record was created","example":"2023-10-19T09:05:52.771Z"},"firstName":{"type":"string","description":"First name of the card owner","example":"Dzuy"},"formId":{"type":"string","description":"Identifier for the form associated with the card owner","example":"TmtB-B7gadaNbH8aF7Pan"},"lastName":{"type":"string","description":"Last name of the card owner","example":"Pham"},"state":{"type":"string","description":"State of residence of the card owner","example":"VIC"},"updatedAt":{"type":"string","format":"date-time","description":"Timestamp of the last update to the card owner's record","example":"2023-10-19T09:05:52.771Z"},"version":{"type":"integer","format":"int32","description":"Version number of the card owner's record","example":1}}},"CardOwnerRequestBody":{"type":"object","properties":{"firstName":{"type":"string","description":"First name of the user","example":"John"},"lastName":{"type":"string","description":"Last name of the user","example":"Doe"},"email":{"type":"string","description":"Email address of the user","example":"john@litecard.com.au"},"phone":{"type":"string","description":"Phone number of the user","example":"1234567890"},"state":{"type":"string","description":"State of Residence","example":"VIC"},"formId":{"type":"string","description":"Id to define the schema for field inputs used to create/update the card..","example":"kSwoChd"},"businessId":{"type":"string","description":"Business Id of the sign up form","example":"sample_dev_company"}},"additionalProperties":true},"CardOwnersScanRequestBody":{"type":"object","properties":{"field":{"type":"string","description":"Attribute name for table filtering","example":"businessId"},"query":{"type":"string","description":"Attribute value for table filtering","example":"abc123"}},"required":["field","query"]},"Certificate":{"type":"object","properties":{"apple":{"type":"string","description":"Apple Certificate Id","example":"st3dfdAsaaf"},"google":{"type":"string","description":"Google Certificate Id","example":"z2pkdjkASg3"}}},"Coupon":{"type":"object","properties":{"id":{"type":"string","description":"Id of the coupon","example":"-jJWhjZ1a"},"name":{"type":"string","description":"Name of the coupon","example":"Grab your first coupon here"},"description":{"type":"string","description":"description of the coupon","example":"Promotions are only for the new registered card owners"},"discountValue":{"type":"number","description":"Coupon discount value","example":"100"},"expiryDate":{"type":"string","description":"Expire date of the coupon","example":"2021-08-15T03:15:56.860Z"},"redemption":{"type":"object","properties":{"buttonText":{"type":"string","description":"Button text of the coupon","example":"Redeem Now (Bar Staff Only)"},"instructions":{"type":"string","description":"Instructions of the coupon","example":"Show this to the bar staff to redeem"}}},"createdAt":{"type":"string","description":"Create date of the coupon","example":"2021-08-11T03:15:56.860Z"},"updatedAt":{"type":"string","description":"Update date of the coupon","example":"2021-08-1T03:15:56.860Z"},"expiryEnabled":{"type":"boolean","description":"Expire status of the coupon","example":"true"},"createdBy":{"type":"string","description":"Auth0 Id of the user who created this instance","example":"V1StGXR8_Z5jdHi6B-myT"},"status":{"type":"string","description":"Redeem status of the coupon","example":"ISSUED"}}},"CreateCouponResponseSchema":{"type":"object","properties":{"message":{"type":"string","additionalProperties":false},"success":{"type":"boolean","description":"Returns whether the request was successful","example":true,"additionalProperties":false}}},"CreateProfileBody":{"type":"object","properties":{"businessName":{"type":"string","description":"Business name","example":"Cryo gym"},"subBusinessName":{"type":"string","description":"Name to show on sub business selection","example":"Cryo gym - Sub 1"},"email":{"type":"string","description":"Business email","example":"Ankus Fang"},"postCode":{"type":"string","description":"Business postcode","example":"CryoGym"},"state":{"type":"string","description":"Business state","example":"AHVW1qv4I_Teqdu4VjwMA"},"logoImage":{"type":"string","nullable":true,"description":"Logo image s3 URL","example":"https://assets.litecard.io/logo.png"},"overrideLitecardLogoImage":{"type":"string","nullable":true,"description":"Override LiteCard logo image s3 URL","example":"https://assets.litecard.io/override-logo.png"},"templateCount":{"type":"number","description":"Current total number of templates a user has.","example":"3"},"templateLimit":{"type":"number","description":"Maximum amount of templates this user can have. A templateLimit of -1 means the user can have unlimited templates.","example":"12"},"createdAt":{"type":"string","description":"Date when pass was first created, in ISO-8601 format","example":"2021-08-11T03:15:56.860Z"},"updatedAt":{"type":"string","description":"Date when pass was last updated, in ISO-8601 format","example":"2021-08-1T03:15:56.860Z"},"settings":{"$ref":"#/components/schemas/BusinessSettings"},"isMasterBusiness":{"type":"boolean","description":"Is the account a master account","example":true},"masterBusinessId":{"type":"string","description":"Master business id","example":"master-business-id"}}},"CreateScanBody":{"type":"object","properties":{"id":{"type":"string","description":"Id of the scan","example":"2fhajJWhjZ1a"},"createdBy":{"type":"string","description":"Auth0 ID of Creator","example":"Ankus Fang"},"deviceId":{"type":"string","description":"Id of the USB Scanner used","example":"CryoGym"},"deviceName":{"type":"string","description":"Name of the USB Scanner","example":"AHVW1qv4I_Teqdu4VjwMA"},"location":{"type":"string","description":"Location of the USB Scanner","example":"eqdu4VjAHVW1qv4I_TwMA"},"createdAt":{"type":"string","description":"Date when pass was first created, in ISO-8601 format","example":"2021-08-11T03:15:56.860Z"},"updatedAt":{"type":"string","description":"Date when pass was last updated, in ISO-8601 format","example":"2021-08-1T03:15:56.860Z"},"businessId":{"type":"string","description":"Business Name","example":"CryoGym"}},"required":["id","createdBy","businessId"]},"CustomFormDesignBackgroundStyles":{"type":"object","description":"Background styling options","properties":{"colour":{"type":"string","description":"Background color","example":"lightgrey","default":"#FFFFFF"},"textColour":{"type":"string","description":"Text color","example":"#000000","default":"#000000"}}},"CustomFormDesignCommonStyles":{"type":"object","description":"Common styles applied throughout the form","properties":{"inputBgColour":{"type":"string","description":"Background color for input fields","example":"#FFFFFF","default":"#FFFFFF"},"inputTextColour":{"type":"string","description":"Text color for input fields","example":"#000000","default":"#000000"},"inputBorderColour":{"type":"string","description":"Border color for input fields","example":"#000000","default":"#000000"},"iconBgColour":{"type":"string","description":"Background color for icons","example":"#000000","default":"#000000"},"iconColour":{"type":"string","description":"Color for icons","example":"#FFFFFF","default":"#FFFFFF"},"linkColour":{"type":"string","description":"Colour of the links","example":"#1677ff","default":"#1677ff"},"activeInputBgColour":{"type":"string","description":"Background color for active input fields","example":"#F0F0F0"},"activeInputTextColour":{"type":"string","description":"Text color for active input fields","example":"#333333"},"activeLinkColour":{"type":"string","description":"Colour of active links","example":"#0056b3"},"disabledInputBgColour":{"type":"string","description":"Background color for disabled input fields","example":"#E0E0E0"},"disabledInputTextColour":{"type":"string","description":"Text color for disabled input fields","example":"#999999"},"errorTextColour":{"type":"string","description":"Colour of error text","example":"red","default":"red"},"leftSectionFontFamily":{"type":"string","description":"Font family for the left section","example":"Arial","deprecated":true},"headerFontFamily":{"type":"string","description":"Font family for the header","example":"Arial","deprecated":true},"fontFamily":{"type":"string","description":"Font family for the header","example":"Arial"}}},"CustomFormDesignFooterSection":{"type":"object","description":"Configuration for the footer section of the form","properties":{"content1":{"type":"string","description":"Footer header text","example":"Footer Header"},"content2":{"type":"string","description":"Content for the footer","example":"This is the footer content."},"image":{"type":"string","description":"URL of the footer image","example":"/path/to/footer-image.png"},"textColour":{"type":"string","description":"Colour of the text","example":"#000000"}}},"CustomFormDesignLeftSection":{"type":"object","description":"Configuration for the left section of the form","properties":{"header":{"type":"string","description":"Header text for the left section","example":"Welcome to the form"},"headerTextColour":{"type":"string","description":"Colour of the header text","example":"#000000","default":"#000000"},"headerLogo":{"type":"string","description":"URL of the header logo image","example":"/path/to/logo.png"},"backgroundImage":{"type":"string","description":"URL of the background image","example":"/path/to/logo.png"},"footerHeader":{"type":"string","description":"Footer header text","example":"Footer Header"},"footerContent":{"type":"string","description":"Content for the footer","example":"This is the footer content."},"footerImage":{"type":"string","description":"URL of the footer image","example":"/path/to/footer-image.png"},"footerLargeImage":{"type":"string","description":"URL of the footer image","example":"/path/to/footer-image.png"},"styles":{"type":"object","description":"Styling options for the left section","properties":{"logoFullWith":{"type":"boolean","description":"Flag to indicate if the logo should use full width","default":true}}},"headerClassName":{"type":"string","description":"Custom CSS class for the header","example":"form-header"}}},"CustomFormDesignRightSection":{"type":"object","description":"Configuration for the right section of the form","properties":{"headerLogo":{"type":"string","description":"URL of the header logo image","example":"/path/to/right-logo.png"},"header":{"type":"string","description":"Header text for the right section","example":"Sign up now!"},"content":{"type":"string","description":"Content for the right section","example":"Please fill in your details"},"instructions":{"type":"string","description":"Content for the instructions section","example":"Please fill in your details"},"headerTextColour":{"type":"string","description":"Colour of the header text","example":"#000000","default":"#000000"},"submitButtonColour":{"type":"string","description":"Colour of the submit button","example":"#FFFFFF","default":"#FFFFFF"},"submitButtonTextColour":{"type":"string","description":"Colour of the submit button text","example":"#000000","default":"#000000"},"submitButtonText":{"type":"string","description":"Text displayed on the submit button","example":"Send","default":"Send"},"submitBorderColour":{"type":"string","description":"Border color of the submit button","example":"transparent","default":"transparent"},"headerClassName":{"type":"string","description":"Custom CSS class for the header","example":"form-header"},"formStyles":{"type":"object","description":"Custom styles for the form","properties":{}}}},"CustomFormDesignSchema":{"allOf":[{"$ref":"#/components/schemas/BaseCustomFormDesignSchema"},{"type":"object","properties":{"languageOverrides":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/BaseCustomFormDesignSchema"}}}}]},"CustomFormDesignSignUpSection":{"type":"object","description":"Configuration for the sign up section of the form","properties":{"content":{"type":"string","description":"Content for the sign up section","example":"To start, please sign in using your Google or Apple account."},"appleButtonText":{"type":"string","description":"Text for the Apple button","example":"Sign in with Apple"},"googleButtonText":{"type":"string","description":"Text for the Google button","example":"Sign in with Google"},"continueButtonText":{"type":"string","description":"Text for the continue button","example":"Continue"},"continueButtonHelperText":{"type":"string","description":"Helper text for the continue button","example":"Click the \"Continue\" button to enter your email manually"}}},"CustomerWebhook":{"type":"object","properties":{"webhookUrl":{"type":"string","description":"The endpoint for Litecard to send the download data","example":"https://example.com/webhook"},"method":{"type":"string","description":"Method to use","enum":["POST"],"example":"POST"},"authType":{"type":"string","enum":["API_KEY"],"description":"The type of Auth to use for the webhook"},"authConfig":{"type":"object","description":"The properties we need to use for Authentication","properties":{"securityHeader":{"type":"string","description":"The header to use in the request when entering the key","example":"X-API-KEY"}}},"events":{"type":"array","description":"Types of events the webhook supports","items":{"enum":["CARD_DOWNLOAD","CARD_REMOVE","REGISTER_WEBHOOK","CARD_CREATE","CARD_UPDATE"],"example":"CARD_DOWNLOAD"}}}},"DateTimeStyle":{"type":"string","description":"Apple date time display style","default":"NOT_USED","example":"SHORT","enum":["NOT_USED","NONE","SHORT","MEDIUM","LONG","FULL"]},"DeleteCardOwnerResponseSchema":{"type":"object","properties":{"message":{"type":"string","additionalProperties":false},"success":{"type":"boolean","description":"Returns whether the request was successful","example":true,"additionalProperties":false}}},"DeleteCouponResponseSchema":{"type":"object","properties":{"message":{"type":"string","additionalProperties":false},"success":{"type":"boolean","description":"Returns whether the request was successful","example":true,"additionalProperties":false}}},"DownloadCsvResponse":{"type":"object","properties":{"signedUrl":{"type":"string","description":"url for downloading the csv","example":"https://lc-business"},"expiryTime":{"type":"string","description":"Expiry time for the url","example":"2022-02-04T06:22:37.773Z"},"startDateTime":{"type":"string","description":"Start date time of the exporting data","example":"2022-02-04T06:22:37.773Z"},"endDateTime":{"type":"string","description":"End date time of the exporting data","example":"2022-02-04T06:22:37.773Z"}}},"ErrorResponse":{"type":"object","properties":{"errorCode":{"type":"string"}}},"ExportCsvRequestBody":{"type":"object","properties":{"dynamicColumns":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string","description":"The column name of the table ","example":"Last Name"},"value":{"type":"string","description":"The dataIndex of the table","example":"firstName"}},"required":["label","value"]}},"tableName":{"type":"string","enum":["SCAN_TABLE","MEMBER_TABLE","CARD_TABLE"],"description":"The name of the table","example":"SCAN_TABLE"},"startDateTime":{"type":"string","description":"StartDateTime of the exporting data","example":"2022-02-04T06:22:37.773Z"},"endDateTime":{"type":"string","description":"EndDateTime of the exporting data","example":"2022-02-04T06:22:37.773Z"}}},"Form":{"type":"object","properties":{"id":{"type":"string","description":"Id for field input form","example":"-jJWhjZ1a"},"createdAt":{"type":"string","description":"Create date of the form","example":"2021-08-11T03:15:56.860Z"},"fields":{"type":"array","items":{"$ref":"#/components/schemas/FormFields"}},"formName":{"type":"string","description":"Name of the form","example":"test form"},"createdBy":{"type":"string","description":"Auth0 Id of the user who created this instance","example":"V1StGXR8_Z5jdHi6B-myT"},"businessId":{"type":"string","description":"Id for the Business that this entity belongs to","example":"LiteCard"},"userType":{"type":"string","description":"user type","example":"staff"},"ttlEnabled":{"type":"boolean","description":"Enable ttl","example":"true"},"ttlPeriod":{"type":"number","description":"Set ttl period","example":"10"},"updatedAt":{"type":"string","description":"Update date of the form","example":"2021-08-11T03:15:56.860Z"},"passwordEnabled":{"type":"boolean","description":"Enable password","example":"true"},"logo":{"type":"string","description":"logo image link url","example":"https://assets-dev.litecard.io/logo.png"},"title":{"type":"string","description":"Title of the form","example":"LiteCard"},"description":{"type":"string","description":"Description of the form","example":"Make your move with LiteCard today."},"style":{"type":"object","properties":{"bgPrimary":{"type":"string","description":"Primary colour for the form background","example":"#1890ff"},"bgSecondary":{"type":"string","description":"Secondary colour for the form background","example":"#1890ff"},"btnPrimary":{"type":"string","description":"Secondary colour for the button","example":"#1890ff"},"btnSecondary":{"type":"string","description":"Secondary colour for the button","example":"#1890ff"},"label":{"type":"string","description":"Label colour for Apple Passes","example":"#f1f0f2"}}},"connectIDEnabled":{"type":"boolean","description":"Enable ConnectID Verification","example":"false"},"isInternal":{"type":"boolean","description":"Public Visibility of the form","example":true},"templateId":{"type":"string","description":"Id for the template used to create the card","example":"test_business"},"status":{"type":"string","description":"Status of the form","example":"Deleted"},"mailchimpSettings":{"type":"object","description":"Form specific mailchimp settings","properties":{"enabled":{"type":"boolean","description":"Enable mailchimp invitation emails","example":false},"eventName":{"type":"string","description":"journey to trigger in mailchimp","example":"my_event"},"mergeFieldMapping":{"type":"object","description":"Additional merge field mappings to card data","additionalProperties":true},"tags":{"type":"array","items":{"$ref":"#/components/schemas/MailchimpTagSettings"}}}},"emailProviderSettings":{"type":"object","description":"Email Provider Settings","properties":{"enabled":{"type":"boolean","description":"Enable Email Provider invitation emails","example":false},"sendEmail":{"type":"boolean","description":"Whether Litecard triggers send email or just updates profile","example":false},"providerName":{"type":"string","description":"Name of Email Provider","example":"BRAZE"},"campaignName":{"type":"string","description":""},"campaignId":{"type":"string","description":""},"mergeFieldMapping":{"type":"object","description":"Additional merge field mappings to card data","additionalProperties":true},"contactFieldMapping":{"type":"object","description":"Additional merge field mappings to update contact data","additionalProperties":true}},"additionalProperties":true},"contactValidationSettings":{"type":"object","description":"Contact Validation Settings","properties":{"providerName":{"type":"string","description":"Name of Contact Validation Provider","example":"CTG"},"types":{"type":"array","description":"Types of contact validation","items":{"type":"string","enum":["EMAIL","PHONE"]},"example":["EMAIL","PHONE"]},"enabled":{"type":"boolean","description":"Enable Contact Validation Settings","example":true}},"required":["providerName"]},"tacURL":{"type":"string","description":"URL of terms and conditions page","example":"https://example.com"},"smsEnabled":{"type":"boolean","description":"Flag to determine if we are to send SMS invitation or not","example":true},"paymentRequired":{"type":"boolean","description":"Whether this form belongs to a template that requires stripe payment","example":false},"hideForm":{"type":"boolean","description":"Filter to hide form from private sign up"},"uiConfig":{"$ref":"#/components/schemas/UIConfig"},"customDesign":{"$ref":"#/components/schemas/CustomFormDesignSchema"},"integrations":{"type":"array","description":"Form integration records","items":{"$ref":"#/components/schemas/FormIntegrationRecord"}}}},"FormFieldUsage":{"type":"array","description":"Usage of the form field","items":{"type":"string","enum":["PRIVATE","PUBLIC","EXTERNAL"]}},"FormFields":{"type":"object","properties":{"enabled":{"type":"boolean"},"mappingType":{"type":"string"},"type":{"type":"string","enum":["text","radio","select","phone","textArea","number"]},"format":{"type":"string","description":"Format of the field","example":"DD/MM/YYYY for date inputs","nullable":true},"label":{"type":"string"},"helpText":{"type":"string","description":"Help text to be displayed next to the field label","example":"Date the member joined","nullable":true},"name":{"type":"string"},"rules":{"type":"array","items":{"type":"string"}},"usage":{"$ref":"#/components/schemas/FormFieldUsage"},"value":{"oneOf":[{"type":"string"},{"type":"number"}]},"placeholder":{"type":"string"},"options":{"type":"array","description":"List of options for drop down list","items":{"type":"object","properties":{"title":{"type":"string","description":"Label for the option","example":"Casual"},"value":{"type":"string","description":"Value for the option","example":"templateId"}}}},"defaultValue":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"}]},"isSecure":{"type":"boolean","description":"Indicates if this field contains sensitive/secure data"}},"required":["enabled","mappingType","type","label","name","rules"]},"FormRequest":{"type":"object","properties":{"fields":{"type":"array","items":{"$ref":"#/components/schemas/FormFields"}},"formName":{"type":"string","description":"Name of the form","example":"test form"},"createdBy":{"type":"string","description":"Auth0 Id of the user who created this instance","example":"V1StGXR8_Z5jdHi6B-myT"},"businessId":{"type":"string","description":"Id for the Business that this entity belongs to","example":"LiteCard"},"userType":{"type":"string","description":"user type","example":"staff"},"ttlEnabled":{"type":"boolean","description":"Enable ttl","example":"true"},"ttlPeriod":{"type":"number","description":"Set ttl period","example":"10"},"passwordEnabled":{"type":"boolean","description":"Enable password","example":"true","default":false},"logo":{"type":"string","description":"logo image link url","example":"https://assets-dev.litecard.io/logo.png"},"title":{"type":"string","description":"Title of the form","example":"LiteCard"},"description":{"type":"string","description":"Description of the form","example":"Make your move with LiteCard today."},"style":{"type":"object","properties":{"bgPrimary":{"type":"string","description":"Primary colour for the form background","example":"#1890ff"},"bgSecondary":{"type":"string","description":"Secondary colour for the form background","example":"#1890ff"},"btnPrimary":{"type":"string","description":"Secondary colour for the button","example":"#1890ff"},"btnSecondary":{"type":"string","description":"Secondary colour for the button","example":"#1890ff"},"label":{"type":"string","description":"Label colour for Apple Passes","example":"#f1f0f2"}}},"connectIDEnabled":{"type":"boolean","description":"Enable ConnectID Verification","example":"false"},"isInternal":{"type":"boolean","description":"Public Visibility of the form","example":true},"templateId":{"type":"string","description":"Id for the template used to create the card","example":"test_business"},"status":{"type":"string","description":"Status of the form","example":"ACTIVE","enum":["ACTIVE","INACTIVE","DELETED"]}}},"ImageContent":{"type":"object","properties":{"imageId":{"type":"string","description":"Image Id","example":"bDy_EeG2Ivd7vVrX45jx","pattern":"^[a-zA-Z0-9-_]+$"},"imageType":{"type":"string","description":"Image Type","enum":["icon","logo","strip","thumbnail"]},"content":{"type":"string","description":"base64 encoded string","example":"data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="}}},"ImageUploadResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true},"objectKey":{"type":"string","description":"s3 object key","example":"cryo/bDy_EeG2Ivd7vVrX45jx-logo@3x.png"},"imageUrl":{"type":"string","description":"s3 image url","example":"https://lc-business-assets-dev.s3.ap-southeast-2.amazonaws.com/test-business/logo.png"}}},"ListCardOwnersResponse":{"type":"object","description":"Fields that are returned for each card owner","properties":{"id":{"type":"string","description":"Id of the card","example":"-jJWhjZ1a"},"firstName":{"type":"string","description":"First name of card owner","example":"Ankus Fang"},"lastName":{"type":"string","description":"Last name of card owner","example":"AHVW1qv4I_Teqdu4VjwMA"},"phone":{"type":"string","description":"phoneNumber of the card owner","example":"0409381912"},"email":{"type":"string","description":"Email of the card owner","example":"test@litecard.io"},"userType":{"type":"string","description":"User Type","example":"basic"},"state":{"type":"string","description":"State the user is from ","example":"VIC"}},"required":["id"],"additionalProperties":true},"ListCardsDetail":{"type":"object","description":"Fields that are returned for each passId","properties":{"id":{"type":"string","description":"Id of the card","example":"-jJWhjZ1a"},"downloadId":{"type":"string","description":"DownloadId used for landing page","example":"V1StGXR8_Z5jdHi6B-myT"},"accountName":{"type":"string","description":"Name associated with the pass","example":"John Doe"},"barcodeValue":{"type":"string","description":"Barcode Value of the Card","example":"eqdu4VjAHVW1qv4I_TwMA"},"passType":{"type":"string","description":"Pass Type","example":"VISIT"},"appleLink":{"type":"string","description":"Link to apple pass","example":"https://litecard-demo-pkpass.s3-ap-southeast-2.amazonaws.com/coldbrew.pkpass"},"googleLink":{"type":"string","description":"Link to google pass","example":"https://pay.google.com/gp/v/save/eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJsaXRlY2FyZC10ZXN0QGNsZXZlci1hYmJleS0zMDg1MDcuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJhdWQiOiJnb29nbGUiLCJ0eXAiOiJzYXZldG9hbmRyb2lkcGF5IiwiaWF0IjoxNjIwMTE0NDkxLCJwYXlsb2FkIjp7ImxveWFsdHlPYmplY3RzIjpbeyJpZCI6IjMzODgwMDAwMDAwMTA2MDYyNDEuTE9ZQUxUWV9PQkpFQ1RfNTRkYmMwNTUtOWRlMy00YWZjLTg0YmUtZDhiNTZjN2U5Mzc5IiwiY2xhc3NJZCI6IjMzODgwMDAwMDAwMTA2MDYyNDEuTE9ZQUxUWV9DTEFTU185OTliOTVlYi00ZDVjLTQzZDEtOGFhOC05Zjg1ZjI0YzlmODQiLCJzdGF0ZSI6IkFDVElWRSIsImFjY291bnROYW1lIjoiSmFzb24iLCJhY2NvdW50SWQiOiIxMjMxMjMiLCJiYXJjb2RlIjp7ImFsdGVybmF0ZVRleHQiOiIxMjMxMjMiLCJ2YWx1ZSI6IjEyMzEyMyIsInR5cGUiOiJDT0RFXzEyOCJ9LCJsb2NhdGlvbnMiOlt7ImxhdGl0dWRlIjoiLTM3LjgwNjIzMTU2ODQyODgxIiwibG9uZ2l0dWRlIjoiMTQ0Ljk4OTkwMjQwODE0ODcifV0sInZhbGlkVGltZUludGVydmFsIjp7InN0YXJ0Ijp7ImRhdGUiOiIyMDIxLTA0LTIwVDAwOjAwOjAwLjAwKzEwOjAwIn0sImVuZCI6eyJkYXRlIjoiMjAyNS0wNC0yMFQyMzo1OTowMC4wMCsxMDowMCJ9fSwibGlua3NNb2R1bGVEYXRhIjp7InVyaXMiOlt7InVyaSI6InRlbDo2NTA1NTU1NTU1IiwiZGVzY3JpcHRpb24iOiJNb2JpbGUgTnVtYmVyIn0seyJ1cmkiOiJtYWlsdG86am9obmRvZUBsaXRlY2FyZC5jb20uYXUiLCJkZXNjcmlwdGlvbiI6IkVtYWlsIn1dfX1dfSwib3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjgwODAiXX0.bR5nSNqMPH6KUwiaqMP-E-nlyjzllOR9-rxUCVgPhqvra_qZ54kc3ZHX64LyViABgSbLIqFEG_EsfA9FCVunAs3ikr1yB5Tk3WiwQ13zt-rH7443_XTI078mtzESl930AL5mVhyrteTMfrhHHV0rrzChXDPgqBjPzsaqs4oGIeW7bVu7S1SC_M8EC-pxTUwqkvvv17xpBVBY_BKzDaK1uEykqdQp2XFHjjRT_esBc6xbiMTDGz11sp395v0RO-9jpp4z-L5igi7prFDgm7ZWBcbSZSKMBckp1lzctLIZ3jidJ9pg-lwgaZyyksmWWnDkVFKE0CWAQV3wFg6M2PXXyA"},"templateId":{"type":"string","description":"Template id of the card","example":"rnb-patron"},"dataFields":{"type":"object","description":"Data Fields of the pass","properties":{"memberSince":{"type":"string","description":"Date time when becoming a member","example":"2021-10-11T07:00:15.075Z"},"fullName":{"type":"string","description":"Full name of the pass","example":"Test name"},"numEntriesRemaining":{"type":"number","description":"Numbers of entries remaining","example":10},"userType":{"type":"string","description":"User type","example":"Patron"},"position":{"type":"string","description":"Position for the user","example":"Staff"}},"additionalProperties":true},"dataFieldHistory":{"$ref":"#/components/schemas/CardDataFieldHistory"},"formId":{"type":"string","description":"Id for field input form","example":"00000001"},"cardOwnerId":{"type":"string","description":"Card owner Id","example":"eJoGyeOMcyd1jnfPSuTvH"}},"required":["id","formId","cardOwnerId","accountName","appleLink","googleLink","templateId"]},"ListFormsResult":{"type":"array","description":"List Forms Schema","items":{"type":"object","description":"Fields that are returned for each formId","properties":{"id":{"type":"object","description":"formId","properties":{"createdAt":{"type":"string","description":"Create date of the form","example":"2021-08-11T03:15:56.860Z"},"fields":{"type":"array","items":{"$ref":"#/components/schemas/FormFields"}},"formName":{"type":"string","description":"Name of the form","example":"test form"},"createdBy":{"type":"string","description":"Auth0 Id of the user who created this instance","example":"V1StGXR8_Z5jdHi6B-myT"},"businessId":{"type":"string","description":"Id for the Business that this entity belongs to","example":"LiteCard"},"ttlEnabled":{"type":"boolean","description":"Enable ttl","example":"true"},"ttlPeriod":{"type":"number","description":"Set ttl period","example":"10"},"updatedAt":{"type":"string","description":"Update date of the form","example":"2021-08-11T03:15:56.860Z"},"passwordEnabled":{"type":"boolean","description":"Enable password","example":"true"},"logo":{"type":"string","description":"logo image link url","example":"https://assets-dev.litecard.io/logo.png"},"title":{"type":"string","description":"Title of the form","example":"LiteCard"},"description":{"type":"string","description":"Description of the form","example":"Make your move with LiteCard today."},"style":{"type":"object","properties":{"bgPrimary":{"type":"string","description":"Primary colour for the form background","example":"#1890ff"},"bgSecondary":{"type":"string","description":"Secondary colour for the form background","example":"#1890ff"},"btnPrimary":{"type":"string","description":"Secondary colour for the button","example":"#1890ff"},"btnSecondary":{"type":"string","description":"Secondary colour for the button","example":"#1890ff"}}},"connectIDEnabled":{"type":"boolean","description":"Enable ConnectID Verification","example":"false"}},"example":"-jJWhjZ1a"}}}},"ListPassesResult":{"type":"array","description":"List Passes Schema","items":{"type":"object","description":"Fields that are returned for each passId","properties":{"id":{"type":"object","description":"passId","properties":{"accountName":{"type":"string","description":"Name associated with the pass","example":"John Doe"},"downloadId":{"type":"string","description":"DownloadId used for landing page","example":"V1StGXR8_Z5jdHi6B-myT"},"createdAt":{"type":"string","description":"Date when pass was first created, in ISO-8601 format","example":"2021-07-01T12:13:20.159Z"},"updatedAt":{"type":"string","description":"Date when pass was last updated, in ISO-8601 format","example":"2021-07-08T02:48:50.399Z"},"appleLink":{"type":"string","description":"Link to apple pass","example":"https://litecard-demo-pkpass.s3-ap-southeast-2.amazonaws.com/coldbrew.pkpass"},"googleLink":{"type":"string","description":"Link to google pass","example":"https://pay.google.com/gp/v/save/eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJsaXRlY2FyZC10ZXN0QGNsZXZlci1hYmJleS0zMDg1MDcuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJhdWQiOiJnb29nbGUiLCJ0eXAiOiJzYXZldG9hbmRyb2lkcGF5IiwiaWF0IjoxNjIwMTE0NDkxLCJwYXlsb2FkIjp7ImxveWFsdHlPYmplY3RzIjpbeyJpZCI6IjMzODgwMDAwMDAwMTA2MDYyNDEuTE9ZQUxUWV9PQkpFQ1RfNTRkYmMwNTUtOWRlMy00YWZjLTg0YmUtZDhiNTZjN2U5Mzc5IiwiY2xhc3NJZCI6IjMzODgwMDAwMDAwMTA2MDYyNDEuTE9ZQUxUWV9DTEFTU185OTliOTVlYi00ZDVjLTQzZDEtOGFhOC05Zjg1ZjI0YzlmODQiLCJzdGF0ZSI6IkFDVElWRSIsImFjY291bnROYW1lIjoiSmFzb24iLCJhY2NvdW50SWQiOiIxMjMxMjMiLCJiYXJjb2RlIjp7ImFsdGVybmF0ZVRleHQiOiIxMjMxMjMiLCJ2YWx1ZSI6IjEyMzEyMyIsInR5cGUiOiJDT0RFXzEyOCJ9LCJsb2NhdGlvbnMiOlt7ImxhdGl0dWRlIjoiLTM3LjgwNjIzMTU2ODQyODgxIiwibG9uZ2l0dWRlIjoiMTQ0Ljk4OTkwMjQwODE0ODcifV0sInZhbGlkVGltZUludGVydmFsIjp7InN0YXJ0Ijp7ImRhdGUiOiIyMDIxLTA0LTIwVDAwOjAwOjAwLjAwKzEwOjAwIn0sImVuZCI6eyJkYXRlIjoiMjAyNS0wNC0yMFQyMzo1OTowMC4wMCsxMDowMCJ9fSwibGlua3NNb2R1bGVEYXRhIjp7InVyaXMiOlt7InVyaSI6InRlbDo2NTA1NTU1NTU1IiwiZGVzY3JpcHRpb24iOiJNb2JpbGUgTnVtYmVyIn0seyJ1cmkiOiJtYWlsdG86am9obmRvZUBsaXRlY2FyZC5jb20uYXUiLCJkZXNjcmlwdGlvbiI6IkVtYWlsIn1dfX1dfSwib3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjgwODAiXX0.bR5nSNqMPH6KUwiaqMP-E-nlyjzllOR9-rxUCVgPhqvra_qZ54kc3ZHX64LyViABgSbLIqFEG_EsfA9FCVunAs3ikr1yB5Tk3WiwQ13zt-rH7443_XTI078mtzESl930AL5mVhyrteTMfrhHHV0rrzChXDPgqBjPzsaqs4oGIeW7bVu7S1SC_M8EC-pxTUwqkvvv17xpBVBY_BKzDaK1uEykqdQp2XFHjjRT_esBc6xbiMTDGz11sp395v0RO-9jpp4z-L5igi7prFDgm7ZWBcbSZSKMBckp1lzctLIZ3jidJ9pg-lwgaZyyksmWWnDkVFKE0CWAQV3wFg6M2PXXyA"},"dataFields":{"type":"object","description":"Data Fields of the pass","properties":{"memberSince":{"type":"string","description":"Date time when becoming a member","example":"2021-10-11T07:00:15.075Z"},"fullName":{"type":"string","description":"Full name of the pass","example":"Test name"},"numEntriesRemaining":{"type":"number","description":"Numbers of entries remaining","example":10},"userType":{"type":"string","description":"User type","example":"Patron"},"position":{"type":"string","description":"Position for the user","example":"Staff"}},"additionalProperties":true},"dataFieldHistory":{"$ref":"#/components/schemas/CardDataFieldHistory"},"formId":{"type":"string","description":"Id for field input form","example":"form123"}},"example":"00000001"}}}},"ListTemplates":{"type":"array","description":"List Templates Schema","items":{"$ref":"#/components/schemas/Template"}},"LogoPositionContentAlignment":{"type":"object","description":"Controls the horizontal alignment of content per device type","properties":{"desktop":{"type":"string","description":"Content alignment on desktop","enum":["left","center"],"default":"left"},"mobile":{"type":"string","description":"Content alignment on mobile","enum":["left","center"],"default":"left"}}},"OnboardMailchimpBody":{"type":"object","properties":{"accessToken":{"type":"string","description":"Mailchimp access token","example":"d657...a926-us14"},"server":{"type":"string","description":"Mailchimp server","example":"us14"},"fromEmail":{"type":"string","description":"Mailchimp email to send from","example":"test@example.com"},"audience":{"type":"string","description":"Name of the Mailchimp Audience","example":"Litecard"},"additionalMergeFields":{"type":"array","description":"Business customisable merge fields or segments","items":{"type":"object","properties":{"name":{"type":"string","description":"Segment Name/Merge Field","example":"postCode"},"helpText":{"type":"string","description":"Helpful text to show in mailchimp UI for Segment","example":"The Post Code of the customer"},"tag":{"type":"string","description":"Tag to use","example":"POSTCODE","maxLength":10,"pattern":"^[A-Z0-9]+$"}},"required":["name","helpText","tag"]}}}},"PaymentAndSignUpResponseSchema":{"type":"object","properties":{"cardId":{"type":"string","description":"Id of created card","example":"abc123"},"checkoutUrl":{"type":"string","description":"URL to redirect to continue payment","example":"https://checkout.url"},"success":{"type":"boolean","description":"Returns whether the request was successful","example":true},"downloadId":{"type":"string","description":"Id used for hosted litecard landing page e.g. https://app.dev.litecard.io/welcome/?id=5c_Wc9h-WCng0oxe8nHNn","example":"5c_Wc9h-WCng0oxe8nHNn"}}},"PrivateResendPassRequest":{"type":"object","properties":{"cardId":{"type":"string","description":"Card Id","example":"00000001"},"formId":{"type":"string","description":"Id for field input form","example":"00000001"},"cardOwnerId":{"type":"string","description":"Card Owner Id","example":"00000001"},"emailTemplateType":{"type":"string","description":"Email Template Type","example":"litecardpass"},"emailType":{"type":"string","description":"Type of Email","enum":["STANDARD","MARKETING"],"example":"STANDARD"},"subject":{"type":"string","description":"Subject of Email","example":"LiteCard Invitation"},"sendAllNotDownloaded":{"type":"boolean","description":"Send to all users who have not downloaded the pass","example":true},"templateId":{"type":"string","description":"Litecard Template Id","example":"E9t4k_YUvx_bejKfjeUs4"},"emailInvitationEnabled":{"type":"boolean","description":"Pass invitation will be sent via email","example":true},"smsInvitationEnabled":{"type":"boolean","description":"Pass invitation will be sent via SMS","example":true}}},"PrivateSignUpRequestBody":{"type":"object","description":"Issue one pass from a template.\n\nTwo halves of the payload are worth keeping straight:\n\n- `cardPayload` is the **data** on this pass: the field values an owner sees, and their contact details.\n- `templateOverrides` is the **design** of this pass: colours, images, locations, barcode.\n\nChange what the pass *says* with the first; change how it *looks* with the second. Anything you leave out of both comes from the template.\n\nThe response carries a `downloadId`. Send the owner to its download page and one tap adds the pass to their wallet.","properties":{"templateId":{"type":"string","description":"Id for the apple/google pass template used to create the card.","example":"test_business"},"tier":{"type":"string","description":"For multi tiered templates. This is to select which tier to build","example":"GOLD"},"externalId":{"type":"string","description":"For use when card is created using an external platform. External ID may be used to reference the card","example":"GOLD"},"cardPayload":{"$ref":"#/components/schemas/BaseCardPayload"},"options":{"$ref":"#/components/schemas/SignUpOptions"},"templateOverrides":{"$ref":"#/components/schemas/TemplateOverridesV1"}},"additionalProperties":false,"required":["templateId","cardPayload"]},"Profile":{"type":"object","properties":{"id":{"type":"string","description":"Business Id","example":"CryoGym"},"businessName":{"type":"string","description":"Business name","example":"Cryo gym"},"subBusinessName":{"type":"string","description":"Name to show on sub business selection","example":"Cryo gym - Sub 1"},"email":{"type":"string","description":"Business email","example":"Ankus Fang"},"postCode":{"type":"string","description":"Business postcode","example":"CryoGym"},"state":{"type":"string","description":"Business state","example":"AHVW1qv4I_Teqdu4VjwMA"},"logoUrl":{"type":"string","description":"Business logo image file","example":"https://assets.dev.litecard.io/Logo-Badge_3.png"},"overrideLitecardLogoUrl":{"type":"string","description":"Override LiteCard logo URL","example":"https://assets.dev.litecard.io/Logo-Badge_3.png"},"templateCount":{"type":"number","description":"Current total number of templates a user has.","example":"3"},"templateLimit":{"type":"number","description":"Maximum amount of templates this user can have. A templateLimit of -1 means the user can have unlimited templates.","example":"12"},"cardCount":{"type":"number","description":"Active passes count","example":"12"},"totalCardCount":{"type":"number","description":"Total passes count","example":"12"},"downloadedApplePassCount":{"type":"number","description":"Current total number of apple pass downloads a business has.","example":"12"},"downloadedGooglePassCount":{"type":"number","description":"Current total number of google pass downloads a business has.","example":"12"},"cardLimit":{"type":"number","description":"Card limit left","example":"12"},"cardOwnerCount":{"type":"number","description":"Current total number of card owners a user has.","example":"100"},"tier":{"type":"string","description":"tier for the business","example":"BUSINESS"},"createdAt":{"type":"string","description":"Date when pass was first created, in ISO-8601 format","example":"2021-08-11T03:15:56.860Z"},"updatedAt":{"type":"string","description":"Date when pass was last updated, in ISO-8601 format","example":"2021-08-1T03:15:56.860Z"},"stripeCustomerId":{"type":"string","description":"User's stripe customer account","example":"cus_KozWy3MZchR5DY"},"tableConfig":{"$ref":"#/components/schemas/TableConfig"},"isMasterBusiness":{"type":"boolean","description":"Is the account a master account","example":true},"masterBusinessId":{"type":"string","description":"Master business id","example":"master-business-id"},"subBusinesses":{"type":"array","description":"Sub businesses","items":{"$ref":"#/components/schemas/SubBusinessSummary"}},"subBusinessRoles":{"$ref":"#/components/schemas/SubBusinessRoles"},"uiConfig":{"$ref":"#/components/schemas/UIConfig"},"webhooks":{"type":"array","items":{"$ref":"#/components/schemas/CustomerWebhook"}},"useEnhancedNotificationSend":{"type":"boolean","description":"Whether to use the enhanced notification sender for sending notifications","example":true},"settings":{"$ref":"#/components/schemas/BusinessSettings"}}},"PublicResendPassRequest":{"type":"object","properties":{"email":{"type":"string","description":"Email","example":"johndoe@example.com"},"phone":{"type":"string","description":"Email","example":"johndoe@example.com"},"formId":{"type":"string","description":"Id for field input form","example":"00000001"}},"required":["formId"]},"PublicSignUpRequestBody":{"type":"object","properties":{"businessId":{"type":"string","description":"Id for the Business that this entity belongs to","example":"LiteCard"},"templateId":{"type":"string","description":"Id for the apple/google pass template used to create the card.","example":"test_business"},"successUrl":{"type":"string","description":"Page for stripe to redirect to on success","example":"http://localhost:5000/success"},"cancelUrl":{"type":"string","description":"Page for stripe to redirect to on cancel","example":"http://localhost:5000"},"cardPayload":{"$ref":"#/components/schemas/BaseCardPayload"},"options":{"$ref":"#/components/schemas/SignUpOptions"},"templateOverrides":{"$ref":"#/components/schemas/TemplateOverridesV1"}},"additionalProperties":false,"required":["cardPayload","businessId","templateId"]},"RateLimit":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Enabled the rate limit","example":true},"rule":{"type":"object","properties":{"duration":{"type":"number","description":"Time Duration for the limit and unit is seconds","example":300},"count":{"type":"number","description":"Numbers of limit count","example":10}}}}},"RegisterBusinessRequestBody":{"type":"object","properties":{"businessName":{"type":"string","description":"Name of the business","example":"Litecard"},"token":{"type":"string","description":"Token for the business","example":"token"},"isMasterBusiness":{"type":"boolean","description":"Is the business a master business","example":true},"logoImage":{"type":"string","nullable":true,"description":"Business logo image file","example":"base64 encoded image"},"email":{"type":"string","format":"email","description":"Contact email for the business","example":"test@ww.com"},"password":{"type":"string","description":"Password for the business","example":"password"},"dashboardAccess":{"type":"array","description":"Access to the dashboard","items":{"type":"string","description":"Access to the dashboard"}},"appUrls":{"type":"array","description":"Urls of apps allowed","items":{"type":"string","description":"Url of apps allowed","example":"https://app.litecard.io"}},"appId":{"type":"string","description":"App ID requesting to create the business","enum":["com.fynd.ext"]},"tableConfig":{"$ref":"#/components/schemas/TableConfig"}},"required":["businessName","dashboardAccess","appUrls","token"]},"RegisterBusinessResponse":{"type":"object","properties":{"businessId":{"type":"string","description":"Id of the business","example":"Litecard"}}},"Scan":{"type":"object","properties":{"id":{"type":"string","description":"The unique identifier for the scan event.","example":"yiur9CQTcrLKmELe4_enI"},"accountName":{"type":"string","description":"The name of the account associated with the scan.","example":"Danny"},"businessId":{"type":"string","description":"The unique identifier for the business associated with the scan.","example":"eFGbNdZEQHJX8RdnLMtP_"},"cardId":{"type":"string","description":"The unique identifier for the card that was scanned.","example":"4048866019"},"cardOwnerId":{"type":"string","description":"The unique identifier for the owner of the card.","example":"-3xlaPTtLTGnmbKoaiyKH"},"createdAt":{"type":"string","format":"date-time","description":"The date and time when the scan event was created.","example":"2023-07-19T04:36:06.937Z"},"createdBy":{"type":"string","description":"The identifier of the user or system that created the scan event.","example":"auth0|64928a1d73375442becf490d"},"deviceId":{"type":"string","description":"The identifier for the device used to perform the scan, if known.","example":"12345-device-id"},"deviceName":{"type":"string","description":"The name of the device used to perform the scan, if known.","example":"Front Counter POS #1"},"location":{"type":"string","description":"The physical location where the scan took place, if known.","example":"Main Store - POS #1"},"templateId":{"type":"string","description":"The unique identifier for the template associated with the scan event.","example":"HtXloDbbAz7Q-IgT7WpLn"},"updatedAt":{"type":"string","format":"date-time","description":"The date and time when the scan event was last updated.","example":"2023-07-19T04:36:06.937Z"},"userType":{"type":"string","description":"User Type","example":"patron"},"dataFields":{"type":"object","description":"Structured data associated with the scan.","additionalProperties":true,"properties":{"fullName":{"type":"string","description":"The full name as it appears on the card.","example":"Danny Bui"},"intro":{"type":"string","description":"An introductory message or description related to the scan.","example":"Scan or show your digital pass at checkout to earn points, discounts, exclusive deals and access to promotions."},"notificationKey":{"type":"string","description":"A key for managing notifications post-scan.","example":"No new notifications"},"points":{"type":"integer","format":"int32","description":"The number of points associated with this scan.","example":6},"store":{"type":"string","description":"The store or location name where the scan took place.","example":"CHAI CHEE"},"updates":{"type":"string","description":"Information on updates or alerts to be sent to the card owner.","example":"You will receive periodic alerts and notifications through this digital membership or via email."}}},"formId":{"type":"string","description":"The unique identifier for the form used to capture the scan data.","example":"xGScW8kBMGFJfxIoGHg7C"},"scanType":{"type":"string","enum":["SCAN","REDEEM"],"description":"The type of scan event, which can be either a 'SCAN' or a 'REDEEM'.","example":"SCAN"},"status":{"type":"string","description":"The current status of the scan.","enum":["DELETED","INACTIVE","ACTIVE","RESERVED","ERROR"],"example":"ACTIVE"}}},"ScanCheckInRequestBody":{"type":"object","properties":{"cardId":{"type":"string","description":"Scanned Card Id","example":"123456789"},"deviceId":{"type":"string","description":"Device ID of the barcode scanner","example":"DFAS87GgjHJG"},"deviceName":{"type":"string","description":"Scanner Name","example":"CryoGym Frontdoor Scanner"},"location":{"type":"string","description":"Location Name","example":"CryoGym"}},"required":["cardId"]},"ScanRedemptionRequestBody":{"type":"object","properties":{"cardId":{"type":"string","description":"Scanned Card Id","example":"123456789"},"action":{"type":"string","description":"Type of action to be executed","example":"REDEEM_BURGER"}},"required":["cardId"]},"ScanRequestBody":{"type":"object","properties":{"barcode":{"type":"string","description":"Scanned barcode","example":"123456789"}},"required":["barcode"]},"ScanResponseSchema":{"type":"object","properties":{"card":{"type":"object","properties":{"id":{"type":"string","description":"ID of the Pass","example":"7048582966"},"downloadId":{"type":"string","description":"DownloadId used for landing page","example":"V1StGXR8_Z5jdHi6B-myT"},"appleLink":{"type":"string","description":"Url for the apple pass","example":"https://litecard-demo-pkpass.s3-ap-southeast-2.amazonaws.com/coldbrew.pkpass"},"businessId":{"type":"string","description":"ID of the Business","example":"business123"},"userType":{"type":"string","description":"Type of User","example":"employee"},"createdAt":{"type":"string","description":"Date Time card was created","example":"2021-11-05T04:25:42.676Z"},"googlePassId":{"type":"string","description":"Id of google pass","example":"1231244"},"authToken":{"type":"string","description":"Internal Use Only. Apple Device Auth Token","example":"dscxdsf234"},"passType":{"type":"string","description":"Mobile wallet pass type","example":"VISIT"},"googleLink":{"type":"string","description":"Url of google pass","example":"https://pay.google.com/gp/v/save/eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJsaXRlY2FyZC10ZXN0QGNsZXZlci1hYmJleS0zMDg1MDcuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJhdWQiOiJnb29nbGUiLCJ0eXAiOiJzYXZldG9hbmRyb2lkcGF5IiwiaWF0IjoxNjIwMTE0NDkxLCJwYXlsb2FkIjp7ImxveWFsdHlPYmplY3RzIjpbeyJpZCI6IjMzODgwMDAwMDAwMTA2MDYyNDEuTE9ZQUxUWV9PQkpFQ1RfNTRkYmMwNTUtOWRlMy00YWZjLTg0YmUtZDhiNTZjN2U5Mzc5IiwiY2xhc3NJZCI6IjMzODgwMDAwMDAwMTA2MDYyNDEuTE9ZQUxUWV9DTEFTU185OTliOTVlYi00ZDVjLTQzZDEtOGFhOC05Zjg1ZjI0YzlmODQiLCJzdGF0ZSI6IkFDVElWRSIsImFjY291bnROYW1lIjoiSmFzb24iLCJhY2NvdW50SWQiOiIxMjMxMjMiLCJiYXJjb2RlIjp7ImFsdGVybmF0ZVRleHQiOiIxMjMxMjMiLCJ2YWx1ZSI6IjEyMzEyMyIsInR5cGUiOiJDT0RFXzEyOCJ9LCJsb2NhdGlvbnMiOlt7ImxhdGl0dWRlIjoiLTM3LjgwNjIzMTU2ODQyODgxIiwibG9uZ2l0dWRlIjoiMTQ0Ljk4OTkwMjQwODE0ODcifV0sInZhbGlkVGltZUludGVydmFsIjp7InN0YXJ0Ijp7ImRhdGUiOiIyMDIxLTA0LTIwVDAwOjAwOjAwLjAwKzEwOjAwIn0sImVuZCI6eyJkYXRlIjoiMjAyNS0wNC0yMFQyMzo1OTowMC4wMCsxMDowMCJ9fSwibGlua3NNb2R1bGVEYXRhIjp7InVyaXMiOlt7InVyaSI6InRlbDo2NTA1NTU1NTU1IiwiZGVzY3JpcHRpb24iOiJNb2JpbGUgTnVtYmVyIn0seyJ1cmkiOiJtYWlsdG86am9obmRvZUBsaXRlY2FyZC5jb20uYXUiLCJkZXNjcmlwdGlvbiI6IkVtYWlsIn1dfX1dfSwib3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjgwODAiXX0.bR5nSNqMPH6KUwiaqMP-E-nlyjzllOR9-rxUCVgPhqvra_qZ54kc3ZHX64LyViABgSbLIqFEG_EsfA9FCVunAs3ikr1yB5Tk3WiwQ13zt-rH7443_XTI078mtzESl930AL5mVhyrteTMfrhHHV0rrzChXDPgqBjPzsaqs4oGIeW7bVu7S1SC_M8EC-pxTUwqkvvv17xpBVBY_BKzDaK1uEykqdQp2XFHjjRT_esBc6xbiMTDGz11sp395v0RO-9jpp4z-L5igi7prFDgm7ZWBcbSZSKMBckp1lzctLIZ3jidJ9pg-lwgaZyyksmWWnDkVFKE0CWAQV3wFg6M2PXXyA"},"samsungLink":{"type":"string","description":"Url of samsung pass","example":"https://a.swallet.link/atw/656147182764415319#Clip?pdata=sIgHCzIwM9g"},"templateId":{"type":"string","description":"Id for the template used to create the card","example":"test_business"},"dataFields":{"type":"object","properties":{"exampleDataField":{"type":"string","description":"Example Value","example":"example value"},"fullName":{"type":"string","description":"Full name of owner of card","example":"John Doe"}}},"dataFieldHistory":{"$ref":"#/components/schemas/CardDataFieldHistory"},"formId":{"type":"string","description":"Id for field input form","example":"00000001"},"updatedAt":{"type":"string","description":"","example":"2021-11-05T04:25:42.676Z"},"pkPassId":{"type":"string","description":"Id of Apple Pass","example":"pass.au.com.litecard.dev.q124234"},"barcodeValue":{"type":"string","description":"what the barcode value is","example":"5434435"},"appleStatus":{"type":"string","description":"Status of card inside of Apple Wallet","example":"ACTIVE"},"googleStatus":{"type":"string","description":"Status of card inside of Google Wallet","example":"INACTIVE"},"samsungStatus":{"type":"string","description":"Status of card inside of Samsung Wallet","example":"ACTIVE"},"cardOwnerId":{"type":"string","description":"Id of the card owner","example":"asb123"},"status":{"type":"string","description":"Activation Status of the card","enum":["ACTIVE","INACTIVE","DELETED","RESERVED","ERROR"],"example":"INACTIVE"},"expiry":{"type":"string","description":"Expiry of card, in ISO-8601 format","example":"2021-08-11T03:15:56.860Z"},"barcodeLink":{"type":"string","description":"Link to the barcode image","example":"https://example.com/blah.png"},"disableQR":{"type":"boolean","description":"Disable QR code on Welcome Page","example":true},"stripeCustomerId":{"type":"string","description":"stripeCustomerId for subscription passes","example":"str_123"},"disableDownload":{"type":"boolean","description":"Disable download link if pass is already activated. Default: false","example":true},"tier":{"type":"string","description":"Membership tier of the card","example":"SILVER"},"numOfScans":{"type":"integer","description":"Number of times the card has been scanned","example":0,"default":0}},"required":["templateId","formId"]},"cardOwner":{"type":"object","description":"Fields that are returned for each card owner","properties":{"id":{"type":"string","description":"Id of the card","example":"-jJWhjZ1a"},"firstName":{"type":"string","description":"First name of card owner","example":"Ankus Fang"},"lastName":{"type":"string","description":"Last name of card owner","example":"AHVW1qv4I_Teqdu4VjwMA"},"phone":{"type":"string","description":"phoneNumber of the card owner","example":"0409381912"},"email":{"type":"string","description":"Email of the card owner","example":"test@litecard.io"},"userType":{"type":"string","description":"User Type","example":"basic"},"state":{"type":"string","description":"State the user is from ","example":"VIC"}},"required":["id"],"additionalProperties":true},"actions":{"type":"object","additionalProperties":{"type":"object","properties":{"condition":{"$ref":"#/components/schemas/TemplateActionCondition"},"action":{"$ref":"#/components/schemas/TemplateActionParams"},"label":{"type":"string","description":"Name of the Action","example":"Redeem Burger"}},"required":["condition","action"]}}},"required":["card","cardOwner"]},"SendNotificationRequestBody":{"type":"object","properties":{"title":{"type":"string","description":"Title of the Notification","example":"Friday Sales"},"message":{"type":"string","description":"Message Body of the Notification","example":"Buy 2 Get The 3rd 20% Off"},"isPushNotification":{"type":"boolean","description":"Toggle for Sending Apple Push Notifications","example":true},"emailType":{"type":"string","description":"Type of Email","enum":["STANDARD","MARKETING"],"example":"MARKETING"}}},"SignUpOptions":{"type":"object","properties":{"downloadId":{"type":"string","description":"Optional. Specify the downloadId that should be used. This Id can be used to get the download urls for the created card","example":null,"nullable":true},"password":{"type":"string","description":"Optional. For password protected public sign ups","example":"password123"},"emailType":{"type":"string","description":"Optional. Senders email address, standard is used for most cases unless a marketing email address has been set up","enum":["STANDARD","MARKETING"],"example":"STANDARD","default":"STANDARD"},"emailInvitationEnabled":{"type":"boolean","description":"Flag to send an email invitation with Apple/Google wallet passes to the user","example":false},"emailTemplate":{"type":"string","description":"Select which email template option to use.","example":"litecardpass"},"smsInvitationEnabled":{"type":"boolean","description":"Flag to send an sms invitation with Apple/Google wallet passes to the user","example":false},"smsTemplate":{"type":"string","description":"Select which email template option to use.","example":"litecardpass"},"sourceEmail":{"type":"string","description":"IF not using default from domain, specify what from address to use","example":"tarfish@litecard.io","enum":["tarfish@litecard.io","donotreply@shakewellwallet.com","noreply@lune.litecard.io"]},"noPI":{"type":"boolean","description":"Flag to disable requirement for Email or Phone in card payload. Default: false","example":true},"fastCreate":{"type":"boolean","description":"Creates card using lazy create method","example":true},"tier":{"type":"string","description":"Tier to use for multi tiered templates, currently this is only supported for language tiered templates","example":"EN"}}},"SignUpResponseSchema":{"type":"object","properties":{"cardId":{"type":"string","description":"Id of created card","example":"abc123"},"success":{"type":"boolean","description":"Returns whether the request was successful","example":true},"downloadId":{"type":"string","description":"Id used for hosted litecard landing page e.g. https://app.dev.litecard.io/welcome/?id=5c_Wc9h-WCng0oxe8nHNn","example":"5c_Wc9h-WCng0oxe8nHNn"},"redirectUrl":{"type":"string","description":"Redirect Url","example":"http://localhost:8000/"},"appleLink":{"type":"string","description":"URL of the apple pass","example":"https://litecard-demo-pkpass.s3-ap-southeast-2.amazonaws.com/coldbrew.pkpass"},"googleLink":{"type":"string","description":"URL of the google pass","example":"https://pay.google.com..."}}},"SpinToWinResponse":{"type":"object","properties":{"prize":{"type":"object","description":"Prize for the spin to win result","example":"FREE_BURGER","properties":{"item":{"type":"string","description":"Prize for the spin to win result","example":"FREE_BURGER"},"index":{"type":"number","description":"Index of the selected prize item","example":"FREE_BURGER"}}},"prizeOptions":{"type":"array","items":{"type":"string"}},"downloadId":{"type":"string","description":"DownloadId used for reward page","example":"V1StGXR8_Z5jdHi6B-myT"}},"additionalProperties":true},"StandardAddress":{"type":"object","description":"Normalized postal address from Google Places or similar provider.","properties":{"@type":{"type":"string","description":"Discriminator for field kind.","enum":["address"],"example":"address"},"subPremise":{"type":"string","description":"Unit / Level/ Apartment / Suite number","example":"Unit 5"},"placeId":{"type":"string","description":"Google place id (e.g., Google Place ID).","example":"ChIJ4-6S2uZC1moRVx70GeLyhvg"},"formattedAddress":{"type":"string","description":"Human-friendly full address.","example":"54 Wellington St, Collingwood VIC 3066, Australia"},"streetNumber":{"type":"string","example":"54"},"streetName":{"type":"string","description":"Street/route name.","example":"Wellington Street"},"suburb":{"type":"string","description":"Sublocality/locality fallback.","example":"Collingwood"},"city":{"type":"string","description":"Locality/postal_town fallback.","example":"Collingwood"},"state":{"type":"string","description":"Admin area level 1 (short).","example":"VIC"},"stateName":{"type":"string","description":"Admin area level 1 (long).","example":"Victoria"},"region":{"type":"string","description":"Admin area level 2 (long).","example":"Yarra City"},"postalCode":{"type":"string","example":"3066"},"country":{"type":"string","description":"Country (long).","example":"Australia"},"countryCode":{"type":"string","description":"ISO 3166-1 alpha-2 country code.","pattern":"^[A-Z]{2}$","example":"AU"},"lat":{"type":"number","example":-37.8071154},"lng":{"type":"number","example":144.9862568}},"required":["@type","placeId","countryCode","postalCode"],"additionalProperties":false},"StripeCheckoutBody":{"type":"object","properties":{"priceId":{"type":"string","description":"priceId for stripe","example":"price_abc123...","pattern":"^price_[A-Za-z0-9]+$"},"origin":{"type":"string","description":"Dashboard origin stripe redirects back to on success or cancel. Must be an allowed litecard origin.","example":"http://localhost:5000","format":"uri"},"quantity":{"type":"number","description":"Set to '1' for one subscription and set to 'undefined' for metered subscription","example":1}}},"StripeCustomerPortalBody":{"type":"object","properties":{"origin":{"type":"string","description":"Set default page for stripe to navigate back","example":"http://localhost:5000"}}},"StripePortalCheckoutBody":{"type":"object","properties":{"priceId":{"type":"string","description":"priceId for stripe","example":"price_abc123...","pattern":"^price_[A-Za-z0-9]+$"},"origin":{"type":"string","description":"Dashboard origin stripe redirects back to on success or cancel. Must be an allowed litecard origin.","example":"http://localhost:5000","format":"uri"},"logoUrl":{"type":"string","description":"Base64 url for business logo","example":"data:image/png;base64"},"businessName":{"type":"string","description":"Name of the business the account to be created","example":"New Business","minLength":1,"maxLength":200},"email":{"type":"string","description":"Email of the account to be created","example":"new-user@email.com","pattern":"^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$","maxLength":320}},"required":["priceId","origin","businessName","email"],"additionalProperties":false},"SubBusinessRoles":{"type":"object","additionalProperties":{"type":"array","description":"Roles associated with the business","items":{"type":"string","example":"admin"}},"description":"An object where keys are business IDs and values are lists of roles","example":{"businessId1":["admin","editor"],"businessId2":["viewer"]}},"SubBusinessSummary":{"type":"object","properties":{"id":{"type":"string","description":"Business Id","example":"CryoGym"},"businessName":{"type":"string","description":"Business name","example":"Cryo gym"},"subBusinessName":{"type":"string","description":"Name to show on sub business selection","example":"Cryo gym - Sub 1"},"logoUrl":{"type":"string","description":"Business logo image file","example":"https://assets.dev.litecard.io/Logo-Badge_3.png"}}},"Template":{"type":"object","description":"Template used to create apple/google wallet passes","allOf":[{"type":"object","properties":{"actions":{"$ref":"#/components/schemas/TemplateActions"}}},{"$ref":"#/components/schemas/TemplateRequest"},{"type":"object","properties":{"id":{"type":"string","description":"Id of the template","example":"V1StGXR8_Z5jdHi6B-myT"},"formId":{"type":"string","description":"Id of the Litecard UI form used  ","example":"V1StGXR8_Z5jdHi6B-myT"},"createdAt":{"type":"string","description":"Date when pass was first created, in ISO-8601 format","example":"2021-08-11T03:15:56.860Z"},"updatedAt":{"type":"string","description":"Date when pass was updated, in ISO-8601 format","example":"2021-08-11T03:15:56.860Z"},"version":{"type":"number","description":"Template Schema Version","example":1},"bulkPassInvitesSentAt":{"type":"string","description":"Date when bulk pass invites were last sent, in ISO-8601 format","example":"2021-08-11T03:15:56.860Z"},"uiConfig":{"$ref":"#/components/schemas/UIConfig"}}}],"required":["dataFields","createdAt"]},"TemplateActionCondition":{"type":"object","properties":{"type":{"type":"string","description":"Type of condition to be run","example":"CHECK_IN"},"optional":{"type":"boolean","description":"Condition to run action on every invocation to the endpoint","example":true},"order":{"type":"integer","description":"Order of actions to be run on each invocation","example":1},"field":{"type":"string","description":"Field name used for conditional checking","example":"points"},"value":{"type":"integer","description":"Value to be compared against during the conditional check","example":10}},"required":["type","optional"]},"TemplateActionParams":{"type":"object","properties":{"type":{"type":"string","description":"Type of action to be executed","example":"INCREMENT"},"amount":{"type":"integer","description":"Value used in conjuction with the action","example":10},"field":{"type":"string","description":"Field name to perform action on","example":"points"},"incrementAmount":{"type":"integer","description":"Used with the INCREMENT_DECREMENT action, amount to be incremented by","example":1},"incrementField":{"type":"string","description":"Used with the INCREMENT_DECREMENT action, field to be incremented","example":"freeBurgerCount"},"decrementAmount":{"type":"integer","description":"Used with the INCREMENT_DECREMENT action, amount to be decremented by","example":10},"decrementField":{"type":"string","description":"Used with the INCREMENT_DECREMENT action, field to be decremented","example":"points"}},"required":["type"]},"TemplateActions":{"type":"object","additionalProperties":{"type":"object","properties":{"condition":{"$ref":"#/components/schemas/TemplateActionCondition"},"action":{"$ref":"#/components/schemas/TemplateActionParams"},"label":{"type":"string","description":"Name of the Action","example":"Redeem Burger"}},"required":["condition","action"]}},"TemplateAndFormRequest":{"type":"object","properties":{"template":{"$ref":"#/components/schemas/TemplateRequest"},"form":{"$ref":"#/components/schemas/FormRequest"}}},"TemplateBarcode":{"type":"object","description":"Barcode Fields of the template","properties":{"barcodeValue":{"type":"string","description":"Value of the Barcode","example":"7048582966","nullable":true,"default":null},"messageEncoding":{"type":"string","description":"Message Encoding of the Barcode","example":"iso-8859-1","nullable":true,"default":null},"type":{"type":"string","description":"Type of the Barcode","example":"CODE_128","nullable":true,"default":null},"altText":{"type":"boolean","description":"Enable having the Pass ID displayed at the bottom of the barcode","example":"1231244","default":true},"enabled":{"type":"boolean","description":"Whether the barcode is enabled","example":true,"default":false}}},"TemplateCardExpiry":{"type":"object","description":"Card Expiry field on Template that details how cards created by the template will expire","oneOf":[{"type":"object","properties":{"expiryType":{"type":"string","description":"Type of expiry, either NEVER, FIXED_DATE, FROM_ACTIVATION ","example":"NEVER","enum":["NEVER"]}}},{"type":"object","properties":{"expiryType":{"type":"string","description":"Type of expiry, either NEVER, FIXED_DATE or FROM_ACTIVATION","example":"FIXED_DATE","enum":["FIXED_DATE"]},"fixedDate":{"type":"string","description":"The fixed date the card produced by this template will expire","example":"2022-01-10T07:00:15.075Z"}}},{"type":"object","properties":{"expiryType":{"type":"string","description":"Type of expiry, either NEVER, FIXED_DATE or FROM_ACTIVATION","example":"FROM_ACTIVATION","enum":["FROM_ACTIVATION"]},"fromActivation":{"type":"object","properties":{"amount":{"type":"string","description":"The amount of time (based on the cardExpiry.fromActivation.measurement field) between the activation of the card produced by this template, and the expiry date","example":"10"},"measurement":{"type":"string","description":"The unit of measurement to calculate the amount of time between the activation of the card produced by this template, and the expiry date","example":"YEARS","enum":["DAYS","MONTHS","YEARS"]}}}}},{"type":"object","properties":{"expiryType":{"type":"string","description":"Type of expiry, either NEVER, FIXED_DATE, FROM_ACTIVATION or FIXED_SCANS","example":"FIXED_SCANS","enum":["FIXED_SCANS"]},"scans":{"type":"number","description":"Number of scans remaining before pass expires","example":1}}}]},"TemplateColours":{"type":"object","description":"Colours of the template","properties":{"background":{"type":"string","description":"Background colour of the template","example":"#ffffff"},"label":{"type":"string","description":"Label colour of the template","example":"#ffffff"},"foreground":{"type":"string","description":"Foreground colour of the template","example":"#ffffff"}}},"TemplateDataFields":{"type":"object","description":"Data Fields of the template","properties":{"appleChangeMessage":{"type":"string","title":"Apple Change Message","description":"This is the formatted string, that will be shown as a push notification message. The format string must contain %@, which is replaced with the field's new content. If no change message is set, the user isn't notified when the field changes.","default":"%@","example":"Gate changed to %@"},"appleDateStyle":{"$ref":"#/components/schemas/DateTimeStyle"},"appleFieldPosition":{"type":"integer","title":"Apple Field Position","description":"The position in the pass structure array. e.g. For a secondary field, a position of 0 means it is first in order to be rendered in that section. Positions start from 0.","default":0,"example":1,"minimum":0},"appleFieldType":{"type":"string","title":"Apple Field Type","description":"The pass structure locations, each value pertains to a certain region/part of the apple pass","default":"","example":"HEADER_FIELD","enum":["PRIMARY_FIELD","HEADER_FIELD","SECONDARY_FIELD","AUXILIARY_FIELD","BACK_FIELD"]},"appleIgnoreTimeZone":{"type":"boolean","title":"Apple Ignore Time Zone","description":"Optional. Always display the time and date in the given time zone, not in the user's current time zone. The default value is false.","default":false,"example":true},"appleAttributedValue":{"type":"string","title":"Apple Attributed Value","description":"Overrides the apple field value key, can contain HTML a tags","example":"<a href='http://google.com'>Google</a>"},"appleIsRelativeDate":{"type":"boolean","title":"Apple Relative Date","description":"Optional. If true, the label's value is displayed as a relative date; otherwise, it is displayed as an absolute date. The default value is false.","default":false,"example":true},"appleTextAlignment":{"type":"string","title":"Apple Text Alignment","description":"Optional. Argument that sets the text alignment of the field text. This value is not allowed on primary or back fields.","default":"PKTextAlignmentNatural","example":"PKTextAlignmentRight","enum":["PKTextAlignmentLeft","PKTextAlignmentCenter","PKTextAlignmentRight","PKTextAlignmentNatural"]},"appleTimeStyle":{"$ref":"#/components/schemas/DateTimeStyle"},"dateField":{"type":"boolean","title":"Date Field","description":"Value is true if the field is a date field","default":false,"example":true},"dateFieldType":{"type":"string","title":"Date Field Type","description":"Type of date field","default":"","example":"DATETIME_NOW","enum":["DATETIME_NOW"]},"defaultValue":{"title":"Default Value","description":"Default field value. Set this value if you want a starting value when you first create a card.","default":"","example":10,"type":"string","nullable":true},"fieldDescription":{"type":"string","description":"Description of the field","example":"This is the full name"},"fieldEnabled":{"type":"boolean","title":"Field Enabled","description":"Value is true if the field is enabled. Default value is true","default":true,"example":false},"fieldLabel":{"type":"string","title":"Field Label","description":"The label of the field","default":"","example":"Member Since"},"fieldType":{"type":"string","title":"Field Type","description":"Type of field, denotes whether the field belongs belongs in the card, or both card & card owner.","default":"","example":"CARD","enum":["CARD","CARD_OWNER_AND_CARD","NONE"]},"fieldValue":{"title":"Field Value","description":"The value of the field. Default value is null as the value will be filled during a create/update process.","default":null,"example":null,"type":"string","nullable":true},"googleFieldType":{"type":"string","nullable":true,"title":"Google Field Type","description":"The type of google field","default":null,"example":"TEXT_MODULE_DATA","enum":["ACCOUNT_NAME","LOYALTY_POINTS","OFFER","TEXT_MODULE_DATA","LINK_MODULE_DATA","MESSAGES","GENERIC",null]},"googleTextModulePosition":{"type":"integer","title":"Google Text Module Field Position","description":"The position in the text modules data array. e.g. A position of 0 means it is first in order to be rendered in that section. Positions start from 0.","default":0,"example":1,"minimum":0},"userCanEdit":{"type":"boolean","title":"User Editable","description":"Value is true if the field can be edited","default":false,"example":true},"frontEndMapping":{"type":"array","items":{"type":"string"},"title":"Front End Mapping","description":"array that decides whether to render field on Scan Check In Page","default":["SCAN"],"example":["SCAN"]}},"required":["fieldLabel"],"additionalProperties":false},"TemplateImages":{"type":"object","description":"Images of the template","properties":{"logo":{"type":"string","description":"URL of uploaded logo image","example":"https://s3bucketlocation/business-name/logo.png"},"hero":{"type":"string","description":"URL of uploaded hero image","example":"https://s3bucketlocation/business-name/hero.png"},"fullHero":{"type":"string","description":"URL of uploaded hero image","example":"https://s3bucketlocation/business-name/hero.png"},"icon":{"type":"string","description":"URL of uploaded icon image","example":"https://s3bucketlocation/business-name/icon.png"},"thumbnail":{"type":"string","description":"URL of uploaded thumbnail image","example":"https://s3bucketlocation/business-name/thumbnail.png"}}},"TemplateNotificationFields":{"type":"object","description":"Notification Fields of the template","properties":{"appleFieldPosition":{"type":"number","description":"Position of the Apple Field","example":0},"userCanEdit":{"type":"boolean","description":"User can edit or not","example":true},"appleChangeMessage":{"type":"string","description":"Apple Change Message","example":"abcde"},"appleDateStyle":{"type":"string","description":"Style of the Apple Date","example":"NOT_USED"},"fieldLabel":{"type":"string","description":"Label of the Field","example":"test label"},"googleFieldType":{"type":"string","description":"Field Type in Google","example":"MESSAGES"},"appleTimeStyle":{"type":"string","description":"Style of the apple Time","example":"NOT_USED"},"fieldValue":{"type":"string","description":"Value of the Field","example":"test value"},"appleFieldType":{"type":"string","description":"Type of the Apple Field","example":"BACK_FIELD"},"appleAttributedValue":{"type":"string","title":"Apple Attributed Value","description":"Overrides the apple field value key, can contain HTML a tags","example":"<a href='http://google.com'>Google</a>"},"appleIgnoreTimeZone":{"type":"boolean","title":"Apple Ignore Time Zone","description":"Optional. Always display the time and date in the given time zone, not in the user's current time zone. The default value is false.","default":false,"example":true},"appleIsRelativeDate":{"type":"boolean","title":"Apple Relative Date","description":"Optional. If true, the label's value is displayed as a relative date; otherwise, it is displayed as an absolute date. The default value is false.","default":false,"example":true},"appleTextAlignment":{"type":"string","title":"Apple Text Alignment","description":"Optional argument that sets the text alignment of the field text. This value is not allowed on primary or back fields.","default":"PKTextAlignmentNatural","example":"PKTextAlignmentRight","enum":["PKTextAlignmentLeft","PKTextAlignmentCenter","PKTextAlignmentRight","PKTextAlignmentNatural"]}}},"TemplateRequest":{"type":"object","properties":{"templateName":{"type":"string","description":"Name of the template","example":"Cherry Loyalty Template"},"isInternal":{"type":"boolean","description":"Public Visibility of the template","example":true},"userType":{"type":"string","description":"User Type","example":"Patron"},"passType":{"type":"string","description":"Pass Type","example":"Visit"},"dataFields":{"type":"object","description":"Data Fields for the pass","additionalProperties":{"$ref":"#/components/schemas/TemplateDataFields"}},"barcode":{"$ref":"#/components/schemas/TemplateBarcode"},"businessId":{"type":"string","description":"Business ID","example":"sample_dev_company"},"notificationFields":{"$ref":"#/components/schemas/TemplateNotificationFields"},"businessName":{"type":"string","description":"Business Name","example":"sample_dev_company"},"rateLimit":{"$ref":"#/components/schemas/RateLimitV1"},"appleWalletSettings":{"allOf":[{"$ref":"#/components/schemas/TemplateWalletSettings"},{"type":"object","properties":{"hideLogoText":{"type":"boolean","description":"Hides the logo text if true","example":true}}}]},"googlePaySettings":{"$ref":"#/components/schemas/TemplateWalletSettings"},"colours":{"$ref":"#/components/schemas/TemplateColours"},"description":{"type":"string","description":"Template description","example":"Test Loyalty Pass"},"status":{"type":"string","description":"Status of the template","example":"ACTIVE","enum":["ACTIVE","INACTIVE","DELETED"]},"images":{"$ref":"#/components/schemas/TemplateImages"},"cardExpiry":{"$ref":"#/components/schemas/TemplateCardExpiry"},"certificate":{"$ref":"#/components/schemas/Certificate"}}},"TemplateResponse":{"type":"object","properties":{"id":{"type":"string","description":"Id of the template","example":"V1StGXR8_Z5jdHi6B-myT"}}},"TemplateWalletSettings":{"type":"object","description":"Data Fields of the template","properties":{"passType":{"type":"string","description":"Type of the Pass","example":"LOYALTY"}}},"UIConfig":{"type":"object","description":"Configuration for the UI","properties":{"signupPage":{"$ref":"#/components/schemas/UIPageSchema"}}},"UIPageSchema":{"type":"object","properties":{"logoUrl":{"type":"string","format":"uri","description":"URL of the logo","example":"https://host/logo.jpg"},"logoPosition":{"$ref":"#/components/schemas/LogoPositionContentAlignment"},"disableQRCode":{"type":"boolean","description":"Disable QR code","example":false},"redirectUrl":{"type":"string","format":"uri","description":"URL to redirect to after sign up","example":"https://host/redirect"},"autoDownloadText":{"type":"string","description":"Text for the qrRedirect screen","example":"Your download should start automatically. Alternatively you can start the download below."},"disableAutoDownload":{"type":"boolean","description":"Disable the automatic wallet redirect on mobile devices. Default: false","example":false},"hintText":{"type":"string","description":"Hint text shown below the download button on mobile and below QR Code on desktop","example":"Tap the button below to add your pass to your wallet."},"orText":{"type":"string","description":"OR text for the qrRedirect screen","example":"OR"},"afterDownloadText":{"type":"string","description":"After download text","example":"If you added your pass, navigate to your mobile wallet to view it."},"retryText":{"type":"string","description":"Button text for retrying the download","example":"Try Again"},"qrText":{"type":"string","description":"Text for the directing the user to scan the QR code","example":"Add to wallet by scanning the QR code"},"footerText":{"type":"string","description":"Text for the directing the user to scan the QR code","example":"This QR code is a link to download your digital pass, not the pass itself."},"backgroundColour":{"type":"string","description":"Background colour of the page","example":"#FFFFFF"},"textColour":{"type":"string","description":"Text colour of the page","example":"#000000"},"faviconPath":{"type":"string","description":"Path to the favicon image","example":"/path/to/favicon.ico"},"documentTitle":{"type":"string","description":"Title of the document","example":"Please enter your title"},"headerBackgroundColour":{"type":"string","description":"Header will be shown with this background colour if provided","example":"#FFFFFF"},"buttonBackgroundColour":{"type":"string","description":"Background colour of the buttons on the page. Falls back to the page text colour if not provided","example":"#000000"},"buttonTextColour":{"type":"string","description":"Text colour of the buttons on the page. Falls back to the page background colour if not provided","example":"#FFFFFF"}},"additionalProperties":false},"UpdateCardRequestBody":{"type":"object","description":"Change a pass that is already in a owner's wallet.\n\nOnly the fields you send change; everything else is left alone. The updated pass is pushed to the device automatically, so the owner sees the new value without re-downloading anything.\n\n`cardPayload` updates the data on the pass. `templateOverrides` updates its design.","properties":{"cardId":{"type":"string","description":"Unique Card Id","example":"-jJWhjZ1a"},"syncStaticFields":{"type":"boolean","description":"Flag to sync static fields","example":true},"tier":{"type":"string","description":"For multi tiered templates. This is to select which tier to build","example":"GOLD"},"cardPayload":{"type":"object","properties":{"exampleProperty":{"type":"string","description":"Example card property, properties are predefined in the card template"}},"additionalProperties":true},"templateOverrides":{"$ref":"#/components/schemas/TemplateOverridesV1"},"notification":{"type":"object","description":"Sends a push notification to the pass owner as part of this update. Omit it and the pass updates silently.","properties":{"title":{"type":"string","description":"Title of the push notification","example":"Points added","minLength":1},"message":{"type":"string","description":"Body of the push notification","example":"You earned 50 points","minLength":1}},"required":["title","message"],"additionalProperties":false}},"additionalProperties":false,"required":["cardId"]},"UpdateCardResponse":{"type":"object","properties":{"cardOwnerUpdate":{"type":"object","description":"Updated fields on the card owner","additionalProperties":true},"cardDataFieldUpdate":{"type":"object","description":"Updated fields on the card","additionalProperties":true},"success":{"type":"boolean","description":"Returns whether the request was successful","example":true,"additionalProperties":false}},"additionalProperties":true},"UpdateProfileBody":{"type":"object","properties":{"businessName":{"type":"string","description":"Business name","example":"Cryo gym"},"subBusinessName":{"type":"string","description":"Name to show on sub business selection","example":"Cryo gym - Sub 1"},"email":{"type":"string","description":"Business email","example":"Ankus Fang"},"postCode":{"type":"string","description":"Business postcode","example":"CryoGym"},"state":{"type":"string","description":"Business state","example":"AHVW1qv4I_Teqdu4VjwMA"},"logoImage":{"type":"string","nullable":true,"description":"Logo image s3 URL","example":"https://assets.litecard.io/logo.png"},"overrideLitecardLogoImage":{"type":"string","nullable":true,"description":"Override LiteCard logo image s3 URL","example":"https://assets.litecard.io/override-logo.png"},"templateCount":{"type":"number","description":"Current total number of templates a user has.","example":"3"},"templateLimit":{"type":"number","description":"Maximum amount of templates this user can have. A templateLimit of -1 means the user can have unlimited templates.","example":"12"},"createdAt":{"type":"string","description":"Date when pass was first created, in ISO-8601 format","example":"2021-08-11T03:15:56.860Z"},"updatedAt":{"type":"string","description":"Date when pass was last updated, in ISO-8601 format","example":"2021-08-1T03:15:56.860Z"},"settings":{"$ref":"#/components/schemas/BusinessSettings"}}},"User":{"type":"object","description":"User object with roles and access details","properties":{"roles":{"type":"array","items":{"type":"string"},"example":["admin","editor"]},"apiAccessAllowed":{"type":"boolean","example":true},"uiDashboardAccess":{"type":"array","items":{"type":"string"},"example":["overview","settings"]},"picture":{"type":"string","example":"https://example.com/avatar.jpg"},"username":{"type":"string","example":"john_doe"},"email":{"type":"string","example":"john@example.com"},"createdAt":{"type":"string","example":"2024-01-10T08:00:00.000Z"},"lastLoginAt":{"type":"string","example":"2024-05-18T12:30:00.000Z"}},"required":["roles","apiAccessAllowed","uiDashboardAccess","picture","username","email","createdAt","lastLoginAt"]},"ValidatePasswordBody":{"type":"object","properties":{"formId":{"type":"string","description":"Id for field input form","example":"V1StGXR8_Z5jdHi6B-myT"},"password":{"type":"string","description":"User's input password","example":"password123"}},"required":["formId","password"]},"VoucherCountV1":{"type":"object","description":"Voucher code counts for a specific field key within a template","properties":{"fieldKey":{"type":"string","description":"The dataField key this count belongs to, or 'default' for single-set templates","example":"offerCode"},"totalCount":{"type":"number","description":"Total number of voucher codes uploaded","example":500},"usedCount":{"type":"number","description":"Number of voucher codes that have been used","example":120},"remainingCount":{"type":"number","description":"Remaining available voucher codes (totalCount - usedCount)","example":380}},"required":["fieldKey","totalCount","usedCount","remainingCount"]},"VoucherUploadGroupV1":{"type":"object","description":"Schema for a voucher upload job group","properties":{"groupId":{"type":"string","example":"JOB#template-1"},"code":{"type":"string","example":"abc123"},"status":{"type":"string","example":"WAITING","enum":["WAITING","IN_PROGRESS","COMPLETED","INVALID_UPLOAD","FAILED"]},"templateId":{"type":"string","example":"template-1"},"jobId":{"type":"string","example":"abc123"},"businessId":{"type":"string","example":"business-1"},"fieldKey":{"type":"string","example":"offer"},"type":{"type":"string","example":"JOB"},"payloadType":{"type":"string","example":"VOUCHER","enum":["VOUCHER","EMAIL","EMAIL_MEMBER"]},"createdAt":{"type":"string","example":"2024-01-01T00:00:00.000Z"},"totalCount":{"type":"number","example":100},"successCount":{"type":"number","example":95},"failedCount":{"type":"number","example":3},"invalidCount":{"type":"number","example":2},"duplicateCount":{"type":"number","example":1}},"required":["groupId","code","status","templateId","jobId","businessId"]},"WelcomeDetailsResponse":{"type":"object","description":"Download Urls for welcome page","properties":{"downloadId":{"type":"string","description":"DownloadId used for landing page","example":"V1StGXR8_Z5jdHi6B-myT"},"appleLink":{"type":"string","description":"Link to apple pass","example":"https://litecard-demo-pkpass.s3-ap-southeast-2.amazonaws.com/coldbrew.pkpass"},"googleLink":{"type":"string","description":"Link to google pass","example":"https://pay.google.com/gp/v/save/eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJsaXRlY2FyZC10ZXN0QGNsZXZlci1hYmJleS0zMDg1MDcuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJhdWQiOiJnb29nbGUiLCJ0eXAiOiJzYXZldG9hbmRyb2lkcGF5IiwiaWF0IjoxNjIwMTE0NDkxLCJwYXlsb2FkIjp7ImxveWFsdHlPYmplY3RzIjpbeyJpZCI6IjMzODgwMDAwMDAwMTA2MDYyNDEuTE9ZQUxUWV9PQkpFQ1RfNTRkYmMwNTUtOWRlMy00YWZjLTg0YmUtZDhiNTZjN2U5Mzc5IiwiY2xhc3NJZCI6IjMzODgwMDAwMDAwMTA2MDYyNDEuTE9ZQUxUWV9DTEFTU185OTliOTVlYi00ZDVjLTQzZDEtOGFhOC05Zjg1ZjI0YzlmODQiLCJzdGF0ZSI6IkFDVElWRSIsImFjY291bnROYW1lIjoiSmFzb24iLCJhY2NvdW50SWQiOiIxMjMxMjMiLCJiYXJjb2RlIjp7ImFsdGVybmF0ZVRleHQiOiIxMjMxMjMiLCJ2YWx1ZSI6IjEyMzEyMyIsInR5cGUiOiJDT0RFXzEyOCJ9LCJsb2NhdGlvbnMiOlt7ImxhdGl0dWRlIjoiLTM3LjgwNjIzMTU2ODQyODgxIiwibG9uZ2l0dWRlIjoiMTQ0Ljk4OTkwMjQwODE0ODcifV0sInZhbGlkVGltZUludGVydmFsIjp7InN0YXJ0Ijp7ImRhdGUiOiIyMDIxLTA0LTIwVDAwOjAwOjAwLjAwKzEwOjAwIn0sImVuZCI6eyJkYXRlIjoiMjAyNS0wNC0yMFQyMzo1OTowMC4wMCsxMDowMCJ9fSwibGlua3NNb2R1bGVEYXRhIjp7InVyaXMiOlt7InVyaSI6InRlbDo2NTA1NTU1NTU1IiwiZGVzY3JpcHRpb24iOiJNb2JpbGUgTnVtYmVyIn0seyJ1cmkiOiJtYWlsdG86am9obmRvZUBsaXRlY2FyZC5jb20uYXUiLCJkZXNjcmlwdGlvbiI6IkVtYWlsIn1dfX1dfSwib3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjgwODAiXX0.bR5nSNqMPH6KUwiaqMP-E-nlyjzllOR9-rxUCVgPhqvra_qZ54kc3ZHX64LyViABgSbLIqFEG_EsfA9FCVunAs3ikr1yB5Tk3WiwQ13zt-rH7443_XTI078mtzESl930AL5mVhyrteTMfrhHHV0rrzChXDPgqBjPzsaqs4oGIeW7bVu7S1SC_M8EC-pxTUwqkvvv17xpBVBY_BKzDaK1uEykqdQp2XFHjjRT_esBc6xbiMTDGz11sp395v0RO-9jpp4z-L5igi7prFDgm7ZWBcbSZSKMBckp1lzctLIZ3jidJ9pg-lwgaZyyksmWWnDkVFKE0CWAQV3wFg6M2PXXyA"},"barcodeLink":{"type":"string","description":"Link to Barcode image","example":"https://example.com/blah"},"samsungLink":{"type":"string","description":"Link to samsung pass","example":"https://a.swallet.link/atw/656147182764415319#Clip?pdata=sIgHCzIwM9g"},"uiConfig":{"$ref":"#/components/schemas/UIConfig"},"disableQR":{"type":"boolean","description":"Disable QR code on Welcome Page","example":true}},"required":["downloadId","appleLink","googleLink"]},"WelcomeOrderDetailsResponse":{"type":"object","description":"Download Urls for welcome order page","required":["downloadId","appleLink"],"properties":{"downloadId":{"type":"string","description":"DownloadId used for landing page","example":"V1StGXR8_Z5jdHi6B-myT"},"appleLink":{"type":"string","description":"Link to apple pass","example":"https://litecard-demo-pkpass.s3-ap-southeast-2.amazonaws.com/coldbrew.pkpass"},"uiConfig":{"$ref":"#/components/schemas/UIConfig"}}},"authenticationRequestBody":{"type":"object","properties":{"username":{"type":"string","description":"Credential: username","example":"user1"},"password":{"type":"string","description":"Credential: password","example":"Ex@Mple5P@ssW0rd"}},"additionalProperties":false},"authenticationResponse":{"type":"object","properties":{"access_token":{"type":"string","description":"JWT Token used for API authentication"},"type":{"type":"string","description":"Type of Token","example":"Bearer"},"expires_in":{"type":"number","description":"Time in seconds before token expires. Defaults to 24 hours.","example":86400}}},"oAuth2ClientCredentialsRequestBody":{"type":"object","properties":{"grant_type":{"type":"string","enum":["client_credentials"],"description":"OAuth2 grant type","example":"client_credentials"},"client_id":{"type":"string","description":"OAuth2 client identifier","example":"my-app-client-id"},"client_secret":{"type":"string","description":"OAuth2 client secret","example":"my-app-client-secret"},"scope":{"type":"string","description":"Optional space-delimited roles to grant, intersected with the client's configured scopes. Recognised values: EMPLOYEE, BUSINESS, ADMIN, MASTER_ADMIN, INTERNAL_ADMIN, SCHEDULE_TASK, USER_ADMIN.","example":"BUSINESS EMPLOYEE"},"business_id":{"type":"string","description":"Business ID to issue the token for. Optional: omit it and the client's configured default_business_id is used, which lets standards-only OAuth2 clients send an unmodified client_credentials request.","example":"business_id"}},"required":["grant_type","client_id","client_secret"],"additionalProperties":true},"responseBody":{"type":"object","properties":{"message":{"type":"string"}}},"AddLocationRequestBody":{"type":"object","properties":{"lat":{"type":"string","description":"Latitude coordinate of location","example":"-37.80690913039505"},"lon":{"type":"string","description":"Longitude coordinate of location","example":"144.986263128836"},"locationName":{"type":"string","description":"Name of location","example":"Litecard HQ"},"address":{"type":"string","description":"Street address of the location. Shown in Samsung Wallet's Geofence Notification location details.","example":"123 Broad Street"},"externalId":{"type":"string","description":"External system identifier for the location","example":"ext-location-456"},"notificationSettings":{"$ref":"#/components/schemas/LocationNotificationSettings"}},"required":["locationName","lon","lat"],"additionalProperties":false},"AppleAppLinkingSettings":{"type":"object","description":"Apple Settings for AppLinking","properties":{"storeIdentifier":{"type":"number","description":"App Store Identifier"}}},"AppleDateTimeStyleV1":{"type":"string","description":"Style of the time to be displayed\n\nSHORT - 11/23/37” or “3:30 PM”.\n\nMEDIUM “Nov 23, 1937” or “3:30:32 PM”.\n\nLONG - “November 23, 1937” or “3:30:32 PM PST”\n\nFULL - “Tuesday, April 12, 1952 AD” or “3:30:42 PM Pacific Standard Time”\n\n","example":"SHORT","enum":["NOT_USED","NONE","SHORT","MEDIUM","LONG","FULL"]},"AppleNumberStyleV1":{"type":"string","description":"Style for the number to be displayed see https://developer.apple.com/documentation/foundation/nsnumberformatterstyle","example":"DECIMAL","enum":["NOT_USED","DECIMAL","PERCENT","SCIENTIFIC","SPELLOUT"]},"BaseNotificationSettings":{"type":"object","description":"Settings for setting up automated notifications","properties":{"name":{"type":"string","description":"Human-readable name for this notification setting","example":"7-day expiry reminder"},"description":{"type":"string","description":"Optional description explaining the purpose of this notification","example":"Sent 7 days before the card expires to remind the owner to renew","nullable":true},"validStartDate":{"type":"string","description":"Start date where this notification is valid","nullable":true},"validEndDate":{"type":"string","description":"End date where this notification is valid","nullable":true},"trigger":{"type":"string","description":"What the delay counts from. AFTER_CREATION and BEFORE_EXPIRY are both worked out when the card is created, forwards from that moment and backwards from cardExpiry respectively. AFTER_DOWNLOAD is worked out the first time the pass is added to an Apple or Google wallet, so nothing is queued for a pass nobody has collected. It fires once: a second device, or a wallet re-registering one we already know about, does not queue another send.","enum":["AFTER_CREATION","BEFORE_EXPIRY","AFTER_DOWNLOAD"]},"delay":{"type":"number","description":"Unit delay before or after the trigger event"},"unit":{"type":"string","description":"Unit of measurement for the delay","enum":["DAYS","HOURS","MINUTES","SECONDS"]},"overrideExpiryAtCreation":{"type":"boolean","description":"BEFORE_EXPIRY only. True when passes are given their expiry at creation instead of inheriting the template's, so the console skips its template-level timing checks. The send time is always derived from the card's own expiry, so this does not change scheduling."},"type":{"type":"string","description":"What type of notification to send","enum":["PN"]},"dataFieldKey":{"type":"string","description":"Key of the push notification field. Default: notificationKey"},"title":{"type":"string","description":"Title of the push notification. Supports placeholders in the format {{<dataField.key>}}"},"message":{"type":"string","description":"Message contents of the push notifications. Supports placeholders in the format {{<dataField.key>}}"},"fallbackTitle":{"type":"string","description":"Used when title placeholders cannot be resolved for a pass. Must not contain placeholders","nullable":true},"fallbackMessage":{"type":"string","description":"Used when message placeholders cannot be resolved for a pass. Must not contain placeholders","nullable":true},"scheduledTime":{"type":"string","description":"Time of day (HH:mm) at which the notification should fire on the calculated trigger date","example":"09:00","nullable":true},"timezone":{"type":"string","description":"IANA timezone identifier used to interpret scheduledTime. Defaults to UTC when omitted.","example":"Australia/Sydney","nullable":true},"createdAt":{"type":"string","description":"ISO-8601 timestamp when this notification setting was created","example":"2026-05-27T09:00:00.000Z"},"updatedAt":{"type":"string","description":"ISO-8601 timestamp when this notification setting was last updated","example":"2026-05-27T09:00:00.000Z"}}},"BaseTemplateV1":{"type":"object","description":"Template used to create apple/google wallet passes","properties":{"name":{"type":"string","description":"Human readable name of the template","example":"Bronze Card"},"cardCount":{"type":"integer","description":"Number of active cards created using this template","example":80},"downloadsCount":{"type":"integer","description":"Number of cards downloaded","example":70},"downloadedApplePassCount":{"type":"integer","description":"Number of cards downloaded on Apple Wallet","example":50},"downloadedGooglePassCount":{"type":"integer","description":"Number of cards downloaded on Google Wallet","example":20},"description":{"type":"string","description":"Template Description","example":"LiteCard Bronze Card"},"businessName":{"type":"string","description":"Name of the business","example":"LiteCard"},"isInternal":{"type":"boolean","description":"Public Visibility of the template/form","example":false},"type":{"type":"string","description":"The Type of Pass","enum":["BUSINESS_CARD","GENERIC","COUPON","EVENT_TICKET"]},"externalId":{"type":"string","description":"External id mapping to third party system","example":"1234"},"settings":{"$ref":"#/components/schemas/SettingsV1"},"stripeSettings":{"$ref":"#/components/schemas/StripeSettings"},"appleWalletSettings":{"$ref":"#/components/schemas/TemplateAppleWalletSettingsV1"},"googleWalletSettings":{"$ref":"#/components/schemas/TemplateGoogleWalletSettingsV1"},"samsungWalletSettings":{"$ref":"#/components/schemas/TemplateSamsungWalletSettingsV1"},"colours":{"$ref":"#/components/schemas/TemplateColoursV1"},"images":{"$ref":"#/components/schemas/TemplateImagesV1"},"barcode":{"$ref":"#/components/schemas/TemplateBarcodeV1"},"locations":{"$ref":"#/components/schemas/TemplateLocationsV1"},"cardExpiry":{"$ref":"#/components/schemas/TemplateCardExpiryV1"},"links":{"type":"array","description":"Template Links","items":{"$ref":"#/components/schemas/TemplateLinkV1"}},"dataFields":{"type":"array","description":"Data Fields of the template","items":{"$ref":"#/components/schemas/TemplateDataFieldV1"}},"certificates":{"$ref":"#/components/schemas/TemplateCertificatesV1"},"actions":{"$ref":"#/components/schemas/TemplateActionsV1"},"scanRules":{"type":"array","description":"Scan Rules of the template","items":{"$ref":"#/components/schemas/TemplateScanRuleV1"}},"businessRules":{"type":"array","description":"Business rules such as rewards, evaluated separately from scan rules","items":{"$ref":"#/components/schemas/TemplateBusinessRuleV1"}},"rateLimit":{"$ref":"#/components/schemas/RateLimitV1"},"appLinking":{"$ref":"#/components/schemas/TemplateAppLinking"},"shopifySettings":{"$ref":"#/components/schemas/ShopifySettings"},"notifications":{"type":"array","items":{"$ref":"#/components/schemas/NotificationSettings"}},"tiers":{"type":"object","description":"Multi tiers Settings","additionalProperties":{"$ref":"#/components/schemas/TierTemplateSettings"}},"competitionSettings":{"$ref":"#/components/schemas/CompetitionSettings"},"bulkPassInvitesSentAt":{"type":"string","description":"Date when bulk pass invites were last sent, in ISO-8601 format","example":"2021-08-11T03:15:56.860Z"}}},"BloomreachFormIntegrationRecord":{"allOf":[{"$ref":"#/components/schemas/FormIntegrationRecordBase"},{"type":"object","properties":{"name":{"type":"string","enum":["BLOOMREACH"]}},"required":["name"]}]},"BloomreachUpdateIntegrationConfig":{"type":"object","description":"Bloomreach config update. Pass events are sent to the Bloomreach add-event API. PATCH semantics: an absent field keeps the stored value","additionalProperties":false,"required":["providerName"],"properties":{"providerName":{"type":"string","enum":["BLOOMREACH"]},"enabled":{"type":"boolean","description":"False stops every Bloomreach send for this form without removing the webhooks"},"webhooks":{"type":"array","description":"Bloomreach endpoints, replaced as a whole on save. Omit to leave the stored ones untouched; send an empty array to remove them all. Each entry is checked against Bloomreach before anything is saved.","items":{"$ref":"#/components/schemas/BloomreachWebhookConfig"}}}},"BloomreachWebhookConfig":{"allOf":[{"$ref":"#/components/schemas/IntegrationWebhookConfig"},{"type":"object","properties":{"baseUrl":{"type":"string","description":"Bloomreach API base URL from Project settings > Access management > API, no path","example":"https://api.exponea.com"},"privateKey":{"type":"boolean","description":"False (default) authenticates with a public API key (`Authorization: Token`). True uses a private key: `apiKey` is then the key id and `apiSecret` its secret (`Authorization: Basic`)"},"apiKey":{"type":"string","description":"Public API key, or the key id when `privateKey` is true. Needs the Events > Set permission for every mapped event type. Write-only; required when creating, and supplying it on an edit rotates the stored credential."},"apiSecret":{"type":"string","description":"Secret of the private key, required with `apiKey` when `privateKey` is true. Write-only, never returned. Rejected when `privateKey` is false"},"projectToken":{"type":"string","minLength":1,"description":"Bloomreach project token","example":"12345678-abcd-1234-abcd-1234567890ab"},"customerIdName":{"type":"string","minLength":1,"description":"Hard customer id the card's externalId is sent under, as named in the Bloomreach project","example":"registered"}},"required":["projectToken","customerIdName"]}]},"BulkFastCreateCardsRequestBody":{"type":"object","description":"Issue up to 100 passes in one call.\n\nFaster than looping the single-card endpoint, and the right choice when you already know the input is clean. The trade is reporting: you do not get the per-row audit trail the CSV upload flow gives you.\n\n`templateOverrides` here applies to every pass in the batch. For per-pass design differences, issue them individually.","properties":{"templateId":{"type":"string","description":"Template ID","example":"template-123"},"createCardPayloads":{"type":"array","description":"Array of card payloads. Limit of 100 per request","maxItems":100,"minItems":1,"items":{"type":"object","properties":{"cardPayload":{"$ref":"#/components/schemas/BaseCardPayload"},"tier":{"type":"string","description":"For multi tiered templates. This is to select which tier to build","example":"GOLD"}},"required":["cardPayload"]}},"options":{"$ref":"#/components/schemas/SignUpOptions"},"templateOverrides":{"$ref":"#/components/schemas/TemplateOverridesV1"}},"required":["templateId","createCardPayloads","options"]},"BulkFastCreateCardsResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Overall results of bulk create. If 1 failed this will return False here","example":true},"cards":{"type":"array","description":"Results in same order as createCardPayloads. Each item includes original payload content plus newly created card data.","items":{"type":"object","properties":{"cardPayload":{"$ref":"#/components/schemas/BaseCardPayload"},"tier":{"type":"string"},"cardId":{"type":"string","description":"Card ID"},"cardOwnerId":{"type":"string","description":"Card Owner ID"},"downloadId":{"type":"string","description":"Download ID"},"appleLink":{"type":"string","description":"Apple Download Link"},"googleLink":{"type":"string","description":"Google Download Link"},"success":{"type":"boolean","description":"Whether the card was created successfully"}}}},"errors":{"type":"array","description":"Only included when one or more cards failed. Each entry pairs the original payload with the failure code and message.","items":{"type":"object","required":["payload","error"],"properties":{"payload":{"type":"object","description":"The original `createCardPayloads[i]` entry.","properties":{"cardPayload":{"$ref":"#/components/schemas/BaseCardPayload"},"tier":{"type":"string"}}},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Stable error code (e.g. `VALIDATION_FAILED`, `CREATE_CARD_FAILED`).","example":"VALIDATION_FAILED"},"message":{"type":"string","description":"Human-readable error message.","example":"Email already exists"}}}}}}}},"Business":{"type":"object","description":"Information about a business entity","properties":{"id":{"type":"string","description":"Unique identifier for the business","example":"L3ZrB748fLxlFD8ZjFVgX"},"appleTeamIdentifier":{"type":"string","description":"Team identifier for Apple services","example":"F5MB9WT9BZ"},"businessName":{"type":"string","description":"Name of the business","example":"Billing"},"cardCount":{"type":"integer","format":"int32","description":"Number of cards associated with the business","example":60},"cardLimit":{"type":"integer","format":"int32","description":"Maximum number of cards allowed for the business (-1 for unlimited)","example":-1},"cardOwnerCount":{"type":"integer","format":"int32","description":"Number of card owners associated with the business","example":8},"certId":{"type":"string","description":"Identifier for the certificate used by the business","example":"freetier"},"createdAt":{"type":"string","format":"date-time","description":"Timestamp of when the business record was created","example":"2022-09-08T06:37:44.803Z"},"email":{"type":"string","format":"email","description":"Contact email for the business","example":"billing_analytics@litecard.com.au"},"logoUrl":{"type":"string","format":"uri","description":"URL to the business's logo","example":"https://assets.litecard.io/Litecard_Badge.png"},"mailchimp":{"type":"object","description":"Mailchimp configuration details for the business","properties":{"accessToken":{"type":"string","description":"Access token for Mailchimp API","example":"__access_token__"},"fromEmail":{"type":"string","format":"email","description":"Email address to send from using Mailchimp","example":"news@corplite.com"},"server":{"type":"string","description":"Mailchimp server prefix","example":"us14"}}},"passTypeIdentifier":{"type":"string","description":"Pass type identifier for mobile wallet services","example":"pass.au.com.lc.dev.freetier"},"templateCount":{"type":"integer","format":"int32","description":"Number of templates created by the business","example":2},"templateLimit":{"type":"integer","format":"int32","description":"Maximum number of templates allowed for the business (-1 for unlimited)","example":-1},"tier":{"type":"string","description":"Tier or plan level of the business","example":"FREE"},"updatedAt":{"type":"string","format":"date-time","description":"Timestamp of the last update to the business's record","example":"2023-02-15T04:39:05.239Z"},"uiConfig":{"$ref":"#/components/schemas/UIConfig"},"settings":{"$ref":"#/components/schemas/BusinessSettings"}}},"BusinessCardOptions":{"type":"object","properties":{"firstName":{"type":"string","description":"First Name","example":"John"},"lastName":{"type":"string","description":"Last Name","example":"Doe"},"middleName":{"type":"string","description":"Middle Name","example":"Sam"},"email":{"type":"string","description":"Email field for email address","example":"john@litecard.com.au"},"workPhone":{"type":"string","description":"Work phone number","example":"+61400000000"},"homePhone":{"type":"string","description":"Home phone number","example":"+61400000000"},"company":{"type":"string","description":"Company of Employment","example":"Litecard"},"title":{"type":"string","description":"Title of position at Company","example":"Developer"},"social":{"type":"array","items":{"$ref":"#/components/schemas/SocialMedia"}}}},"BusinessCardRequestBody":{"type":"object","properties":{"businessId":{"type":"string","description":"Id for the Business that this entity belongs to","example":"LiteCard"},"templateId":{"type":"string","description":"Id for the apple/google pass template used to create the card.","example":"test_business"},"cardPayload":{"$ref":"#/components/schemas/BaseCardPayload"},"options":{"$ref":"#/components/schemas/SignUpOptions"},"businessCard":{"$ref":"#/components/schemas/BusinessCardOptions"}}},"CSVExportRecord":{"type":"object","description":"CSV Export Record","properties":{"id":{"type":"string","description":"Unique export ID","example":"550e8400-e29b-41d4-a716-446655440000"},"tableName":{"type":"string","description":"Table being exported","example":"cardsTable"},"status":{"type":"string","description":"Export status","enum":["PENDING","PROCESSING","COMPLETED","FAILED"],"example":"COMPLETED"},"description":{"type":"string","nullable":true,"description":"Human-readable description of export scope (e.g., 'All Active Cards' or 'Bronze Card Template')","example":"Cards for Bronze Card template"},"startDateTime":{"type":"string","nullable":true,"description":"Export filter: from date","example":"2023-01-01T00:00:00Z"},"endDateTime":{"type":"string","nullable":true,"description":"Export filter: to date","example":"2023-12-31T23:59:59Z"},"templateId":{"type":"string","nullable":true,"description":"Optional template filter","example":"tmpl-456"},"templateName":{"type":"string","nullable":true,"description":"Template name (populated when templateId is specified)","example":"Bronze Card"},"statusList":{"type":"array","items":{"type":"string","enum":["ACTIVE","INACTIVE","DELETED","AVAILABLE","EXPIRED"]},"nullable":true,"description":"Pass status filter list","example":["ACTIVE","INACTIVE"]},"createdAt":{"type":"string","description":"Created timestamp","example":"2025-12-15T12:00:00Z"},"updatedAt":{"type":"string","description":"Last updated timestamp","example":"2025-12-15T12:05:00Z"},"presignedUrl":{"type":"string","nullable":true,"description":"S3 presigned download URL (when completed)","example":"https://s3.amazonaws.com/bucket/file.csv?..."},"expiresIn":{"type":"number","nullable":true,"description":"Presigned URL expiration time in seconds","example":3600},"errorMessage":{"type":"string","nullable":true,"description":"Error details if export failed","example":"Lambda timeout after 30 seconds"}}},"CardSyncSummaryListV1":{"type":"array","items":{"$ref":"#/components/schemas/CardSyncSummaryV1"}},"CardSyncSummaryV1":{"type":"object","properties":{"count":{"type":"integer","description":"Total number of cards processed","example":5000},"failedCount":{"type":"integer","description":"Number of failed card syncs","example":0},"platform":{"type":"string","description":"Platform for the card sync","enum":["apple","google"],"example":"apple"},"successCount":{"type":"integer","description":"Number of successful card syncs","example":5000},"type":{"type":"string","description":"Type of sync operation","enum":["TEMPLATE_CARD_SYNC"],"example":"TEMPLATE_CARD_SYNC"}},"required":["count","failedCount","platform","successCount","type"]},"CardUploadGroupV1":{"type":"object","description":"Schema for Card Upload Group","properties":{"id":{"type":"string","example":"group-id"},"businessId":{"type":"string","example":"business-id"},"count":{"type":"object","description":"valid + invalid = total and valid = created + updated + failed + skipped","properties":{"failed":{"type":"number","example":0},"invalid":{"type":"number","example":2},"skipped":{"type":"number","example":0},"created":{"type":"number","example":1},"updated":{"type":"number","example":1},"total":{"type":"number","example":3},"valid":{"type":"number","example":1}}},"createdAt":{"type":"string","example":"2024-05-30T06:53:18.700Z"},"fileName":{"type":"string","example":"cards.csv"},"resultsLink":{"type":"string","description":"The one results file, present only when the upload produced exactly one. An upload large enough to be loaded in rounds sets resultsLinks instead; the two never both appear.","example":"https://litecard-dev-uploads.s3.ap-southeast-2.amazonaws.com/business-id/card-uploads/group-id/results.csv"},"resultsLinks":{"type":"array","description":"Every results file, present only when the upload produced more than one, in which case resultsLink is absent. An upload large enough to be loaded in rounds writes one file per round.","items":{"type":"string"},"example":["https://litecard-dev-uploads.s3.ap-southeast-2.amazonaws.com/business-id/card-uploads/group-id/results-batch-1.csv","https://litecard-dev-uploads.s3.ap-southeast-2.amazonaws.com/business-id/card-uploads/group-id/results-batch-2.csv"]},"runProgress":{"type":"object","description":"Live progress of the run currently in flight, added to as each batch comes back and zeroed when a run starts. Use this while `status` is PROCESSING; use `count` once it is COMPLETED. They are different things: `count` is counted from the records at the end and always reconciles, this is a running tally of the one run you are watching.","properties":{"created":{"type":"number","example":6900},"updated":{"type":"number","example":0},"failed":{"type":"number","example":120}}},"retryCount":{"type":"number","description":"How many retries have been started for this upload. Compared against the server's maximum when deciding whether another is allowed.","example":2},"retryDetails":{"type":"array","description":"One entry per retry attempt, appended when the attempt starts and completed as it finishes, so an in-flight attempt is visible with its counts still at zero. Each entry counts only the rows that attempt re-ran; `count` is the whole upload. The results links always point at every row's latest outcome.","items":{"type":"object","properties":{"attempt":{"type":"number","example":1},"requestedAt":{"type":"string","example":"2026-09-22T04:10:00.000Z"},"completedAt":{"type":"string"},"status":{"type":"string","enum":["RETRY_PENDING","RETRY_IN_PROGRESS","RETRY_COMPLETED","FAILED"]},"includeValidationFailed":{"type":"boolean","example":false},"retried":{"type":"number","example":10000},"success":{"type":"number","example":7000},"failed":{"type":"number","example":3000},"skipped":{"type":"number","example":0},"invalid":{"type":"number","example":0},"batches":{"type":"array","description":"The batches this attempt rebuilt.","items":{"type":"number"},"example":[1,4]}}}},"overrideExisting":{"type":"boolean","example":true},"status":{"type":"string","enum":["PARSING_ERROR","RECORD_COUNT_EXCEEDED","PENDING","VALIDATION_COMPLETE","PROCESSING","COMPLETED","QUEUED","IN_PROGRESS","FAILED","RETRY_PENDING","RETRY_IN_PROGRESS","RETRY_COMPLETED"],"description":"A retry moves the upload through RETRY_PENDING (requested, not yet picked up), RETRY_IN_PROGRESS and RETRY_COMPLETED; a retry that breaks ends FAILED. Another retry can be requested only from COMPLETED, FAILED or RETRY_COMPLETED.","example":"COMPLETED"},"templateId":{"type":"string","example":"template-id"},"type":{"type":"string","enum":["FILE_UPLOAD","TEMPLATE_CARD_SYNC"]},"summary":{"$ref":"#/components/schemas/CardSyncSummaryListV1"}},"required":["id","businessId","createdAt","fileName","overrideExisting","status","templateId","type"]},"CardUploadV1":{"type":"object","description":"Schema for detailed card upload data","properties":{"id":{"type":"string","example":"id"},"businessId":{"type":"string","example":"business-id"},"cardId":{"type":"string","example":"card12345"},"cardPayload":{"type":"object","additionalProperties":true,"description":"Generic card payload with unspecified fields","example":{"email":"123214312","firstName":"Nimesh","lastName":"Jayamanne"}},"createdAt":{"type":"string","example":"2024-05-30T06:45:02.736Z"},"errors":{"type":"array","items":{"type":"string","example":"abc is not a valid email"}},"recordIndex":{"type":"number","example":2},"status":{"type":"string","enum":["QUEUED","PROCESSING","CREATED","UPDATED","FAILED","SKIPPED","VALIDATION_FAILED"],"example":"VALIDATION_FAILED"},"templateId":{"type":"string","example":"template-id"}},"required":["id","businessId","createdAt","recordIndex","status","templateId"]},"CertificateUploadV1":{"type":"object","properties":{"certificate":{"type":"string","description":"base64 encoded certificate","example":"MIIGEDCCBPigAwIBAgIQL...=="},"description":{"type":"string","description":"A description about the certificate"}},"required":["certificate"]},"ColumnValues":{"type":"object","properties":{"title":{"type":"string","description":"Readable label for column Value"},"value":{"type":"string","description":"Column value property that matches db entry"}}},"CompetitionSettings":{"type":"object","properties":{"type":{"type":"string","enum":["FIRST_X"],"description":"Type of competition. FIRST_OF first x number of entries will win","example":"FIRST_X"},"limit":{"type":"number","description":"Limit of the competition. If type is FIRST_X, this is used to determine maximum cards","example":10},"segmentName":{"type":"string","description":"Name of the segment used as the flag to determine if the user is a winner of the competition","example":"segment1"},"voucherAssociated":{"type":"boolean","description":"Flag to determine if there are voucher codes associated with the competition","example":true}},"required":["type","segmentName","voucherAssociated"]},"CreatFastCardRequestBody":{"type":"object","properties":{"downloadId":{"type":"string","description":"The ID of the card previously created","example":"abc123"},"deviceType":{"type":"string","description":"What type of card to create","enum":["APPLE","GOOGLE"],"example":"APPLE"}},"required":["downloadId","deviceType"]},"CreateSubBusinessRequestBody":{"type":"object","properties":{"businessName":{"type":"string","description":"Business name","example":"Cryo gym"},"email":{"type":"string","description":"Business email","example":"Ankus Fang"},"password":{"type":"string","description":"Business password","example":"password"},"appUrls":{"type":"array","description":"App urls","items":{"type":"string","description":"App url","example":"https://app.litecard.io"}},"country":{"type":"string","description":"Business country","example":"Australia"},"postCode":{"type":"string","description":"Business postcode","example":"CryoGym"},"phone":{"type":"string","description":"Business phone","example":"Ankus Fang"},"state":{"type":"string","description":"Business state","example":"VIC"},"logoImage":{"type":"string","nullable":true,"description":"Logo image s3 URL","example":"https://assets.litecard.io/logo.png"},"overrideLitecardLogoImage":{"type":"string","nullable":true,"description":"Override LiteCard logo image s3 URL","example":"https://assets.litecard.io/override-logo.png"},"tableConfig":{"$ref":"#/components/schemas/TableConfig"}}},"CreateTemplateNotification":{"description":"Request body for creating a template notification setting","allOf":[{"$ref":"#/components/schemas/BaseNotificationSettings"},{"type":"object","required":["trigger","type","title","message","delay","unit"]}]},"CreateTemplateV1":{"type":"object","description":"Template used to create apple/google wallet passes","properties":{"name":{"type":"string","description":"Human readable name of the template","example":"Bronze Card"},"cardCount":{"type":"integer","description":"Number of active cards created using this template","example":80},"downloadsCount":{"type":"integer","description":"Number of cards downloaded","example":70},"downloadedApplePassCount":{"type":"integer","description":"Number of cards downloaded on Apple Wallet","example":50},"downloadedGooglePassCount":{"type":"integer","description":"Number of cards downloaded on Google Wallet","example":20},"description":{"type":"string","description":"Template Description","example":"LiteCard Bronze Card"},"businessName":{"type":"string","description":"Name of the business","example":"LiteCard"},"isInternal":{"type":"boolean","description":"Public Visibility of the template/form","example":false},"type":{"type":"string","description":"The Type of Pass","enum":["BUSINESS_CARD","GENERIC","COUPON","EVENT_TICKET"]},"externalId":{"type":"string","description":"External id mapping to third party system","example":"1234"},"settings":{"$ref":"#/components/schemas/SettingsV1"},"stripeSettings":{"$ref":"#/components/schemas/StripeSettings"},"appleWalletSettings":{"$ref":"#/components/schemas/TemplateAppleWalletSettingsV1"},"googleWalletSettings":{"$ref":"#/components/schemas/TemplateGoogleWalletSettingsV1"},"samsungWalletSettings":{"$ref":"#/components/schemas/TemplateSamsungWalletSettingsV1"},"colours":{"type":"object","description":"Colours of the template","properties":{"background":{"type":"string","description":"Background colour of the pass","example":"#ffffff"},"label":{"type":"string","description":"Label colour of the apple pass","example":"#ffffff"},"foreground":{"type":"string","description":"Text colour of the apple pass","example":"#ffffff"},"strip":{"type":"string","description":"Text colour on top of strip image of the apple pass","example":"#ffffff"},"samsungBlink":{"type":"string","description":"Colour of the blinking indicating area in samsung wallet","example":"#00ffaa"},"samsungFont":{"type":"string","description":"Colour of the font on the Samsung card art","enum":["dark","light"],"example":"dark"}},"required":["background","label","foreground"]},"images":{"type":"object","description":"Images of the template","properties":{"logo":{"type":"string","description":"URL of uploaded a logo image (square), used by Apple, Google & Samsung, 300px by 300px.","example":"https://s3bucketlocation/business-name/logo.png"},"logoDarkMode":{"type":"string","description":"URL of uploaded a logo image (square), used by Samsung, 300px by 300px. Used specifically for dark mode.","example":"https://s3bucketlocation/business-name/logo.png"},"logoLightMode":{"type":"string","description":"URL of uploaded a logo image (square), used by Samsung, 300px by 300px. Used specifically for light mode.","example":"https://s3bucketlocation/business-name/logo.png"},"hero":{"type":"string","description":"URL of uploaded hero image, used by Google. 1032px x 336px","example":"https://s3bucketlocation/business-name/hero.png"},"strip":{"type":"string","description":"URL of uploaded strip image, used by Apple. 1125px by 432px. Will overwrite Apple's thumbnail image.","example":"https://s3bucketlocation/business-name/hero.png"},"appleLogoOverride":{"type":"string","description":"URL of uploaded logo image used by Apple. This will replace the shared logo image. 150px height with max width of 480px","example":"https://s3bucketlocation/business-name/hero.png"},"icon":{"type":"string","description":"URL of uploaded icon image, used by Apple","example":"https://s3bucketlocation/business-name/icon.png"},"thumbnail":{"type":"string","description":"URL of uploaded thumbnail image. 270px by 270px. Only available on Apple Event Ticket and Generic card types and certain Samsung Card Types.","example":"https://s3bucketlocation/business-name/thumbnail.png"},"background":{"type":"string","description":"URL of uploaded background image. 270px by 270px. Only available on Apple Event Ticket card types","example":"https://s3bucketlocation/business-name/background.png"}},"required":["icon","logo"]},"barcode":{"type":"object","description":"Barcode Fields of the template","properties":{"barcodeValue":{"type":"string","description":"Value of the Barcode","example":"4805723345"},"messageEncoding":{"type":"string","description":"Message Encoding of the Barcode","example":"iso-8859-1","nullable":true},"type":{"type":"string","description":"Type of the Barcode","example":"CODE_128","enum":["NOT_USED","QR_CODE","CODE_128","PDF_417","AZTEC"]},"altText":{"type":"string","description":"Displayed alternative value at the bottom of the barcode","example":"1231244","nullable":true},"disabledAltText":{"type":"boolean","description":"Disable the alternative text below the barcode","example":false,"nullable":true},"fieldMap":{"type":"string","description":"If barcodeValue is set to ${CUSTOM}. fieldMap must be present to identify which field the barcodeValue should be set from.","example":"memberId","nullable":true},"prefix":{"type":"string","description":"For Readers / scanners that require a prefix before a barcodeValue","example":"?","nullable":true},"suffix":{"type":"string","description":"For Readers / scanners that require a suffix after a barcodeValue","example":";","nullable":true}},"required":["type","altText","barcodeValue"]},"locations":{"$ref":"#/components/schemas/TemplateLocationsV1"},"cardExpiry":{"$ref":"#/components/schemas/TemplateCardExpiryV1"},"links":{"type":"array","description":"Template Links","items":{"$ref":"#/components/schemas/TemplateLinkV1"}},"dataFields":{"type":"array","description":"Data Fields of the template","items":{"$ref":"#/components/schemas/TemplateDataFieldV1"}},"certificates":{"$ref":"#/components/schemas/TemplateCertificatesV1"},"actions":{"$ref":"#/components/schemas/TemplateActionsV1"},"scanRules":{"type":"array","description":"Scan Rules of the template","items":{"$ref":"#/components/schemas/TemplateScanRuleV1"}},"businessRules":{"type":"array","description":"Business rules such as rewards, evaluated separately from scan rules","items":{"$ref":"#/components/schemas/TemplateBusinessRuleV1"}},"rateLimit":{"$ref":"#/components/schemas/RateLimitV1"},"appLinking":{"$ref":"#/components/schemas/TemplateAppLinking"},"shopifySettings":{"$ref":"#/components/schemas/ShopifySettings"},"notifications":{"type":"array","items":{"$ref":"#/components/schemas/NotificationSettings"}},"tiers":{"type":"object","description":"Multi tiers Settings","additionalProperties":{"$ref":"#/components/schemas/TierTemplateSettings"}},"competitionSettings":{"$ref":"#/components/schemas/CompetitionSettings"},"bulkPassInvitesSentAt":{"type":"string","description":"Date when bulk pass invites were last sent, in ISO-8601 format","example":"2021-08-11T03:15:56.860Z"}},"required":["name","description","businessName","appleWalletSettings","googleWalletSettings","colours","images","barcode"]},"CreateUserGroupRequestBody":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Name of the user group","example":"Store Managers"},"roles":{"type":"array","items":{"type":"string"},"description":"Roles assigned to this group","example":["Employee"]}},"additionalProperties":false},"CreateUserRequestBody":{"type":"object","required":["email","userGroupId"],"properties":{"email":{"type":"string","format":"email","description":"Email address of the new user","example":"newuser@example.com"},"password":{"type":"string","description":"Password. Required when sendInvitation is false.","example":"Str0ngP@ssword!"},"userGroupId":{"type":"string","description":"ID of the user group to assign the user to"},"sendInvitation":{"type":"boolean","default":false,"description":"If true, sends an invitation email with a password setup link instead of requiring a password"}},"additionalProperties":false},"DatabaseMetaData":{"type":"object","properties":{"createdAt":{"type":"string","description":"Record creation date","example":"2021-08-11T03:15:56.860Z"},"updatedAt":{"type":"string","description":"Record update date","example":"2021-08-1T03:15:56.860Z"},"createdBy":{"type":"string","description":"User Id of the creator","example":"abc123"}}},"DeleteCardsByContactRequestBody":{"type":"object","properties":{"email":{"type":"string","description":"Email of the card owner","example":"test@example.com"},"phone":{"type":"string","description":"Phone number of the card owner","example":"+61400000000"},"databaseDelete":{"type":"boolean","description":"Delete the cards from the database","example":true,"default":false}}},"DeleteScanV1ResponseSchema":{"type":"object","properties":{"scanId":{"type":"string","description":"Scan ID","example":"25dgjks2q3kqqha"}},"required":["scanId"]},"EmailRemindersRequestBody":{"type":"object","properties":{"templateId":{"type":"string","description":"Template of the cards you want to send reminder to. If value not provided, will send to all passes that haven't been activated.","example":"test_business"},"emailTemplate":{"type":"string","description":"Select which email template option to use.","example":"litecardpass","enum":["litecardpass","table-tennis","tarfish","tarfish-form","cx3","tt-reminder","mubc","muttc"]}},"required":["emailTemplate"]},"ExportCsvV1RequestBody":{"type":"object","properties":{"tableName":{"type":"string","enum":["cardsTable","scansTable","SCAN_TABLE","notificationGroupsTable","notificationsTable"],"description":"The name of the front end table that is to be exported","example":"SCAN_TABLE"},"startDateTime":{"type":"string","description":"StartDateTime of the exporting data","example":"2022-02-04T06:22:37.773Z"},"endDateTime":{"type":"string","description":"EndDateTime of the exporting data","example":"2022-02-04T06:22:37.773Z"},"templateId":{"type":"string","description":"Id of the template to be exported","example":"template-123"},"statusList":{"type":"array","items":{"type":"string","enum":["ACTIVE","INACTIVE","DELETED","AVAILABLE","EXPIRED"]},"nullable":true,"description":"Status filter list","example":["ACTIVE","INACTIVE"]},"appleWalletStatus":{"type":"array","items":{"type":"string","enum":["ACTIVE","INACTIVE","ALL","NOT_DOWNLOADED"]},"nullable":true,"description":"Apple Wallet status filter - ACTIVE: downloaded and active, INACTIVE: downloaded and removed, NOT_DOWNLOADED: never downloaded, ALL: all cards","example":["ACTIVE","NOT_DOWNLOADED"]},"googleWalletStatus":{"type":"array","items":{"type":"string","enum":["ACTIVE","INACTIVE","ALL","NOT_DOWNLOADED"]},"nullable":true,"description":"Google Wallet status filter - ACTIVE: downloaded and active, INACTIVE: downloaded and removed, NOT_DOWNLOADED: never downloaded, ALL: all cards","example":["ACTIVE","NOT_DOWNLOADED"]}}},"ExternalCallbackV1":{"type":"object","properties":{"type":{"type":"string","description":"Status of the callback"}},"additionalProperties":true},"ExternalSubscription":{"type":"object","description":"External inbound webhook subscription stored per template","properties":{"businessId":{"type":"string","description":"Business that owns this subscription","example":"R9xh12dga8i"},"subscriptionId":{"type":"string","description":"Provider-assigned subscription identifier","example":"gid://shopify/WebhookSubscription/123456789"},"provider":{"type":"string","description":"External provider the subscription is registered with","enum":["SHOPIFY"],"example":"SHOPIFY"},"topic":{"type":"string","description":"Generic inbound webhook topic","enum":["CUSTOMER_CREATE","CUSTOMER_UPDATE","CUSTOMER_DELETE"],"example":"CUSTOMER_CREATE"},"providerTopic":{"type":"string","description":"Provider-specific topic name","example":"CUSTOMERS_CREATE"},"templateId":{"type":"string","description":"Template events are delivered to","example":"AyIbjHCCGnYZwq9PdkB79"},"callbackUri":{"type":"string","description":"URI the provider delivers webhook events to","example":"https://bff-api.dev.litecard.io/api/v1/external/callback/SHOPIFY/R9xh12dga8i/AyIbjHCCGnYZwq9PdkB79"},"metafieldNamespaces":{"type":"array","description":"Metafield namespaces attached to every delivery; absent when none were requested.","items":{"type":"string","example":"loyalty"}},"filter":{"$ref":"#/components/schemas/InboundWebhookFilter"},"providerFilter":{"type":"string","readOnly":true,"description":"`filter` compiled to the provider's own syntax, exactly as sent","example":"(metafields.namespace:custom AND metafields.key:mf1 AND metafields.value:NEW)"},"createdAt":{"type":"string","format":"date-time","description":"Record creation timestamp","example":"2026-07-17T00:00:00.000Z"},"updatedAt":{"type":"string","format":"date-time","description":"Record last update timestamp","example":"2026-07-17T00:00:00.000Z"}},"required":["businessId","subscriptionId","provider","topic","providerTopic","templateId","callbackUri","createdAt","updatedAt"]},"FormFieldsV1":{"type":"object","properties":{"type":{"type":"string","enum":["text","radio","select","phone","textArea","number","date","dateTime","image","nationality","address"]},"format":{"type":"string","description":"Format of the field","example":"DD/MM/YYYY for date inputs","nullable":true},"label":{"type":"string","description":"The label of the field on the card/form","example":"First Name"},"helpText":{"type":"string","description":"Help text to be displayed next to the field label","example":"Date the member joined","nullable":true},"name":{"type":"string","description":"Unique key","example":"firstName"},"rules":{"type":"array","description":"Validation rules that will be run on this field","items":{"type":"string","enum":["required","phone","email","integer"]}},"allowOauthValidation":{"type":"boolean","description":"Enable validation of field values using social account authentication (OAuth sign-in)","example":true},"usage":{"$ref":"#/components/schemas/FormFieldUsage"},"mappingType":{"type":"string","enum":["CARD_OWNER","CARD_OWNER_AND_CARD","CARD"],"description":"Determines if this field is mapped from form to card or cardowner or both","example":true},"value":{"oneOf":[{"type":"string"},{"type":"number"}]},"placeholder":{"type":"string","description":"Place owner text","example":"Full Name"},"defaultCountryCode":{"type":"string","description":"Default country code for phone number","example":"au"},"options":{"type":"array","description":"List of options for drop down list","items":{"type":"object","properties":{"title":{"type":"string","description":"Label for the option","example":"Casual"},"value":{"type":"string","description":"Value for the option","example":"templateId"}},"required":["title","value"]}},"defaultValue":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"}]},"isSecure":{"type":"boolean","title":"Secure Field","description":"Indicates if this field contains sensitive/secure data","example":false}},"required":["type","label","name","rules"]},"FormIntegrationRecord":{"description":"WRITE side: one integration record as persisted on the form by the attach flow — durable config only, discriminated by provider. Live provider state is never stored here; reads get it via IntegrationDetailsV1.","oneOf":[{"$ref":"#/components/schemas/KlaviyoFormIntegrationRecord"},{"$ref":"#/components/schemas/SquareFormIntegrationRecord"},{"$ref":"#/components/schemas/InboundListenerFormIntegrationRecord"},{"$ref":"#/components/schemas/TalonOneFormIntegrationRecord"},{"$ref":"#/components/schemas/BloomreachFormIntegrationRecord"}],"discriminator":{"propertyName":"name","mapping":{"KLAVIYO":"#/components/schemas/KlaviyoFormIntegrationRecord","SQUARE":"#/components/schemas/SquareFormIntegrationRecord","LC_INBOUND_LISTENER":"#/components/schemas/InboundListenerFormIntegrationRecord","TALONONE":"#/components/schemas/TalonOneFormIntegrationRecord","BLOOMREACH":"#/components/schemas/BloomreachFormIntegrationRecord"}}},"FormIntegrationRecordBase":{"type":"object","description":"Fields every form integration record carries","properties":{"integrationId":{"type":"string","description":"Stable id of THIS form's attach record, minted on attach and preserved across config updates. Many integrationIds (one per form) can share one connectionId."},"connectionId":{"type":"string","description":"Business-level OAuth connection this entry reads tokens from (stamped at connect; the token-table key component). One connection can back entries on many forms: re-attaching mints a new integrationId but keeps the connectionId; reconnecting the provider account swaps the connectionId but keeps the integrationId. Absent on legacy API-key entries. Source of truth for token reads — no listing fallback."},"eventType":{"type":"string","enum":["RECEIVE","SEND","BOTH"],"description":"Declarative direction label for the integration: RECEIVE (inbound only), SEND (outbound only), or BOTH (bidirectional, e.g. KLAVIYO which sends pass events out and receives the add-to-list flow webhook). Nothing routes on it at runtime — the only readers are the inbound api-key config gates (LC_INBOUND_LISTENER, TALONONE), which require the record to assert an inbound direction."},"enabled":{"type":"boolean","description":"The standard per-form on/off switch for this integration entry, carried by every provider. The runtime gate reads it: false means skip this entry entirely (no sync, no send). This is the ONLY disable — connections themselves are just connected or removed."},"overridesLitecardInvitation":{"type":"boolean","description":"When true (and the entry is enabled), this integration delivers the pass invite itself, so LiteCard suppresses BOTH its own invitation email and its invitation SMS. bff signup and card-api create check it across all enabled entries via formHasLitecardInvitationOverride, which also requires the entry to actually deliver for the card (created event on, resolvable identifier)."}},"required":["eventType"]},"FormShopifySettings":{"type":"object","description":"Shopify integration settings for this form","properties":{"client":{"type":"string","description":"User key to be used to retrieve client credentials and verify owner"},"version":{"type":"string","description":"Version of the Shopify settings","enum":["V1","V0"]},"templateMap":{"type":"object","description":"Map Shopify webhook values to card template fields","additionalProperties":true},"storeName":{"type":"string","description":"Shopify store name (myshopify.com subdomain)","example":"litecard-dev-store"},"enabled":{"type":"boolean","description":"The Shopify integration's switch, saved by the integration config. False skips inbound customer events; absent on forms saved before it existed."},"createOnUpdate":{"type":"boolean","description":"True makes a customers/update event for a customer with no card on this template create one. Absent or false skips it."},"postProcessWrites":{"$ref":"#/components/schemas/ShopifyPostProcessWrites"}}},"FormV1":{"type":"object","allOf":[{"type":"object","properties":{"id":{"type":"string","description":"Id of the form","example":"-jJWhjZ1a"},"fields":{"type":"array","items":{"$ref":"#/components/schemas/FormFieldsV1"}},"formName":{"type":"string","description":"Name of the form","example":"test form"},"businessId":{"type":"string","description":"Identifier for the Business that this entity belongs to","example":"LiteCard"},"passwordEnabled":{"type":"boolean","description":"Enable password","example":"true","default":false},"logo":{"type":"string","description":"logo image link url","example":"https://assets-dev.litecard.io/logo.png"},"title":{"type":"string","description":"Title of the form","example":"LiteCard"},"description":{"type":"string","description":"Description of the form","example":"Make your move with LiteCard today."},"style":{"type":"object","properties":{"bgPrimary":{"type":"string","description":"Primary colour for the form background","example":"#1890ff"},"bgSecondary":{"type":"string","description":"Secondary colour for the form background","example":"#1890ff"},"btnPrimary":{"type":"string","description":"Secondary colour for the button","example":"#1890ff"},"btnSecondary":{"type":"string","description":"Secondary colour for the button","example":"#1890ff"},"label":{"type":"string","description":"Label colour for Apple Passes","example":"#f1f0f2"},"customCssLinks":{"type":"array","description":"List of custom css links","items":{"type":"string"}}}},"connectIDEnabled":{"type":"boolean","description":"Enable ConnectID Verification","example":"false"},"isInternal":{"type":"boolean","description":"Public Visibility of the form","example":true},"templateId":{"type":"string","description":"Identifier for the template used to create the card","example":"test_business"},"status":{"type":"string","description":"Status of the form","example":"ACTIVE","enum":["ACTIVE","INACTIVE","DELETED"]},"paymentRequired":{"type":"boolean","description":"Require payment after form submission"},"tacURL":{"type":"string","description":"Terms and Condition link to be used"},"smsEnabled":{"type":"boolean","description":"Flag to send SMS or not. Used by our sign up form"},"hideForm":{"type":"boolean","description":"Hide form from Private Signup"},"mailchimpSettings":{"type":"object","description":"Form specific mailchimp settings","properties":{"enabled":{"type":"boolean","description":"Enable mailchimp invitation emails","example":false},"eventName":{"type":"string","description":"journey to trigger in mailchimp","example":"my_event"},"mergeFieldMapping":{"type":"object","description":"Additional merge field mappings to card data","additionalProperties":true},"mergeFieldFormat":{"type":"object","description":"Format of the merge fields, For example MM/DD for birthday type data in mailchimp","additionalProperties":true},"tags":{"type":"array","items":{"$ref":"#/components/schemas/MailchimpTagSettings"}}}},"emailProviderSettings":{"type":"object","description":"Email Provider Settings","properties":{"enabled":{"type":"boolean","description":"Enable Email Provider invitation emails","example":false},"sendEmail":{"type":"boolean","description":"Whether Litecard triggers send email or just updates profile","example":false},"providerName":{"type":"string","description":"Name of Email Provider","example":"BRAZE"},"campaignName":{"type":"string","description":""},"campaignId":{"type":"string","description":""},"mergeFieldMapping":{"type":"object","description":"Additional merge field mappings to card data","additionalProperties":true},"contactFieldMapping":{"type":"object","description":"Additional merge field mappings to update contact data","additionalProperties":true}},"additionalProperties":true},"integrations":{"type":"array","description":"Form integration records","items":{"$ref":"#/components/schemas/FormIntegrationRecord"}},"shopifySettings":{"$ref":"#/components/schemas/FormShopifySettings"}}},{"$ref":"#/components/schemas/DatabaseMetaData"}],"required":["id","fields","formName","createdBy","businessId","logo","description","style","templateId","isInternal","status"]},"GenericUpdateIntegrationConfig":{"type":"object","properties":{"providerName":{"type":"string","enum":["LC_INBOUND_LISTENER","DOTDIGITAL"],"description":"Provider name"},"username":{"type":"string","description":"Integration username"},"password":{"type":"string","description":"Integration password"},"campaignId":{"type":"string","description":"Campaign identifier"},"syncContact":{"type":"boolean","description":"Whether to sync contact information","example":true},"sendEmail":{"type":"boolean","description":"Whether Litecard triggers the campaign email","example":true},"passStatusSync":{"type":"object","nullable":true,"description":"DOTDIGITAL: keeps one Dotdigital contact data field at ACTIVE while the pass is in a wallet and INACTIVE once it is removed. Absent or null means Litecard writes nothing after pass create.","properties":{"contactDataField":{"type":"string","description":"The Dotdigital contact data field to write, from contactFieldNames","example":"WALLET_STATUS"}},"required":["contactDataField"]},"mergeFieldMapping":{"type":"object","additionalProperties":{"type":"string"},"description":"Mapping of merge fields (local -> provider)"},"contactFieldMapping":{"type":"object","additionalProperties":{"type":"string"},"description":"Mapping of contact fields (local -> provider)"},"enabled":{"type":"boolean","description":"Whether the integration is enabled"},"webhooks":{"type":"array","description":"Outbound webhooks for providers with their own event API, replaced as a whole on save. Omit to leave the stored webhooks untouched; send an empty array to remove them all.","items":{"$ref":"#/components/schemas/IntegrationWebhookConfig"}}},"required":["providerName"]},"GoogleAppLinkingSettings":{"type":"object","description":"Google settings for AppLinking","properties":{"appUri":{"type":"string","description":"URL to the app. Recommended to use a dynamic link"}},"required":["appUri"]},"InboundListenerFormIntegrationRecord":{"allOf":[{"$ref":"#/components/schemas/FormIntegrationRecordBase"},{"type":"object","properties":{"name":{"type":"string","enum":["LC_INBOUND_LISTENER"]},"mergeFields":{"type":"object","additionalProperties":{"type":"string"},"description":"Merge field mappings (local field -> provider field)"}},"required":["name"]}]},"InboundWebhookConfig":{"type":"object","description":"One provider topic Litecard should be called back on. Matched to the stored subscription by `topic`; other properties of a read-back entry are ignored.","properties":{"topic":{"type":"string","description":"Generic inbound webhook topic","enum":["CUSTOMER_CREATE","CUSTOMER_UPDATE","CUSTOMER_DELETE"],"example":"CUSTOMER_UPDATE"},"metafieldNamespaces":{"type":"array","description":"Metafield namespaces to attach to every delivery. Payloads carry no metafields without this. Namespaces a `filter` reads are added automatically.","items":{"type":"string","example":"loyalty"}},"filter":{"$ref":"#/components/schemas/InboundWebhookFilter"}},"required":["topic"]},"InboundWebhookDetails":{"type":"object","description":"A subscription registered inside the provider for one topic.","properties":{"subscriptionId":{"type":"string","description":"Provider-assigned subscription identifier","example":"gid://shopify/WebhookSubscription/123456789"},"topic":{"type":"string","description":"Generic inbound webhook topic","enum":["CUSTOMER_CREATE","CUSTOMER_UPDATE","CUSTOMER_DELETE"],"example":"CUSTOMER_UPDATE"},"providerTopic":{"type":"string","description":"Provider-specific topic name","example":"CUSTOMERS_UPDATE"},"callbackUri":{"type":"string","description":"URI the provider delivers events to","example":"https://bff-api.dev.litecard.io/api/v1/external/callback/SHOPIFY/R9xh12dga8i/AyIbjHCCGnYZwq9PdkB79"},"metafieldNamespaces":{"type":"array","description":"Metafield namespaces the subscription was created with; absent when none were requested.","items":{"type":"string","example":"loyalty"}},"filter":{"$ref":"#/components/schemas/InboundWebhookFilter"},"providerFilter":{"type":"string","readOnly":true,"description":"`filter` compiled to the provider's own syntax, exactly as sent","example":"(metafields.namespace:custom AND metafields.key:mf1 AND metafields.value:NEW)"}},"required":["subscriptionId","topic","providerTopic","callbackUri"]},"InboundWebhookFilter":{"type":"object","description":"Only events that meet these conditions are delivered. Litecard compiles them into the provider's own filter and checks them again when an event arrives. Not allowed on CUSTOMER_DELETE, whose payload carries only the customer id. Matching is case-sensitive.","additionalProperties":false,"properties":{"match":{"type":"string","enum":["all","any"],"description":"`all` needs every condition to hold, `any` needs one"},"conditions":{"type":"array","minItems":1,"items":{"type":"object","additionalProperties":false,"properties":{"source":{"type":"string","enum":["metafield"]},"namespace":{"type":"string","pattern":"^[A-Za-z0-9_-]+$","example":"custom"},"key":{"type":"string","pattern":"^[A-Za-z0-9_-]+$","example":"mf1"},"op":{"type":"string","enum":["equals","notEquals","exists","notExists"],"description":"`exists` means the metafield has a non-empty value. `value` is required for equals and notEquals only."},"value":{"type":"string","example":"NEW"}},"required":["source","namespace","key","op"]}}},"required":["match","conditions"],"example":{"match":"any","conditions":[{"source":"metafield","namespace":"custom","key":"mf1","op":"equals","value":"NEW"},{"source":"metafield","namespace":"custom","key":"mf1","op":"equals","value":"REDEEMED"},{"source":"metafield","namespace":"custom","key":"litecard_id","op":"exists"}]}},"IntegrationAuthProperties":{"type":"object","properties":{"providerName":{"type":"string","enum":["LC_INBOUND_LISTENER","DOTDIGITAL","TALONONE","KLAVIYO","SQUARE","SHOPIFY","BLOOMREACH"],"description":"Provider name"},"username":{"type":"string","description":"Integration username"},"password":{"type":"string","description":"Integration password"},"campaignId":{"type":"string","description":"Campaign identifier"},"accessToken":{"type":"string","description":"SQUARE: access token to validate against Square without storing it"},"environment":{"type":"string","enum":["sandbox","production"],"description":"SQUARE: which Square environment to validate the token against. Defaults to the stored setting"},"webhook":{"type":"object","description":"BLOOMREACH: one webhook to check against Bloomreach without saving it. Omit to re-check every stored webhook","additionalProperties":false,"required":["baseUrl","projectToken"],"properties":{"id":{"type":"string","description":"Stored webhook being edited. With no apiKey, its stored key is used"},"baseUrl":{"type":"string","minLength":1},"projectToken":{"type":"string","minLength":1},"privateKey":{"type":"boolean","description":"True checks a private key: apiKey is the key id, apiSecret its secret"},"apiKey":{"type":"string","description":"Public key, or the private key id. Required unless id is given"},"apiSecret":{"type":"string","description":"Secret of the private key, with privateKey true"}}}},"required":["providerName"]},"IntegrationDetailsV1":{"type":"object","properties":{"formId":{"type":"string","description":"Form identifier"},"validCredentials":{"type":"boolean","description":"Whether provider credentials are valid"},"hostValidated":{"type":"boolean","description":"Whether host has been validated"},"contactFieldNames":{"type":"array","items":{"type":"string"},"description":"Contact field names"},"syncContact":{"type":"boolean","description":"Whether to sync contact information","example":true},"sendEmail":{"type":"boolean","description":"Whether Litecard triggers the campaign email.","example":true},"passStatusSync":{"type":"object","nullable":true,"description":"DOTDIGITAL: keeps one Dotdigital contact data field at ACTIVE while the pass is in a wallet and INACTIVE once it is removed. Absent or null means Litecard writes nothing after pass create.","properties":{"contactDataField":{"type":"string","description":"The Dotdigital contact data field to write, from contactFieldNames","example":"WALLET_STATUS"}},"required":["contactDataField"]},"emailFieldNames":{"type":"array","items":{"type":"string"},"description":"Email field names"},"campaignId":{"type":"string","description":"Campaign identifier"},"campaigns":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Id of Campaign"},"name":{"type":"string","description":"Name of Campaign"}},"required":["label","key"]},"description":"List of campaigns"},"contactFieldMapping":{"type":"object","additionalProperties":{"type":"string"},"description":"Mapping from contact field -> provider field"},"enabled":{"type":"boolean","description":"Feature enabled flag"},"mergeFieldMapping":{"type":"object","additionalProperties":{"type":"string"},"description":"Merge field mapping"},"webhooks":{"type":"array","description":"Outbound webhooks configured for this form, for providers with their own event API. Never carries the provider integration key.","items":{"$ref":"#/components/schemas/IntegrationWebhookDetails"}},"scheduledJobs":{"type":"array","description":"Scheduled jobs configured for this form, one per job type. Recurring configuration, not individual runs.","items":{"type":"object","properties":{"groupId":{"type":"string"},"type":{"type":"string","example":"EXPORT_CUSTOMER_BALANCES"},"status":{"type":"string","enum":["ACTIVE","PAUSED"]},"intervalInMinutes":{"type":"integer"},"mergeFieldMapping":{"type":"object","additionalProperties":{"type":"string"}},"providerConfig":{"type":"object"},"nextExecutionTime":{"type":"string"},"lastCompletedJobId":{"type":"string"}}}},"litecardFieldNames":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string","description":"Human readable label"},"key":{"type":"string","description":"Field key in payload"},"formField":{"type":"boolean","description":"True when the entry is one of the form's own fields, false when Litecard appends it (pass links, ids, fullName)."},"required":{"type":"boolean","description":"Whether the form enforces this field with a `required` rule. Absent for synthetic entries such as externalId"},"type":{"type":"string","description":"Form field type, matching FormFieldsV1.type, for example \"phone\". Absent for synthetic entries such as externalId"},"external":{"type":"boolean","description":"True when the value is platform-filled at pass create, for example an allocated voucher code. The flow webhook carries no line for such a field, so it can only sync out to Klaviyo"},"externalProviderMapping":{"type":"string","description":"What fills an `external` field, from the template's formFieldSettings.externalProviderMapping, for example VOUCHER_CODE. Absent for non-external fields"}},"required":["label","key"]},"description":"Litecard field names"},"providerName":{"type":"string","enum":["LC_INBOUND_LISTENER","DOTDIGITAL","TALONONE","KLAVIYO","SQUARE","SHOPIFY","BLOOMREACH"],"description":"Provider name"},"apiKey":{"type":"string","description":"Auto-generated API key for authenticating inbound requests"},"contactFields":{"type":"array","description":"SQUARE: the customer properties a Litecard field can be mapped to, the eight built-in ones followed by the merchant's own writable Square custom fields as `custom:<key>`. A property only a typed field can fill says so in its label (birthday needs a date field, address needs an address field)","items":{"type":"object","properties":{"label":{"type":"string","description":"Human readable name"},"value":{"type":"string","description":"Square customer property, e.g. reference_id"}},"required":["label","value"]}},"environment":{"type":"string","enum":["sandbox","production"],"description":"Square environment the access token belongs to. Defaults to production for the demo and enterprise stages, sandbox elsewhere"},"customerMatch":{"type":"string","enum":["emailOrPhone","email","phone"],"description":"Which contact detail identifies an existing Square customer when the pass barcode does not match one. Defaults to emailOrPhone"},"customerMatchOptions":{"type":"array","description":"SQUARE: the customer match strategies, for the picker","items":{"type":"object","properties":{"label":{"type":"string"},"value":{"type":"string","enum":["emailOrPhone","email","phone"]}},"required":["label","value"]}},"customerGroupId":{"type":"string","description":"SQUARE: customer group every synced customer is added to"},"customerGroups":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Square customer group id"},"name":{"type":"string","description":"Square customer group name"}}},"description":"SQUARE: customer groups in the merchant's Square account, for the config dropdown. Only set while validCredentials is true"},"integrationId":{"type":"string","description":"Stable id of the integration entry this response resolved"},"storeName":{"type":"string","description":"SHOPIFY: the connected myshopify store for this form"},"customerIdField":{"type":"string","description":"SHOPIFY: the litecard field key mapped to the Shopify customer id"},"defaultNamespace":{"type":"string","description":"SHOPIFY: metafield namespace used when a mapping omits one"},"defaultType":{"type":"string","description":"SHOPIFY: metafield type used when a mapping omits one"},"events":{"type":"array","items":{"type":"string"},"description":"SHOPIFY: the card events this sync is subscribed to"},"metafields":{"type":"array","description":"SHOPIFY: the saved per-field metafield mappings","items":{"type":"object","properties":{"source":{"type":"string"},"namespace":{"type":"string"},"key":{"type":"string"},"type":{"type":"string"}}}},"listId":{"type":"string","description":"Selected Klaviyo list id"},"mergeFieldSync":{"type":"array","description":"KLAVIYO: the stored rows plus a synthesized starter row for every unmapped mappable key (template fields, externalId, system values), so every mappable field has exactly one row","items":{"type":"object","additionalProperties":false,"required":["litecardField","externalProperty","send","receive"],"properties":{"litecardField":{"type":"string"},"externalProperty":{"type":"string"},"send":{"type":"boolean","description":"Write this field's value to the profile as a custom property. Contact fields (email, name, phone) always populate the native profile attributes regardless of this setting"},"receive":{"type":"boolean"}}}},"eventNames":{"type":"object","additionalProperties":false,"description":"Custom Klaviyo metric names for the pass lifecycle events. An absent key uses the default name resolved at delivery","properties":{"created":{"type":"string"},"downloaded":{"type":"string"},"removed":{"type":"string"}}},"automation":{"type":"object","additionalProperties":false,"description":"Per-event automation toggles. An absent key is treated as true","properties":{"createdAddToList":{"type":"boolean"},"createdSubscribe":{"type":"boolean"},"createdSendEvent":{"type":"boolean"},"createdUpdateProfile":{"type":"boolean"},"downloadedSendEvent":{"type":"boolean"},"downloadedUpdateProfile":{"type":"boolean"},"removedSendEvent":{"type":"boolean"}}},"downloadSync":{"type":"object","nullable":true,"description":"SHOPIFY: the saved download-status metafield config. KLAVIYO: the pass-status profile property the Pass Downloaded and Pass Removed events write (`customField` + `dataType`, no namespace). Absent or null: not configured","properties":{"customField":{"type":"string"},"dataType":{"type":"string","enum":["STRING","BOOLEAN"]},"namespace":{"type":"string"}}},"identifier":{"type":"object","additionalProperties":false,"description":"How the pass owner is matched to a Klaviyo profile. `email` is the default; `mobile` matches on phone number; `uniqueId` matches on the Klaviyo `external_id` taken from the field named in `field`","properties":{"type":{"type":"string","enum":["email","mobile","uniqueId"]},"field":{"type":"string","minLength":1,"description":"Litecard field name. Required and non-empty when `type` is `uniqueId`"}},"required":["type"],"anyOf":[{"properties":{"type":{"enum":["email","mobile"]}}},{"properties":{"type":{"enum":["uniqueId"]}},"required":["field"]}]},"consentField":{"type":"string","description":"Template field whose signup value is the marketing-consent opt-in"},"perOccurrenceEvents":{"type":"boolean","description":"Occurrence-scoped Klaviyo event dedupe. Absent/true: each add/re-download is a distinct event; false: once per card."},"inboundWebhooks":{"type":"array","description":"SHOPIFY: the subscriptions registered inside Shopify for this form's template, one per topic. Reports what Litecard registered, not what Shopify still holds.","items":{"$ref":"#/components/schemas/InboundWebhookDetails"}},"inboundFieldMapping":{"type":"object","description":"SHOPIFY: template field key to the Shopify payload path it is filled from, e.g. `metafield.custom.mf1` or `first_name`. Stored on the form as `shopifySettings.templateMap`.","additionalProperties":{"type":"string"},"example":{"tier":"metafield.custom.mf1","firstName":"first_name"}},"postProcessWrites":{"$ref":"#/components/schemas/ShopifyPostProcessWrites"},"createOnUpdate":{"type":"boolean","description":"SHOPIFY: true when a customers/update event for a customer with no card creates one. Stored on the form as `shopifySettings.createOnUpdate`."},"writeImmediately":{"type":"boolean","description":"SHOPIFY: true when CARD_CREATE writes to Shopify straight away before falling back to the batch; false sends it with the batch only. Stored on the external call as `batchConfig.writeImmediately`."},"templateFields":{"type":"array","description":"SHOPIFY: the template's data fields an inbound event can fill. `userCanEdit` is false for fields the pass holder never sees on the form.","items":{"type":"object","properties":{"key":{"type":"string","example":"tier"},"label":{"type":"string","example":"Tier"},"userCanEdit":{"type":"boolean"}},"required":["key","label","userCanEdit"]}},"customerMetafields":{"type":"array","description":"SHOPIFY: the store's customer metafield definitions, for picking a namespace and key instead of typing them. Empty when the store is not connected or Shopify could not be reached. Metafields without a definition are not listed.","items":{"type":"object","properties":{"namespace":{"type":"string","example":"custom"},"key":{"type":"string","example":"mf1"},"name":{"type":"string","description":"The merchant's display name for the field","example":"Stage"},"type":{"type":"string","description":"Shopify metafield type","example":"single_line_text_field"},"choices":{"type":"array","description":"Allowed values, when the definition restricts them","items":{"type":"string","example":"NEW"}}},"required":["namespace","key","name","type"]}},"flowWebhookBody":{"type":"string","description":"KLAVIYO: the create-flow webhook JSON body for the saved mergeFieldSync, byte-identical to the one the flow installer embeds. Render verbatim for manual setup"},"connected":{"type":"boolean","description":"KLAVIYO: whether an active OAuth connection exists for this business"},"needsReauth":{"type":"boolean","description":"KLAVIYO: true when the stored grant was revoked or rejected (invalid_grant, 401, or 403). The merchant must re-authorize the integration"},"grantedScopes":{"type":"array","items":{"type":"string"},"description":"KLAVIYO: OAuth scopes the merchant granted on the Klaviyo consent screen"},"missingScopes":{"type":"array","items":{"type":"string"},"description":"KLAVIYO: requested OAuth scopes the merchant declined on the consent screen. Only set while `connected` is true and `needsReauth` is false"},"webhookKey":{"type":"string","description":"KLAVIYO: per-business secret for the public flow webhook, sent in the `x-litecard-key` header. Only set while `connected` is true"},"connectionId":{"type":"string","description":"KLAVIYO: OAuth connection this form's entry reads from"},"installedFlow":{"type":"object","description":"KLAVIYO: the flow set created by POST /integrations/klaviyo/flow-install","properties":{"flowId":{"type":"string","description":"Id of the created \"Litecard: Create Wallet Pass\" flow"},"flowUrl":{"type":"string"},"installedAt":{"type":"string"},"deliverFlowId":{"type":"string","description":"Id of the created \"Litecard: Deliver Wallet Pass\" flow"},"deliverFlowUrl":{"type":"string"},"followUpFlowId":{"type":"string","description":"Id of the created \"Litecard: Pass Downloaded Follow-up\" flow"},"followUpFlowUrl":{"type":"string"}}},"lists":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Klaviyo list id"},"name":{"type":"string","description":"Klaviyo list name"}}},"description":"KLAVIYO: lists available in the connected Klaviyo account, for the config dropdown"}}},"IntegrationValidationRequestBody":{"allOf":[{"$ref":"#/components/schemas/IntegrationAuthProperties"}]},"IntegrationWebhookConfig":{"type":"object","description":"One provider endpoint Litecard should send pass events to. Mirrors the stored record, so `eventNameMapping` means exactly what it does on webhook registration.","properties":{"id":{"type":"string","description":"Id of the entry being edited. Omit to create a new one. An id the form does not already own is rejected.","example":"V1StGXR8_Z5jdHi6B-myT"},"baseUrl":{"type":"string","description":"Provider API base URL for this deployment, no path","example":"https://acme.europe-west1.talon.one"},"apiKey":{"type":"string","description":"Provider integration key. Write-only: accepted on create and validate, never returned. Required when creating; on an edit, supplying it rotates the stored key and omitting it keeps it.","example":"dbc644d33aa74d582bd9479c59e16f97"},"eventNameMapping":{"type":"object","description":"Litecard event to the name the provider knows it by. Keys must be CARD_CREATE, CARD_UPDATE, CARD_DOWNLOAD or CARD_REMOVE; an event with no entry is not forwarded.","additionalProperties":{"type":"string"},"minProperties":1,"example":{"CARD_DOWNLOAD":"litecard_card_downloaded","CARD_REMOVE":"litecard_card_removed"}}},"required":["baseUrl","eventNameMapping"]},"IntegrationWebhookDetails":{"type":"object","description":"A configured integration webhook. The provider key is never returned.","properties":{"id":{"type":"string","description":"Id of the stored external-call record. Send it back to edit this entry; omit it from the list to remove the entry.","example":"V1StGXR8_Z5jdHi6B-myT"},"baseUrl":{"type":"string","description":"Provider API base URL for this deployment","example":"https://acme.europe-west1.talon.one"},"eventNameMapping":{"type":"object","description":"Litecard event to the name the provider knows it by. Keys must be CARD_CREATE, CARD_UPDATE, CARD_DOWNLOAD or CARD_REMOVE; an event with no entry is not forwarded.","additionalProperties":{"type":"string"},"minProperties":1,"example":{"CARD_DOWNLOAD":"litecard_card_downloaded","CARD_REMOVE":"litecard_card_removed"}},"projectToken":{"type":"string","description":"BLOOMREACH: project token the events are added to","example":"12345678-abcd-1234-abcd-1234567890ab"},"customerIdName":{"type":"string","description":"BLOOMREACH: hard customer id the card's externalId is sent under","example":"registered"},"privateKey":{"type":"boolean","description":"BLOOMREACH: whether this webhook authenticates with a private key (key id and secret) rather than a public API key. The credential itself is never returned"},"validated":{"type":"boolean","description":"Whether the dry-run call to the provider passed when this entry was last written. Not a live check.","example":true}},"required":["id","baseUrl","eventNameMapping","validated"]},"KlaviyoFormIntegrationRecord":{"allOf":[{"$ref":"#/components/schemas/FormIntegrationRecordBase"},{"type":"object","properties":{"name":{"type":"string","enum":["KLAVIYO"]},"listId":{"type":"string","description":"Klaviyo list profiles are added to"},"mergeFieldSync":{"type":"array","description":"Explicit per-field sync rows, replaced as a whole on save. `litecardField` is a template field key, the synthetic `externalId`, or a system value key (`appleLink`, `googleLink`, `downloadId`, `litecardId`, `landingPage`, `qrCode`). `externalProperty` is the Klaviyo profile property the field binds to; it must not contain `'`, `{` or `}` (they corrupt the installed flow's webhook template). `send` writes the value to Klaviyo as a custom profile property on pass create; `receive` makes the installed flow webhook carry the field into pass creation. EXTERNAL (platform-filled) fields and system value keys must have `receive: false`. A field with no row rides its starter row; `litecardField` must be unique across rows","items":{"type":"object","additionalProperties":false,"required":["litecardField","externalProperty","send","receive"],"properties":{"litecardField":{"type":"string"},"externalProperty":{"type":"string"},"send":{"type":"boolean","description":"Write this field's value to the profile as a custom property. Contact fields (email, name, phone) always populate the native profile attributes regardless of this setting"},"receive":{"type":"boolean"}}}},"eventNames":{"type":"object","additionalProperties":false,"description":"Custom Klaviyo metric names for the pass lifecycle events. An absent key uses the default name resolved at delivery","properties":{"created":{"type":"string"},"downloaded":{"type":"string"},"removed":{"type":"string"}}},"automation":{"type":"object","additionalProperties":false,"description":"Per-event automation toggles. An absent key is treated as true","properties":{"createdAddToList":{"type":"boolean"},"createdSubscribe":{"type":"boolean"},"createdSendEvent":{"type":"boolean"},"createdUpdateProfile":{"type":"boolean"},"downloadedSendEvent":{"type":"boolean"},"downloadedUpdateProfile":{"type":"boolean"},"removedSendEvent":{"type":"boolean"}}},"downloadSync":{"type":"object","additionalProperties":false,"description":"KLAVIYO: the profile property the Pass Downloaded and Pass Removed events write, and the only property those events carry. STRING writes ACTIVE while the pass is in a wallet and INACTIVE once removed; BOOLEAN writes whether the pass is in a wallet. Required while either event is enabled","required":["customField","dataType"],"properties":{"customField":{"type":"string","minLength":1,"description":"Klaviyo custom profile property name"},"dataType":{"type":"string","enum":["STRING","BOOLEAN"]}}},"identifier":{"type":"object","additionalProperties":false,"description":"How the pass owner is matched to a Klaviyo profile. `email` is the default; `mobile` matches on phone number; `uniqueId` matches on the Klaviyo `external_id` taken from the field named in `field`","properties":{"type":{"type":"string","enum":["email","mobile","uniqueId"]},"field":{"type":"string","minLength":1,"description":"Litecard field name. Required and non-empty when `type` is `uniqueId`"}},"required":["type"],"anyOf":[{"properties":{"type":{"enum":["email","mobile"]}}},{"properties":{"type":{"enum":["uniqueId"]}},"required":["field"]}]},"consentField":{"type":"string","description":"Template field whose signup value is the marketing-consent opt-in"},"perOccurrenceEvents":{"type":"boolean","description":"Occurrence-scoped Klaviyo event dedupe. Absent/true: each add/re-download is a distinct event; false: once per card."},"installedFlow":{"type":"object","description":"The one-click-installed Klaviyo flow set, written by flow-install and carried through config saves","properties":{"flowId":{"type":"string"},"flowUrl":{"type":"string"},"installedAt":{"type":"string"},"emailTemplateId":{"type":"string"},"deliverFlowId":{"type":"string"},"deliverFlowUrl":{"type":"string"},"followUpFlowId":{"type":"string"},"followUpFlowUrl":{"type":"string"}}}},"required":["name"]}]},"KlaviyoUpdateIntegrationConfig":{"type":"object","description":"Klaviyo config update. Carries only Klaviyo settings; fields belonging to other providers are rejected. PATCH semantics: an absent field preserves the stored value, and seeded defaults apply only on first attach. A form field keyed `profile_id` is rejected, because the flow webhook body reserves that key for the Klaviyo profile id","additionalProperties":false,"properties":{"providerName":{"type":"string","enum":["KLAVIYO"]},"enabled":{"type":"boolean","description":"Whether the integration is enabled"},"listId":{"type":"string","description":"Klaviyo list this form syncs to. An empty string clears the stored list"},"mergeFieldSync":{"type":"array","description":"Explicit per-field sync rows, replaced as a whole on save. `litecardField` is a template field key, the synthetic `externalId`, or a system value key (`appleLink`, `googleLink`, `downloadId`, `litecardId`, `landingPage`, `qrCode`). `externalProperty` is the Klaviyo profile property the field binds to; it must not contain `'`, `{` or `}` (they corrupt the installed flow's webhook template). `send` writes the value to Klaviyo as a custom profile property on pass create; `receive` makes the installed flow webhook carry the field into pass creation. EXTERNAL (platform-filled) fields and system value keys must have `receive: false`. A field with no row rides its starter row; `litecardField` must be unique across rows","items":{"type":"object","additionalProperties":false,"required":["litecardField","externalProperty","send","receive"],"properties":{"litecardField":{"type":"string"},"externalProperty":{"type":"string"},"send":{"type":"boolean","description":"Write this field's value to the profile as a custom property. Contact fields (email, name, phone) always populate the native profile attributes regardless of this setting"},"receive":{"type":"boolean"}}}},"eventNames":{"type":"object","additionalProperties":false,"description":"Custom Klaviyo metric names for the pass lifecycle events. An absent key uses the default name resolved at delivery","properties":{"created":{"type":"string"},"downloaded":{"type":"string"},"removed":{"type":"string"}}},"automation":{"type":"object","additionalProperties":false,"description":"Per-event automation toggles. An absent key is treated as true","properties":{"createdAddToList":{"type":"boolean"},"createdSubscribe":{"type":"boolean"},"createdSendEvent":{"type":"boolean"},"createdUpdateProfile":{"type":"boolean"},"downloadedSendEvent":{"type":"boolean"},"downloadedUpdateProfile":{"type":"boolean"},"removedSendEvent":{"type":"boolean"}}},"downloadSync":{"type":"object","additionalProperties":false,"description":"KLAVIYO: the profile property the Pass Downloaded and Pass Removed events write, and the only property those events carry. STRING writes ACTIVE while the pass is in a wallet and INACTIVE once removed; BOOLEAN writes whether the pass is in a wallet. Required while either event is enabled. Null clears the stored property (only valid with both events off); absent leaves it unchanged","required":["customField","dataType"],"properties":{"customField":{"type":"string","minLength":1,"description":"Klaviyo custom profile property name"},"dataType":{"type":"string","enum":["STRING","BOOLEAN"]}},"nullable":true},"identifier":{"type":"object","additionalProperties":false,"description":"How the pass owner is matched to a Klaviyo profile. `email` is the default; `mobile` matches on phone number; `uniqueId` matches on the Klaviyo `external_id` taken from the field named in `field`. Null clears the stored identifier, reverting to the `email` default; absent leaves it unchanged","properties":{"type":{"type":"string","enum":["email","mobile","uniqueId"]},"field":{"type":"string","minLength":1,"description":"Litecard field name. Required and non-empty when `type` is `uniqueId`"}},"required":["type"],"anyOf":[{"properties":{"type":{"enum":["email","mobile"]}}},{"properties":{"type":{"enum":["uniqueId"]}},"required":["field"]}],"nullable":true},"consentField":{"type":"string","description":"Template field whose signup value is the marketing-consent opt-in"},"perOccurrenceEvents":{"type":"boolean","description":"Occurrence-scoped Klaviyo event dedupe. Absent/true: each add/re-download is a distinct event; false: once per card."}},"required":["providerName"]},"ListCertificatesResponseV1":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the certificate","example":"freetier-apple"},"businessId":{"type":"string","description":"Unique identifier for the business","example":"L3ZrB748fLxlFD8ZjFVgX"},"name":{"type":"string","description":"Name of the certificate"},"applePassTypeIdentifier":{"type":"string","description":"Pass Type Identifier (APPLE only)","example":"pass.com.example.test"},"appleTeamIdentifier":{"type":"string","description":"Team identifier (APPLE only)","example":"ABCDE12345"},"type":{"type":"string","description":"Type of pass","example":"APPLE"},"expiryDate":{"type":"string","description":"Timestamp of when pass will expire","example":"2023-01-01T00:00:00.000Z"},"isMaster":{"type":"boolean","description":"Whether the certificate is set up in master business level","example":true}},"required":["id","businessId","name","type","expiryDate"]},"ListNotificationGroupsResponse":{"type":"object","properties":{"groups":{"type":"array","description":"Array of notification groups","items":{"$ref":"#/components/schemas/NotificationGroup"}},"next":{"type":"string","description":"Next key for dynamoDB query"}}},"ListPassOfCardOwnerRequestV1":{"type":"object","properties":{"email":{"type":"string","description":"Email of Card Owner"},"phone":{"type":"string","description":"Phone number of Card Owner"}}},"ListPassRequestV1":{"type":"object","description":"Filters for listing passes.\n\nCursor-paginated: the response carries `next`, and sending it back gets the following page. Stop when it is absent, and do not construct cursors yourself.\n\nUse `projectionExpression` to fetch only the fields you need. On large result sets that is the difference between a fast page and a slow one.","properties":{"next":{"type":"string","description":"Next key for dynamoDB query"},"projectionExpression":{"type":"string","description":"What keys to get inside card table"}}},"ListTemplatesV1":{"type":"array","items":{"$ref":"#/components/schemas/TemplateV1"}},"ListWebhooksResponse":{"type":"object","properties":{"webhooks":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"}}}},"Location":{"type":"object","properties":{"id":{"type":"string","description":"Id of location","example":"location-123"},"lat":{"type":"string","description":"Latitude coordinate of location","example":"-37.80690913039505"},"lon":{"type":"string","description":"Longitude coordinate of location","example":"144.986263128836"},"locationName":{"type":"string","description":"Name of location","example":"Litecard HQ"},"address":{"type":"string","description":"Street address of the location. Shown in Samsung Wallet's Geofence Notification location details.","example":"123 Broad Street"},"businessId":{"type":"string","description":"BusinessId of Location"},"externalId":{"type":"string","description":"External system identifier for the location","example":"ext-location-456"},"notificationSettings":{"$ref":"#/components/schemas/LocationNotificationSettings"},"createdAt":{"type":"string","description":"Record creation date"}}},"LocationNotificationSettings":{"type":"object","description":"Notification settings for a location","properties":{"apple":{"type":"object","description":"Notification settings for Apple devices","properties":{"lockScreenMessage":{"type":"string","description":"Optional fallback wording for this place. A location entry on a template or pass carries its own apple.lockScreenMessage and that always wins; this is used only when the entry leaves it out. Required at creation only when the business has locationNotificationsIndependentOfTemplates turned on, otherwise the call fails with 400 MISSING_LOCK_SCREEN_MESSAGE.","example":"Use your pass to get rewards at our offline store!"}}}}},"MailchimpTagSettings":{"type":"object","description":"Mailchimp Tag Settings","properties":{"value":{"type":"string","description":"Used if value is static. Value of the tag to be applied."},"fieldMap":{"type":"string","description":"If value is not static, map it to a specific form field"},"static":{"type":"boolean","description":"If the value is static across all passes or if it's dynamic based on pass"}}},"ManagedUser":{"type":"object","description":"A user managed by the business","properties":{"userId":{"type":"string","description":"user_id in Auth0 user database","example":"auth0|abc123"},"email":{"type":"string","format":"email","example":"user@example.com"},"username":{"type":"string","example":"user@example.com"},"picture":{"type":"string","example":"https://..."},"userGroupId":{"type":"string","description":"Assigned user group ID"},"createdAt":{"type":"string","format":"date-time"},"lastLoginAt":{"type":"string","format":"date-time"},"emailVerified":{"type":"boolean","description":"Whether the user's email is verified"},"apiAccessAllowed":{"type":"boolean"},"uiDashboardAccess":{"type":"array","items":{"type":"string"}},"roles":{"type":"array","items":{"type":"string"}}}},"MigrateTemplateRequestBody":{"type":"object","properties":{"templateId":{"type":"string","description":"Template ID","example":"template-123"},"cardId":{"type":"string","description":"Card ID","example":"card-123"}}},"MigrateTemplateResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the template was migrated successfully","example":true},"googleLink":{"type":"string","description":"Google Link","example":"https://pay.google.com/gp/v/save/..."}}},"NextAvailableBusinessRuleV1":{"allOf":[{"$ref":"#/components/schemas/TemplateBusinessRuleV1"},{"type":"object","required":["executeAfter"],"properties":{"executeAfter":{"type":"integer","minimum":1,"description":"Number of additional scans before this rule becomes eligible"}}}]},"NextAvailableScanRuleV1":{"allOf":[{"$ref":"#/components/schemas/TemplateScanRuleV1"},{"type":"object","required":["executeAfter"],"properties":{"executeAfter":{"type":"integer","minimum":1,"description":"Number of additional scans before this rule becomes eligible"}}}]},"NotificationGroup":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the notification group"},"title":{"type":"string","description":"Title of the notification group. Supports editable placeholders in the format {{<dataField.key>}}"},"message":{"type":"string","description":"Message content of the notification group. Supports editable placeholders in the format {{<dataField.key>}}"},"fallbackTitle":{"type":"string","description":"Required when title or message includes placeholders. Used when placeholder values cannot be resolved for a pass. Must not contain placeholders"},"fallbackMessage":{"type":"string","description":"Required when title or message includes placeholders. Used when placeholder values cannot be resolved for a pass. Must not contain placeholders"},"cardIdsS3Key":{"type":"string","description":"S3 Key where the card IDs are stored for the notification group"},"cardIdsLength":{"type":"integer","description":"Total number of card IDs in the notification group"},"participantsCount":{"type":"integer","description":"Number of participants in the notification group"},"totalEligiblePasses":{"type":"integer","description":"Total number of eligible passes for the notification group"},"sentVia":{"type":"array","description":"Array of objects detailing the count of notifications sent via each method","items":{"type":"object","required":["type","count"],"properties":{"type":{"type":"string","description":"The method used for sending the notification","enum":["EMAIL","PUSH_NOTIFICATION"]},"count":{"type":"integer","description":"Number of notifications sent using the method","minimum":0},"platform":{"type":"string","description":"Platform used for sending the notification","enum":["apple","google","samsung","email","sms"]},"successCount":{"type":"integer","description":"Number of successful notifications sent","minimum":0},"failedCount":{"type":"integer","description":"Number of failed notifications","minimum":0}}}},"sendTime":{"type":"string","format":"date-time","description":"Creation date and time of the notification group"},"expiresAt":{"type":"string","format":"date-time","description":"Google Push Notification expiry date and time for the notification group"},"createdAt":{"type":"string","format":"date-time","description":"Creation date and time of the notification group"},"status":{"type":"string","description":"Current status of the notification group","enum":["PENDING","SENT","IN_PROGRESS","FAILED","CANCELLED","QUEUED"]},"businessId":{"type":"string","description":"ID of the business that owns the notification group"},"deliveryReports":{"type":"object","description":"Object containing delivery reports for the notification group, keyed by notification method","additionalProperties":{"type":"object","description":"Delivery report per platform (e.g. email, sms)","additionalProperties":{"type":"integer"}}},"recurringCounter":{"type":"number","description":"Counter to keep track of how many times a recurring notification has been sent"},"recurringConfig":{"$ref":"#/components/schemas/NotificationRecurringConfig"}}},"NotificationRecurringConfig":{"type":"object","description":"Configuration for recurring notifications","properties":{"recurrenceType":{"type":"string","enum":["DAILY","WEEKDAY","WEEKEND","MONTHLY_DATES","CUSTOM_DAYS"],"description":"Type for recurring notification","example":"WEEKDAY"},"sendTime":{"type":"string","description":"Time to send the notification. Supports UTC format (HH:mm:ss.sssZ) or time with timezone offset (HH:mm:ss+HH:mm or HH:mm:ss-HH:mm). Recommended to use time with timezone offset.","example":"01:00:00+07:00"},"condition":{"type":"object","description":"Termination rule for recurring notifications. At least one of occurrences or endDate must be provided","properties":{"occurrences":{"type":"number","description":"Maximum number of times to send the notification.","example":5},"endDate":{"type":"string","description":"UTC timestamp when recurring notifications should stop.","example":"2022-12-31T04:41:15.246Z"}},"anyOf":[{"required":["occurrences"]},{"required":["endDate"]}]},"customConfig":{"type":"object","description":"Custom configuration for recurring notifications. Applicable for MONTHLY_DATES and CUSTOM_DAYS","properties":{"days":{"type":"array","description":"Day names for custom recurring notifications. Only applicable when recurrenceType is CUSTOM_DAYS","items":{"type":"string","enum":["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],"example":"Saturday","description":"Day name (e.g., Monday, Tuesday, Wednesday, etc.)"}},"weekInterval":{"type":"number","description":"Number of weeks between notifications. Only applicable for CUSTOM_DAYS","example":2},"dates":{"type":"array","description":"Day of month for recurring notifications. Only applicable for MONTHLY_DATES","items":{"type":"number","example":15,"description":"Day of month (1-31)"}}}},"expiryInSeconds":{"type":"number","description":"Duration in seconds after the scheduled sendTime before the notification expires. Applies only to Google Wallet push notifications."}},"required":["recurrenceType","sendTime","condition"]},"NotificationSegments":{"type":"object","description":"Narrow one template down to the passes carrying particular segment values. A pass must match every slot you fill: the values inside one slot are alternatives, and a slot you leave out matches everything. Used as an audience strategy in its own right when cardIds, templateIds and templateTier are all empty, and as a filter on top of templateTier when that is set.","properties":{"templateId":{"type":"string","description":"The template to slice. Required when segments is the audience strategy; ignored when segments is narrowing a templateTier, since each entry there names its own template."},"segment1":{"type":"array","items":{"type":"string","description":"Segment Value"},"description":"List of segment values"},"segment2":{"type":"array","items":{"type":"string","description":"Segment Value"},"description":"List of segment values"},"segment3":{"type":"array","items":{"type":"string","description":"Segment Value"},"description":"List of segment values"},"segment4":{"type":"array","items":{"type":"string","description":"Segment Value"},"description":"List of segment values"},"segment5":{"type":"array","items":{"type":"string","description":"Segment Value"},"description":"List of segment values"}},"required":["templateId"]},"NotificationSettings":{"description":"Settings for setting up automated notifications","allOf":[{"$ref":"#/components/schemas/BaseNotificationSettings"},{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the notification setting"}},"required":["trigger","type","title","message","delay","unit"]}]},"NotificationTemplateTier":{"type":"object","properties":{"templateId":{"type":"string","description":"ID of the template. Template must be a multi tier template."},"tier":{"type":"string","description":"Name of the tier to filter by"}},"required":["templateId","tier"]},"NotificationV1":{"type":"object","description":"Who to notify, and what to say.\n\n`notification` is the message. The remaining fields select the audience, and they are mutually exclusive: `cardIds`, then `templateIds`, then `templateTier`, then `segments`, then `options.sendAll`. Litecard stops at the first one you set and ignores the rest.\n\nPush notifications reach the lock screen through the wallet pass itself, with no app install. Sends are queued, recorded as a notification group, and can be cancelled before they fire but never recalled after.","properties":{"cardIds":{"type":"array","description":"Exact passes to notify, and the highest-precedence audience strategy: set it and templateIds, templateTier, segments and sendAll are all ignored. Up to 10,000 ids per call: batch a larger audience into successive calls rather than looping one id at a time. Cannot be combined with notification.recurringConfig. Required, so send an empty array when you are selecting the audience another way.","items":{"type":"string","description":"id of card that was created","example":"abc123"}},"notification":{"type":"object","properties":{"title":{"type":"string","description":"Becomes the label of the field named by dataFieldKey. Apple shows it on the back of the pass; Google uses it as the message header. Supports {{placeholders}}, which require fallbackTitle.","example":"Litecard - Notification"},"fallbackTitle":{"type":"string","description":"Required when title includes placeholders. Used when placeholder values cannot be resolved for a pass. Must not contain placeholders","example":"Litecard Notification"},"message":{"type":"string","description":"Becomes the value of the field named by dataFieldKey, and is the line that reaches the lock screen. Emoji are supported and a newline renders as a line break. Supports {{placeholders}}, which require fallbackMessage.","example":"Welcome to Litecard"},"fallbackMessage":{"type":"string","description":"Required when message includes placeholders. Used when placeholder values cannot be resolved for a pass. Must not contain placeholders","example":"You have a new update from Litecard"},"dataFieldKey":{"type":"string","description":"Always 'notificationKey'. Litecard keeps one dedicated field on every pass for notification text, and this is its key. Send it as a constant.","example":"notificationKey"},"sendTime":{"type":"string","description":"Time to send the notification. Uses UTC time.","example":"2022-11-07T04:31:15.246Z"},"expiresAt":{"type":"string","description":"Time for the Google Wallet notification to expire. Uses UTC time.","example":"2022-11-14T04:41:15.246Z"},"recurringConfig":{"$ref":"#/components/schemas/NotificationRecurringConfig"}},"required":["title","message","dataFieldKey"]},"segments":{"$ref":"#/components/schemas/NotificationSegments"},"templateIds":{"type":"array","description":"Second in precedence, used when cardIds is empty. Notify everyone holding a pass from these templates: the union of every active pass across the ids. Use a single templateId whenever the title or message contains {{placeholders}}: placeholder keys come from template.dataFields[].key, so any template in the list that does not define a key sends your fallback to all of its holders, silently. Several ids are safe only when they all define the same keys.","items":{"type":"string","description":"card template id","example":"template-123"}},"templateTier":{"type":"array","description":"Third in precedence, used when cardIds and templateIds are both empty. Each entry names a template and one of its tiers; entries are queried separately and the results unioned. The template ids are not validated against each other, so a list spanning several templates is accepted silently, as is a tier the named template does not have. Send to one template per call, one tier or several: that is what this strategy is indexed for, and mixing templates breaks {{placeholders}} for the same reason multiple templateIds does. This is also the only audience field that combines with another: send segments alongside it and each entry's passes are then filtered by those segment values.","items":{"$ref":"#/components/schemas/NotificationTemplateTier"}},"options":{"type":"object","properties":{"email":{"type":"boolean","description":"Send email notification","default":false},"emailType":{"type":"string","description":"Optional. Senders email address, standard is used for most cases unless a marketing email address has been set up","enum":["STANDARD","MARKETING"],"default":"STANDARD"},"pushNotification":{"type":"boolean","description":"Send push notification","default":true},"sendAll":{"type":"boolean","description":"Last in precedence, used only when cardIds, templateIds, templateTier and segments are all absent. Sends to every active pass in the business, across every template. Rarely the right choice: the copy has to suit every owner, there is no unsend, and placeholders will resolve to your fallback for most of them because each template defines its own dataFields.","default":false}},"required":["pushNotification"]}},"required":["notification","cardIds"]},"PassCountDetails":{"allOf":[{"type":"object","properties":{"id":{"type":"string","description":"The unique identifier for the business."},"name":{"type":"string","description":"The name of the business."}},"required":["id","name"]},{"$ref":"#/components/schemas/PassCountDetailsForTemplate"}],"description":"Statistics for a single business related to pass counts."},"PassCountDetailsForTemplate":{"type":"object","properties":{"downloadedApplePassCount":{"type":"integer","format":"int32","description":"The count of Apple passes downloaded for this business."},"downloadedGooglePassCount":{"type":"integer","format":"int32","description":"The count of Google passes downloaded for this business."},"deletedApplePassCount":{"type":"integer","format":"int32","description":"The count of Apple passes downloaded for this business."},"deletedGooglePassCount":{"type":"integer","format":"int32","description":"The count of Google passes downloaded for this business."},"cardsCount":{"type":"integer","format":"int32","description":"The count of cards created during this duration."},"downloadsCount":{"type":"integer","format":"int32","description":"The count of card downloads for this business."},"activeCardsCount":{"type":"integer","format":"int32","description":"The count of active cards created during this duration."},"deletedCardsCount":{"type":"integer","format":"int32","description":"The count of deleted cards created during this duration."},"inactiveCardsCount":{"type":"integer","format":"int32","description":"The count of inactive cards created during this duration."},"inactiveApplePassCount":{"type":"integer","format":"int32","description":"The count of inactive Apple passes for this business."},"inactiveGooglePassCount":{"type":"integer","format":"int32","description":"The count of inactive Google passes for this business."},"paymentsRequiredCount":{"type":"integer","format":"int32","description":"The count of passes payments is not yet completed for this business."}},"description":"Statistics for a business or template related to pass counts."},"PassCountsForTemplate":{"type":"object","properties":{"templateId":{"type":"string","description":"Id of the template for which the pass counts are retrieved","example":"template-123"},"duration":{"type":"string","enum":["PAST_7_DAYS","PAST_30_DAYS","ALL"],"description":"Duration for which the pass counts are retrieved"},"passCounts":{"$ref":"#/components/schemas/PassCountDetailsForTemplate"}},"description":"Schema for retrieving pass counts for a specific template over a specified duration."},"PassCountsHistory":{"type":"object","properties":{"duration":{"type":"string","enum":["PAST_7_DAYS","PAST_30_DAYS","ALL"],"description":"Duration "},"businesses":{"type":"array","items":{"$ref":"#/components/schemas/PassCountDetails"},"description":"List of businesses related to the pass counts."}},"description":"Schema for the history of pass counts including the duration and associated businesses."},"PassTrendDetails":{"type":"object","properties":{"date":{"type":"string","format":"date in YYYY-MM-DD","description":"The date for the pass trend.(YYYY-MM-DD)"},"downloadedApplePassCount":{"type":"integer","format":"int32","description":"The count of Apple passes downloaded for this business."},"downloadedGooglePassCount":{"type":"integer","format":"int32","description":"The count of Google passes downloaded for this business."},"deletedApplePassCount":{"type":"integer","format":"int32","description":"The count of Apple passes downloaded for this business."},"deletedGooglePassCount":{"type":"integer","format":"int32","description":"The count of Google passes downloaded for this business."},"cardsCount":{"type":"integer","format":"int32","description":"The count of cards created during this duration."},"downloadsCount":{"type":"integer","format":"int32","description":"The count of card downloads for this duration."},"activeCardsCount":{"type":"integer","format":"int32","description":"The count of active cards created during this duration."},"deletedCardsCount":{"type":"integer","format":"int32","description":"The count of deleted cards created during this duration."},"inactiveCardsCount":{"type":"integer","format":"int32","description":"The count of inactive cards created during this duration."},"inactiveApplePassCount":{"type":"integer","format":"int32","description":"The count of inactive Apple passes for this business."},"inactiveGooglePassCount":{"type":"integer","format":"int32","description":"The count of inactive Google passes for this business."},"paymentsRequiredCount":{"type":"integer","format":"int32","description":"The count of passes payments is not yet completed for this business."}},"required":["date","downloadedApplePassCount","downloadedGooglePassCount"],"description":"Statistics for a single business related to pass counts."},"ProcessCardUploadGroupAndPresignedUrlRequestBody":{"type":"object","description":"Request body for processing a card upload group and generating a presigned URL","properties":{"templateId":{"type":"string","example":"template-1"},"headerMappings":{"type":"object","additionalProperties":{"type":"string"},"example":{"name":"Full Name","age":"Years Old","email":"Email Address"}},"fileName":{"type":"string","example":"cards.csv"},"overrideExisting":{"type":"boolean","example":true},"smsInvitationEnabled":{"type":"boolean","example":true},"emailInvitationEnabled":{"type":"boolean","example":true},"returnFile":{"type":"boolean","deprecated":true,"description":"Deprecated, and ignored on this upload path. It used to push the results CSV to the business's SFTP server, and only the older pipeline still acts on it. Do not send it on new integrations.","example":false},"fastCreate":{"type":"boolean","description":"If fast create is to be enabled","example":false}},"required":["templateId","fileName","headerMappings"]},"ProcessCardUploadGroupAndPresignedUrlResponse":{"type":"object","description":"Response for processing a card upload group and generating a presigned URL","properties":{"groupId":{"type":"string","example":"group-123"},"uploadUrl":{"type":"string","example":"https://litecard-dev-uploads.s3.ap-southeast-2.amazonaws.com/business-id/card-uploads/group-id/upload.csv?AWSAccessKeyId=AKIA...&Expires=1700000000&Signature=abc123"},"status":{"type":"string","example":"pending"}}},"PublicRedeemRequestBody":{"type":"object","properties":{"barcodeValue":{"type":"string","description":"barcode value from pass","example":"1234567890"},"businessId":{"type":"string","description":"ID of the business that owns the pass","example":"R9xh12dga8i"},"updatePayload":{"type":"object","description":"Update payload"}},"required":["barcodeValue","businessId","updatePayload"]},"PublicScanBarcodeV1RequestBody":{"type":"object","description":"Resolve a scanned barcode into its pass and owner. The public variant, for consumer-facing scanners.\n\nThis reads only. To make the scan count for something, follow it with `POST /api/v1/scans/actions`.","properties":{"barcodeValue":{"type":"string","description":"barcode value from pass","example":"1234567890"},"businessId":{"type":"string","description":"ID of the business that owns the pass","example":"R9xh12dga8i"}},"required":["barcodeValue","businessId"]},"RateLimitV1":{"type":"object","description":"Card Scan Rate Limits","oneOf":[{"type":"object","properties":{"enabled":{"type":"boolean","description":"The rateLimit is enabled / disabled for this template","example":true},"limitType":{"type":"string","description":"Type of Rate Limit either INTERVAL or DATE","example":"INTERVAL","enum":["INTERVAL"]},"rule":{"type":"object","properties":{"duration":{"type":"number","description":"Time Duration for the limit and unit is seconds","example":300},"count":{"type":"number","description":"Numbers of limit count","example":10}}}}},{"type":"object","properties":{"enabled":{"type":"boolean","description":"The rateLimit is enabled / disabled for this template","example":true},"limitType":{"type":"string","description":"Type of Rate Limit either INTERVAL or DATE","example":"DATE","enum":["DATE"]},"rule":{"type":"object","properties":{"dateLimit":{"type":"object","properties":{"amount":{"type":"string","description":"The amount of time between scan periods.","example":"10"},"measurement":{"type":"string","description":"The unit of measurement to calculate between scan periods.","example":"YEARS","enum":["DAYS","MONTHS","YEARS"]}}}}}}}]},"RegisterInboundWebhookRequestBody":{"type":"object","description":"Register inbound webhook subscriptions for an external provider","properties":{"provider":{"type":"string","description":"The external provider to register subscriptions with","enum":["SHOPIFY"],"example":"SHOPIFY"},"templateId":{"type":"string","description":"Template the subscription events are delivered to","example":"AyIbjHCCGnYZwq9PdkB79"},"topics":{"type":"array","description":"Generic topics to subscribe to. Defaults to all topics supported by the provider.","items":{"type":"string","enum":["CUSTOMER_CREATE","CUSTOMER_UPDATE","CUSTOMER_DELETE"],"example":"CUSTOMER_CREATE"}},"config":{"type":"object","description":"Provider-specific configuration. For Shopify, requires { storeName } and optionally { metafieldNamespaces }.","additionalProperties":true,"properties":{"storeName":{"type":"string","description":"Shopify store name (myshopify.com subdomain)","example":"litecard-dev-store"},"metafieldNamespaces":{"type":"array","description":"Metafield namespaces to attach to every delivery. Webhook payloads carry no metafields without this. Fixed at creation time: changing it requires deleting and re-registering the subscription.","items":{"type":"string","example":"loyalty"}},"filter":{"$ref":"#/components/schemas/InboundWebhookFilter"}},"example":{"storeName":"litecard-dev-store","metafieldNamespaces":["loyalty"]}}},"required":["provider","templateId"]},"RegisterScheduledJobRequestBody":{"type":"object","required":["templateId","providerName","type","providerConfig"],"properties":{"templateId":{"type":"string"},"providerName":{"type":"string","enum":["TALONONE"]},"type":{"type":"string","enum":["EXPORT_CUSTOMER_BALANCES"]},"intervalInMinutes":{"type":"integer","minimum":1,"default":10,"description":"How long after one run starts the next is scheduled."},"startInMinutes":{"type":"integer","minimum":0,"default":1,"description":"Delay before the first run. 0 starts it immediately, which is how to verify the credentials without waiting a full interval."},"providerConfig":{"description":"Settings for the partner named by `providerName`. Widens as providers are added.","oneOf":[{"$ref":"#/components/schemas/TalonOneConfig"}]},"mergeFieldMapping":{"type":"object","additionalProperties":{"type":"string"},"description":"Template dataField keys this job writes into. Every value must be an editable field on the template."}}},"SamsungAppLinkingSettings":{"type":"object","description":"Samsung Settings for AppLinking","properties":{"appLinkName":{"type":"string","description":"Name of the relevant app, shown on the Samsung Wallet card"},"appLinkData":{"type":"string","description":"URL to the app. Recommended to use a dynamic link"}},"required":["appLinkName","appLinkData"]},"SamsungLayoutSectionV1":{"type":"object","description":"One section (header/primary/auxiliary) of a Samsung Flexible Layout.","properties":{"preset":{"type":"string","description":"Which of Samsung's preset arrangements for this section is in use.","example":"squareMin"}},"required":["preset"]},"ScanBarcodeV1RequestBody":{"type":"object","description":"Resolve a scanned barcode into its pass and owner. The authenticated variant, for your own staff apps and tills.\n\nPrefer this wherever you control the scanner, because it is scoped to your business. It reads only: apply the outcome with `POST /api/v1/scans/actions`.","properties":{"barcodeValue":{"type":"string","description":"barcode value from pass","example":"1234567890"}},"required":["barcodeValue"]},"ScanV1ResponseSchema":{"type":"object","properties":{"card":{"type":"object","properties":{"id":{"type":"string","description":"ID of the Pass","example":"7048582966"},"downloadId":{"type":"string","description":"DownloadId used for landing page","example":"V1StGXR8_Z5jdHi6B-myT"},"appleLink":{"type":"string","description":"Url for the apple pass","example":"https://litecard-demo-pkpass.s3-ap-southeast-2.amazonaws.com/coldbrew.pkpass"},"businessId":{"type":"string","description":"ID of the Business","example":"business123"},"userType":{"type":"string","description":"Type of User","example":"employee"},"createdAt":{"type":"string","description":"Date Time card was created","example":"2021-11-05T04:25:42.676Z"},"googlePassId":{"type":"string","description":"Id of google pass","example":"1231244"},"authToken":{"type":"string","description":"Internal Use Only. Apple Device Auth Token","example":"dscxdsf234"},"passType":{"type":"string","description":"Mobile wallet pass type","example":"VISIT"},"googleLink":{"type":"string","description":"Url of google pass","example":"https://pay.google.com..."},"samsungLink":{"type":"string","description":"Url of samsung pass","example":"https://a.swallet.link/atw/656147182764415319#Clip?pdata=sIgHCzIwM9g"},"templateId":{"type":"string","description":"Id for the template used to create the card","example":"test_business"},"dataFields":{"type":"object","properties":{"exampleDataField":{"type":"string","description":"Example Value","example":"example value"},"fullName":{"type":"string","description":"Full name of owner of card","example":"John Doe"}}},"formId":{"type":"string","description":"Id for field input form","example":"00000001"},"updatedAt":{"type":"string","description":"","example":"2021-11-05T04:25:42.676Z"},"pkPassId":{"type":"string","description":"Id of Apple Pass","example":"pass.au.com.litecard.dev.q124234"},"barcodeValue":{"type":"string","description":"what the barcode value is","example":"5434435"},"appleStatus":{"type":"string","description":"Status of card inside of Apple Wallet","example":"ACTIVE"},"googleStatus":{"type":"string","description":"Status of card inside of Google Wallet","example":"INACTIVE"},"samsungStatus":{"type":"string","description":"Status of card inside of Samsung Wallet","example":"ACTIVE"},"cardOwnerId":{"type":"string","description":"Id of the card owner","example":"asb123"},"status":{"type":"string","description":"Activation Status of the card","enum":["ACTIVE","INACTIVE","DELETED","RESERVED","ERROR","AVAILABLE"],"example":"INACTIVE"},"expiry":{"type":"string","description":"Expiry of card, in ISO-8601 format","example":"2021-08-11T03:15:56.860Z"},"barcodeLink":{"type":"string","description":"Link to the barcode image","example":"https://example.com/blah.png"},"cardOwnerCopy":{"type":"object","description":"A copy of the card owner's information","properties":{"id":{"type":"string","description":"Unique identifier for the card owner","example":"JUJCSVGnLihP6xcTRElpJ"},"businessId":{"type":"string","description":"Identifier for the business associated with the card owner","example":"LgAcJKiMP5qJf8mKv9a9S"},"name":{"type":"string","description":"Full name of the card owner","example":"John Doe"},"membershipNumber":{"type":"string","description":"Membership number associated with the card","example":"M123456789"}}},"disableQR":{"type":"boolean","description":"Disable QR code on Welcome Page","example":true},"stripeCustomerId":{"type":"string","description":"stripeCustomerId for subscription passes","example":"str_123"},"disableDownload":{"type":"boolean","description":"Disable download link if pass is already activated. Default: false","example":true},"tier":{"type":"string","description":"Membership tier of the card","example":"SILVER"},"numOfScans":{"type":"integer","description":"Number of times the card has been scanned","example":0,"default":0}},"required":["formId","templateId"]},"cardOwner":{"type":"object","description":"Fields that are returned for each card owner","properties":{"id":{"type":"string","description":"Id of the card","example":"-jJWhjZ1a"},"firstName":{"type":"string","description":"First name of card owner","example":"Ankus Fang"},"lastName":{"type":"string","description":"Last name of card owner","example":"AHVW1qv4I_Teqdu4VjwMA"},"phone":{"type":"string","description":"phoneNumber of the card owner","example":"0409381912"},"email":{"type":"string","description":"Email of the card owner","example":"test@litecard.io"},"userType":{"type":"string","description":"User Type","example":"basic"},"state":{"type":"string","description":"State the user is from ","example":"VIC"}},"required":["id"],"additionalProperties":true},"eligibleScanRules":{"type":"array","description":"Scan rules eligible at the card's current numOfScans","items":{"$ref":"#/components/schemas/TemplateScanRuleV1"}},"eligibleBusinessRules":{"type":"array","description":"Business rules eligible for the card's current state","items":{"$ref":"#/components/schemas/TemplateBusinessRuleV1"}},"nextAvailableScanRules":{"type":"array","description":"Scan rules not yet eligible whose non-scan conditions already pass, with scans remaining","items":{"$ref":"#/components/schemas/NextAvailableScanRuleV1"}},"nextAvailableBusinessRules":{"type":"array","description":"Business rules not yet eligible whose non-scan conditions already pass, with scans remaining","items":{"$ref":"#/components/schemas/NextAvailableBusinessRuleV1"}}},"required":["card","cardOwner"]},"ScheduledJobConfig":{"type":"object","required":["type","providerConfig"],"properties":{"type":{"type":"string","enum":["EXPORT_CUSTOMER_BALANCES"],"description":"Which job this group runs."},"intervalInMinutes":{"type":"integer","minimum":1,"default":10},"startInMinutes":{"type":"integer","minimum":0,"default":1},"enabled":{"type":"boolean","default":true,"description":"False pauses the group. Types not listed are left as they are."},"providerConfig":{"oneOf":[{"$ref":"#/components/schemas/TalonOneConfig"}],"description":"Partner settings. Omit managementApiKey to keep the stored one; supplying it rotates the credential."}}},"ScheduledJobResponse":{"type":"object","properties":{"groupId":{"type":"string"},"status":{"type":"string","enum":["ACTIVE","PAUSED"]},"currentJobId":{"type":"string"},"nextExecutionTime":{"type":"string","format":"date-time"}}},"SearchPassV1RequestBody":{"type":"object","description":"Find passes by who holds them.\n\nSupply an email, a phone number, a card id, or an external id. This is the lookup behind a support tool or a till: staff have a customer detail, not a card id.\n\nFor an unfiltered listing, use `POST /api/v1/pass` instead.","properties":{"email":{"type":"string","description":"Email of the card owner","example":"test@example.com"},"phone":{"type":"string","description":"Phone number of the card owner","example":"+61400000000"},"id":{"type":"string","description":"Unique identifier of the card","example":"0234523543"},"externalId":{"type":"string","description":"Identifier the card carries from an external system (e.g. a CRM or POS customer id). Exact, case-sensitive match across every template in the business.","example":"CUST-10023"}}},"SearchPassesResponse":{"type":"object","properties":{"passes":{"type":"array","description":"Array of passes associated with the card owner","items":{"$ref":"#/components/schemas/Card"}}}},"SettingsV1":{"type":"object","properties":{"paymentRequired":{"type":"boolean","description":"If card creation requires payment","example":false},"enforceUniqueEmail":{"type":"boolean","description":"Only allow one email address per template","example":false},"enforceUniquePhone":{"type":"boolean","description":"Only allow one phone number per template","example":false},"resendForUniqueEmail":{"type":"boolean","description":"Resend card if email is unique","example":false},"resendForUniquePhone":{"type":"boolean","description":"Resend card if phone is unique","example":false},"timezone":{"type":"string","description":"Timezone used for displaying dates and times on the pass. Default is UTC","example":"Australia/Melbourne"},"sourceEmail":{"type":"string","description":"IF not using default from domain, specify what from address to use","example":"tarfish@litecard.io","enum":["tarfish@litecard.io","donotreply@shakewellwallet.com","noreply@lune.litecard.io"]},"emailTemplate":{"type":"string","description":"Email template used when creating a new card","example":"litecardpass"},"emailSubject":{"type":"string","description":"The subject of the Invitation email sent when a pass is generated","example":"Welcome to Litecard"},"formPassword":{"type":"string","description":"Form Password","example":"Litecard2023"},"smsTemplate":{"type":"string","description":"SMS template used when creating a new card","example":"litecardpass"},"smsSenderId":{"type":"string","description":"Sender ID for SMS if it is not the Business Name. NOTE: This SenderId needs to have been verified first","example":"Litecard"},"disableBacklink":{"type":"boolean","description":"Disable backlink trackining. Default is enabled","example":true},"barcodeImage":{"type":"boolean","description":"Provides an image alternative of the barcode","example":true},"tacURL":{"type":"string","description":"Use this if client requires an alternative terms and conditions link","example":"https://example.com"},"hideForm":{"type":"boolean","description":"Hide this Form from the private create card page","example":true},"disableQR":{"type":"boolean","description":"Disable QR code on Download page","example":true},"primaryContact":{"type":"string","description":"Preferred medium to use when sending invitation emails","enum":["EMAIL","SMS","EMAIL_SMS"],"example":"SMS"},"externalProvider":{"type":"string","description":"External Provider","example":"SQUARE","enum":["SQUARE","LITECARD","ZII_POS","CIRCLES","SHOPIFY","YOTPO"]},"disableDownload":{"type":"boolean","description":"Option to disable download page if pass is active. Default: false","example":true},"quickSignUp":{"type":"boolean","description":"Option to enable quick sign up for card creation","example":true},"isDeduplicateDeviceAndIp":{"type":"boolean","description":"Option to remove creating duplicate passes for quick signup forms. (Applicable only quickSignUp is true)","example":true},"externalSystemConfig":{"type":"object","description":"External System Configuration","additionalProperties":{"type":"string"},"example":{"configKey1":"configValue1","configKey2":"configValue2"}},"disableInvitationEmail":{"type":"boolean","description":"Option to disable Invitation Email being sent. This will override the option inside options payload","example":true},"disableInvitationSMS":{"type":"boolean","description":"Option to disable Invitation Phone being sent. This will override the option inside options payload.","example":true},"multiTier":{"type":"boolean","description":"Flag to notify whether this template has multiple tier options","example":true},"multiTierVersion":{"type":"number","description":"Immutable version decided at the creation of a template","example":2},"fastCreate":{"type":"boolean","description":"Flag to use the lazy card creation method. This setting overwrites the value provided during card creation","example":true},"competitionSignup":{"type":"boolean","description":"Flag to notify whether this signup form template is associated with a competition","example":true},"languageTier":{"type":"boolean","description":"Flag to notify whether tier names are standardised language codes. Only effective for multi tiered templates","example":true},"noPI":{"type":"boolean","description":"If true, cards created from this template do not require email or phone; defaults card creation to noPI without per-request flag.","example":true},"historyRecordCount":{"type":"number","description":"Number of notification history records to display on the pass. Defaults to 3.","example":3},"returnDuplicate":{"type":"boolean","description":"If uniqueValue identifier is set, return duplicate card if it exists.","example":true},"applyStylesToDownloadPage":{"type":"boolean","description":"If true, the style of the template will be applied to the download page. Default: false","example":false}}},"ShopifyPostProcessWrites":{"type":"object","description":"Per inbound topic, the customer metafields Litecard writes back once the event's card write succeeds. When the event carried a `from` value, Litecard writes the matching `to`, so the webhook that write triggers no longer matches the subscription filter. Only CUSTOMER_UPDATE is supported. A `to` may not equal any `from` on the same field. Omit on save to leave the stored rules untouched; send an empty object to remove them. Stored on the form as `shopifySettings.postProcessWrites`.","additionalProperties":false,"properties":{"CUSTOMER_UPDATE":{"type":"array","maxItems":25,"items":{"type":"object","additionalProperties":false,"properties":{"field":{"type":"string","description":"`namespace.key` of the customer metafield","example":"custom.lc_format"},"type":{"type":"string","description":"Shopify metafield type, needed to write the value","example":"single_line_text_field"},"writes":{"type":"array","minItems":1,"items":{"type":"object","additionalProperties":false,"properties":{"from":{"type":"string","example":"NEW"},"to":{"type":"string","example":"NEW_LC_SYNCED"}},"required":["from","to"]}}},"required":["field","type","writes"]}}},"example":{"CUSTOMER_UPDATE":[{"field":"custom.lc_format","type":"single_line_text_field","writes":[{"from":"NEW","to":"NEW_LC_SYNCED"},{"from":"REDEEMED","to":"REDEEMED_LC_SYNCED"}]}]}},"ShopifySettings":{"type":"object","description":"Shopify specific settings","properties":{"generateCoupon":{"type":"boolean","description":"Generate Coupon on Card Creation"},"ruleId":{"type":"number","description":"What rule the coupon should be using"},"templateMap":{"type":"object","description":"Map Loyalty Integration webhook values to Template fields","additionalProperties":true}}},"ShopifyUpdateIntegrationConfig":{"type":"object","description":"Shopify outbound sync config for a form: pushes litecard fields onto the matched Shopify customer as metafields, one bulk import per time bucket. Source of truth is the external-calls table; this upserts that row.","additionalProperties":false,"properties":{"providerName":{"type":"string","enum":["SHOPIFY"]},"enabled":{"type":"boolean","description":"Whether the sync is enabled"},"storeName":{"type":"string","description":"The myshopify store, e.g. `acme` or `acme.myshopify.com`"},"customerIdField":{"type":"string","description":"Litecard field holding the Shopify customer id (bulk customerUpdate matches by id)"},"defaultNamespace":{"type":"string","description":"Metafield namespace used when a mapping omits one (default `litecard`)"},"defaultType":{"type":"string","description":"Metafield type used when a mapping omits one (default `single_line_text_field`)"},"events":{"type":"array","items":{"type":"string"},"description":"Card events that trigger the sync (default CARD_CREATE, CARD_DOWNLOAD)"},"downloadSync":{"type":"object","description":"Optional download-status metafield, written on every event from the card's wallet statuses once any wallet has held the pass. STRING writes ACTIVE/INACTIVE, BOOLEAN writes whether any wallet holds the pass. Add CARD_REMOVE to events to capture removals.","additionalProperties":false,"properties":{"customField":{"type":"string","description":"Metafield key to write the download status into"},"dataType":{"type":"string","enum":["STRING","BOOLEAN"]},"namespace":{"type":"string","description":"Overrides defaultNamespace for this metafield"}},"required":["customField","dataType"]},"metafields":{"type":"array","description":"Per-field mapping: which litecard field feeds which customer metafield","items":{"type":"object","additionalProperties":false,"properties":{"source":{"type":"string","description":"Litecard field key"},"namespace":{"type":"string","description":"Overrides defaultNamespace for this field"},"key":{"type":"string","description":"Metafield key on the customer"},"type":{"type":"string","description":"Overrides defaultType for this field"}},"required":["source","key"]}},"inboundFieldMapping":{"type":"object","description":"Which Shopify field fills each template field when a customer event arrives, keyed by template field. A `metafield.<namespace>.<key>` path also adds its namespace to every inbound subscription. Omit to leave the stored mapping untouched; send an empty object to remove it.","additionalProperties":{"type":"string"},"example":{"tier":"metafield.custom.mf1","firstName":"first_name"}},"postProcessWrites":{"$ref":"#/components/schemas/ShopifyPostProcessWrites"},"createOnUpdate":{"type":"boolean","description":"True makes a customers/update event for a customer with no card create one. Omit to leave the stored value untouched. Stored on the form as `shopifySettings.createOnUpdate`."},"writeImmediately":{"type":"boolean","description":"True makes CARD_CREATE write to Shopify straight away, falling back to the batch if that write fails. False sends it with the batch only. Omit to leave the stored value untouched (true when never set). Stored on the external call as `batchConfig.writeImmediately`."},"inboundWebhooks":{"type":"array","description":"Shopify customer topics that call Litecard back, one subscription each, replaced as a whole on save. Omit to leave the stored subscriptions untouched; send an empty array to remove them all. A topic whose stored subscription already matches is left alone; a changed `metafieldNamespaces` or `filter` updates it in place inside Shopify. Removing a filter recreates it.","items":{"$ref":"#/components/schemas/InboundWebhookConfig"}}},"required":["providerName"]},"SocialMedia":{"type":"object","properties":{"platform":{"type":"string","example":"Facebook","description":"Social Media Platform"},"link":{"type":"string","example":"https://twitter.com/example","description":"URL of social media profile"},"handle":{"type":"string","example":"example1","description":"Username of the social media platform"}}},"SquareFormIntegrationRecord":{"allOf":[{"$ref":"#/components/schemas/FormIntegrationRecordBase"},{"type":"object","properties":{"name":{"type":"string","enum":["SQUARE"]},"environment":{"type":"string","enum":["sandbox","production"],"description":"Square environment the access token belongs to. Defaults to production for the demo and enterprise stages, sandbox elsewhere"},"customerMatch":{"type":"string","enum":["emailOrPhone","email","phone"],"description":"Which contact detail identifies an existing Square customer when the pass barcode does not match one. Defaults to emailOrPhone"},"customerGroupId":{"type":"string","description":"Square customer group every synced customer is added to"},"mergeFields":{"type":"object","additionalProperties":{"type":"string"},"description":"Litecard field -> Square customer property overrides on top of the defaults (email, firstName, lastName, phone, barcodeValue as reference_id). A target of `custom:<key>` names one of the merchant's own Square custom fields. An empty string removes a default"},"customAttributeTypes":{"type":"object","additionalProperties":{"type":"string"},"description":"Square custom field key -> its data type (String, Email, PhoneNumber, Number, Boolean, Date, Address), for every `custom:` target in mergeFields. Snapshotted on save so a send coerces the value without calling Square"}},"required":["name"]}]},"SquareUpdateIntegrationConfig":{"type":"object","description":"Square config update. PATCH semantics: an absent field preserves the stored value. `accessToken` is verified against Square and stored server-side; it is never returned","additionalProperties":false,"properties":{"providerName":{"type":"string","enum":["SQUARE"]},"enabled":{"type":"boolean","description":"Whether the integration is enabled"},"accessToken":{"type":"string","minLength":1,"description":"Access token from an application in the merchant's Square Developer Console. Replaces the stored token; absent keeps it"},"environment":{"type":"string","enum":["sandbox","production"],"description":"Square environment the access token belongs to. Defaults to production for the demo and enterprise stages, sandbox elsewhere"},"customerMatch":{"type":"string","enum":["emailOrPhone","email","phone"],"description":"Which contact detail identifies an existing Square customer when the pass barcode does not match one. Defaults to emailOrPhone"},"customerGroupId":{"type":"string","description":"Square customer group every synced customer is added to. An empty string clears it"},"mergeFieldMapping":{"type":"object","additionalProperties":{"type":"string"},"description":"Litecard field -> Square customer property (reference_id, given_name, family_name, email_address, phone_number, company_name, birthday, address), or `custom:<key>` for one of the merchant's own Square custom fields, on top of the defaults. An empty string removes a default. Replaced as a whole"}},"required":["providerName"]},"StripeSettings":{"type":"object","properties":{"type":{"type":"string","description":"The Type of Stripe Flow","enum":["LINKED","STANDARD"]},"priceId":{"type":"string","description":"Stripe ID of the product the price is associated with, setting this value will require stripe checkout on card creation","example":"price_1MfJi0CHalkCPRwLFiBsMDXW"},"accountId":{"type":"string","description":"If user has a stripe connect account. Provide account Id here."},"paymentFee":{"type":"string","description":"Fee Litecard will take off from the transaction.","example":"123"},"paymentPerecent":{"type":"string","description":"Fee Litecard for subscription transactions","example":"123"},"paymentType":{"type":"string","description":"Type of payment for the stripe checkout. Default: payment","enum":["payment","subscription"],"example":"payment"},"trial":{"type":"object","description":"Trial settings for stripe subscriptions","properties":{"enabled":{"type":"boolean","description":"Enable trial for stripe subscription","example":true},"days":{"type":"number","description":"Number of days for the trial","example":14}},"required":["enabled","days"]}}},"TableColumn":{"type":"object","properties":{"key":{"type":"string","description":"DynamoDB path to get data"},"label":{"type":"string","description":"Column Header"},"mapping":{"type":"string","description":"Map special values to the segments"},"format":{"type":"string","description":"Format the display","enum":["date","datetime","time","number","currency"]},"values":{"type":"array","description":"Values for the segment","items":{"$ref":"#/components/schemas/ColumnValues"}},"usage":{"type":"array","description":"Visibility of the column in csv export and api response","items":{"type":"string","description":"Usage of the column","enum":["CSV","UI"]}},"groups":{"type":"object","additionalProperties":{"type":"object","properties":{"defaultValue":{"type":"string","description":"The default value for the group"},"label":{"type":"string","description":"The label for the group"},"mappings":{"type":"object","additionalProperties":{"type":"string"},"description":"Key-value pairs mapping strings to strings"}}},"description":"Group specific properties, each key represents a unique group name"}}},"TableConfig":{"type":"object","properties":{"cardsTable":{"type":"object","properties":{"columns":{"type":"array","description":"Template Links","items":{"$ref":"#/components/schemas/TableColumn"}}}}}},"TalonOneConfig":{"type":"object","required":["baseUrl","loyaltyProgramId","managementApiKey"],"properties":{"baseUrl":{"type":"string","description":"Talon.One origin this job calls. It reaches the MANAGEMENT API, which may live on a different host from the Integration API the form integration's own baseUrl points at — the two fields share a name, not a meaning. `managementApiKey` beside it is the tell.","example":"https://mycompany.talon.one"},"loyaltyProgramId":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Loyalty program whose balances are exported."},"pointsLedger":{"type":"string","description":"Ledger the balance is read from. Omit or send Main for the program's main ledger. Points and tier are named separately because a tier is per ledger, not per profile: a profile usually carries a different tier on a subledger than on main."},"tierLedger":{"type":"string","description":"Ledger the tier is read from. Omit or send Main for the program's main ledger. Points and tier are named separately because a tier is per ledger, not per profile: a profile usually carries a different tier on a subledger than on main."},"managementApiKey":{"type":"string","writeOnly":true,"description":"Talon.One MANAGEMENT API key (the ManagementKey-v1 scheme), not an Integration API key. Stored as an SSM SecureString and never returned, logged, or placed in the schedule payload."}}},"TalonOneFormIntegrationRecord":{"allOf":[{"$ref":"#/components/schemas/FormIntegrationRecordBase"},{"type":"object","properties":{"name":{"type":"string","enum":["TALONONE"]},"mergeFields":{"type":"object","additionalProperties":{"type":"string"},"description":"Merge field mappings (local field -> provider field)"},"providerConfig":{"type":"object","description":"Which balance a live lookup reads. Required for the inbound listener to enrich an event with points and tier; a scheduled job carries its own copy.","properties":{"dependencyCallEnabled":{"type":"boolean","description":"Opt in to reading a live balance while handling an inbound event. Off unless set: an integration configured before this existed has no Integration API key stored and must not be called."},"baseUrl":{"type":"string","description":"Talon.One Integration API origin, e.g. https://acme.talon.one."},"loyaltyProgramId":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Loyalty program the profile's balance is read from."},"pointsLedger":{"type":"string","description":"Ledger the balance is read from. Omit or send Main for the program's main ledger. Points and tier are named separately because a tier is per ledger, not per profile: a profile usually carries a different tier on a subledger than on main."},"tierLedger":{"type":"string","description":"Ledger the tier is read from. Omit or send Main for the program's main ledger. Points and tier are named separately because a tier is per ledger, not per profile: a profile usually carries a different tier on a subledger than on main."}}}},"required":["name"]}]},"TalonOneUpdateIntegrationConfig":{"type":"object","required":["providerName"],"properties":{"providerName":{"type":"string","enum":["TALONONE"]},"providerConfig":{"type":"object","description":"Which balance a live lookup reads. Required for the inbound listener to enrich an event with points and tier; a scheduled job carries its own copy.","properties":{"dependencyCallEnabled":{"type":"boolean","description":"Opt in to reading a live balance while handling an inbound event. Off unless set: an integration configured before this existed has no Integration API key stored and must not be called."},"baseUrl":{"type":"string","description":"Talon.One Integration API origin, e.g. https://acme.talon.one."},"loyaltyProgramId":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"Loyalty program the profile's balance is read from."},"pointsLedger":{"type":"string","description":"Ledger the balance is read from. Omit or send Main for the program's main ledger. Points and tier are named separately because a tier is per ledger, not per profile: a profile usually carries a different tier on a subledger than on main."},"tierLedger":{"type":"string","description":"Ledger the tier is read from. Omit or send Main for the program's main ledger. Points and tier are named separately because a tier is per ledger, not per profile: a profile usually carries a different tier on a subledger than on main."}}},"mergeFieldMapping":{"type":"object","additionalProperties":{"type":"string"},"description":"Mapping of merge fields. `points` names the template field a balance sync writes into."},"enabled":{"type":"boolean"},"webhooks":{"type":"array","description":"Outbound webhooks, replaced as a whole on save. Omit to leave the stored ones untouched.","items":{"$ref":"#/components/schemas/IntegrationWebhookConfig"}},"scheduledJobs":{"type":"array","description":"Scheduled jobs to create, update or pause. Only the types listed are touched; omit the key to change none.","items":{"$ref":"#/components/schemas/ScheduledJobConfig"}}}},"TemplateActionConditionV1":{"type":"object","properties":{"type":{"type":"string","description":"Type of condition to be run","example":"CHECK_IN"},"optional":{"type":"boolean","description":"Condition to run action on every invocation to the endpoint","example":true},"order":{"type":"integer","description":"Order of actions to be run on each invocation","example":1},"field":{"type":"string","description":"Field name used for conditional checking","example":"points"},"value":{"type":"integer","description":"Value to be compared against during the conditional check","example":10}},"required":["type","optional"]},"TemplateActionParamsV1":{"type":"object","properties":{"type":{"type":"string","description":"Type of action to be executed","example":"INCREMENT"},"amount":{"type":"integer","description":"Value used in conjunction with the action","example":10},"field":{"type":"string","description":"Field name to perform action on","example":"points"},"incrementAmount":{"type":"integer","description":"Used with the INCREMENT_DECREMENT action, amount to be incremented by","example":1},"incrementField":{"type":"string","description":"Used with the INCREMENT_DECREMENT action, field to be incremented","example":"points"},"decrementAmount":{"type":"integer","description":"Used with the INCREMENT_DECREMENT action, amount to be decremented by","example":10},"decrementField":{"type":"string","description":"Used with the INCREMENT_DECREMENT action, field to be decremented","example":"points"},"postCalc":{"type":"object","description":"Settings to be applied to the amount for post calculation","properties":{"modifier":{"type":"string","description":"Type of calculation to be applied to the amount","enum":["PERCENTAGE","DISCOUNT"],"example":"PERCENTAGE"},"amount":{"type":"number","description":"Amount to be applied. Should be the whole number (e.g. 10 for 10 percent)","example":10}},"required":["modifier","amount"]}},"required":["type"]},"TemplateActionsRequestBody":{"type":"object","description":"Apply the template's scan rules to one pass.\n\nScan rules are the automatic outcome of a presentation: increment a stamp, add points, mark a ticket checked in. Litecard applies the rules configured on the template and pushes the updated pass to the device, so the owner's balance changes while they are still at the counter.\n\n`scanFrequency` advances more than one step at once, for a purchase that earns several stamps.","properties":{"cardId":{"type":"string","description":"Id for the Business that this entity belongs to","example":"LiteCard"},"scanFrequency":{"type":"integer","minimum":1,"default":1,"description":"Number of scan steps for this action. Scan rules are evaluated and applied once per step (from current numOfScans + 1 through + scanFrequency). Defaults to 1."}},"required":["cardId"]},"TemplateActionsResponseSchema":{"type":"object","properties":{"success":{"type":"boolean","description":"Action successful","example":true}}},"TemplateActionsV1":{"type":"object","deprecated":true,"description":"Deprecated. Use TemplateScanRuleV1 and TemplateBusinessRuleV1 instead.","additionalProperties":{"type":"object","properties":{"condition":{"$ref":"#/components/schemas/TemplateActionConditionV1"},"action":{"$ref":"#/components/schemas/TemplateActionParamsV1"},"label":{"type":"string","description":"Name of the Action","example":"Redeem Burger"}},"required":["condition","action"]}},"TemplateAppLinking":{"type":"object","description":"Template Settings to Allow AppLinking on the pass.","properties":{"googleSettings":{"$ref":"#/components/schemas/GoogleAppLinkingSettings"},"appleSettings":{"$ref":"#/components/schemas/AppleAppLinkingSettings"},"samsungSettings":{"$ref":"#/components/schemas/SamsungAppLinkingSettings"}}},"TemplateAppleWalletSettingsV1":{"type":"object","description":"Settings specific to apple wallet","properties":{"passType":{"type":"string","description":"Apple wallet card type","example":"STORE_CARD","enum":["STORE_CARD","BOARDING_PASS","COUPON","EVENT_TICKET","GENERIC"]},"hideLogo":{"type":"boolean","description":"Hides the logo in the top right of apple wallet passes","example":false},"hideLogoText":{"type":"boolean","description":"Hides logo text in the top right of apple wallet passes","example":false},"disableSharing":{"type":"boolean","description":"Disables the share button on the apple pass. This does not stop the user from taking a screenshot and sharing the pass details."},"transitType":{"type":"string","enum":["PKTransitTypeAir"]}},"required":["passType"]},"TemplateBarcodeV1":{"type":"object","description":"Barcode Fields of the template","properties":{"barcodeValue":{"type":"string","description":"Value of the Barcode","example":"4805723345"},"messageEncoding":{"type":"string","description":"Message Encoding of the Barcode","example":"iso-8859-1","nullable":true},"type":{"type":"string","description":"Type of the Barcode","example":"CODE_128","enum":["NOT_USED","QR_CODE","CODE_128","PDF_417","AZTEC"]},"altText":{"type":"string","description":"Displayed alternative value at the bottom of the barcode","example":"1231244","nullable":true},"disabledAltText":{"type":"boolean","description":"Disable the alternative text below the barcode","example":false,"nullable":true},"fieldMap":{"type":"string","description":"If barcodeValue is set to ${CUSTOM}. fieldMap must be present to identify which field the barcodeValue should be set from.","example":"memberId","nullable":true},"prefix":{"type":"string","description":"For Readers / scanners that require a prefix before a barcodeValue","example":"?","nullable":true},"suffix":{"type":"string","description":"For Readers / scanners that require a suffix after a barcodeValue","example":";","nullable":true}}},"TemplateBaseRuleV1":{"type":"object","description":"Shared rule shape for template scan and business rules: conditions gate when the action runs","required":["ruleId","conditions","action"],"properties":{"ruleId":{"type":"integer","description":"Unique identifier for this rule within the template","example":1784703983474},"ruleName":{"type":"string","description":"Optional human-readable name for the rule, e.g: Deactivate membership after 100 visits or Earn points for every visits","example":"Earn points"},"conditions":{"type":"array","description":"All conditions must pass for the rule action to run. Include $.numOfScans to gate by scan count","minItems":1,"items":{"$ref":"#/components/schemas/TemplateRuleConditionV1"}},"action":{"$ref":"#/components/schemas/TemplateRuleActionV1"}}},"TemplateBusinessRuleActionsRequestBody":{"type":"object","description":"Apply one staff-approved business rule to a pass, such as handing over an earned reward.\n\nThe distinction from scan rules matters: scan rules fire on every presentation, business rules fire only when a person decides they should. Counting the stamp is a scan rule; giving away the free coffee is a business rule.\n\nThe rule must be eligible for this pass at its current scan count and field values, so a reward cannot be redeemed twice.","properties":{"cardId":{"type":"string","description":"Pass ID to apply the business rule to","example":"LiteCard"},"businessRuleId":{"type":"integer","description":"Business rule id to execute (e.g. cashier-approved reward). Must match an eligible business rule for this card at its current numOfScans and field values.","example":1784703983474}},"required":["cardId","businessRuleId"]},"TemplateBusinessRulePostActionV1":{"oneOf":[{"type":"object","required":["type","payload"],"properties":{"type":{"type":"string","enum":["PUSH_NOTIFICATION"]},"payload":{"$ref":"#/components/schemas/TemplatePostActionPushNotificationPayloadV1"}},"additionalProperties":false}]},"TemplateBusinessRuleV1":{"description":"Template business rule","allOf":[{"$ref":"#/components/schemas/TemplateBaseRuleV1"},{"type":"object","required":["ruleName","type"],"properties":{"ruleName":{"type":"string","description":"Human-readable label shown in scan apps when offering this rule","example":"Redeem reward"},"type":{"type":"string","description":"Business rule category. REWARD covers point or balance redemption actions","enum":["REWARD"]},"postActions":{"type":"array","description":"Post actions to run after this business rule is applied (e.g. push notification)","items":{"$ref":"#/components/schemas/TemplateBusinessRulePostActionV1"}}}}]},"TemplateCardExpiryV1":{"type":"object","description":"Card Expiry field on Template that details how cards created by the template will expire","oneOf":[{"type":"object","properties":{"expiryType":{"type":"string","description":"Type of expiry, either NEVER, FIXED_DATE, FROM_ACTIVATION or FIXED_SCANS","example":"NEVER","enum":["NEVER"]}}},{"type":"object","properties":{"expiryType":{"type":"string","description":"Type of expiry, either NEVER, FIXED_DATE, FROM_ACTIVATION or FIXED_SCANS","example":"FIXED_DATE","enum":["FIXED_DATE"]},"startDate":{"type":"string","description":"ISO 8601 date time that the card becomes active in the users digital wallet. For Apple cards, the card is still active however it will show up on the top of the stack and Google cards there is no effect unless they are grouped","example":"2022-11-24T20:40:07Z"},"endDate":{"type":"string","description":"ISO 8601 date time that the card expires in the users digital wallet","example":"2022-11-24T20:40:07Z"}}},{"type":"object","properties":{"expiryType":{"type":"string","description":"Type of expiry, either NEVER, FIXED_DATE, FROM_ACTIVATION or FIXED_SCANS","example":"FROM_ACTIVATION","enum":["FROM_ACTIVATION"]},"fromActivation":{"type":"object","properties":{"amount":{"type":"string","description":"The amount of time (based on the cardExpiry.fromActivation.measurement field) between the activation of the card produced by this template, and the expiry date","example":"10"},"measurement":{"type":"string","description":"The unit of measurement to calculate the amount of time between the activation of the card produced by this template, and the expiry date","example":"YEARS","enum":["DAYS","MONTHS","YEARS"]},"timezone":{"type":"string","description":"By default expiry is set as UTC. This is to set it as a specific timezone","example":"Australia/Melbourne","enum":["Australia/Adelaide","Australia/Brisbane","Australia/Darwin","Australia/Hobart","Australia/Melbourne","Australia/Perth","Australia/Sydney"]}}}}},{"type":"object","properties":{"expiryType":{"type":"string","description":"Type of expiry, either NEVER, FIXED_DATE, FROM_ACTIVATION or FIXED_SCANS","example":"FIXED_SCANS","enum":["FIXED_SCANS"]},"scans":{"type":"number","description":"Number of scans remaining before pass expires","example":1}}}]},"TemplateCertificatesV1":{"type":"object","properties":{"appleSettings":{"type":"object","description":"Apple specific settings","properties":{"certId":{"type":"string","description":"Apple Certificate Litecard Id","example":"st3dfdAsaaf"}}},"googleSettings":{"type":"object","description":"Google specific settings","properties":{"certId":{"type":"string","description":"Google Certificate Litecard Id","example":"s2pkdjkASg3"},"googleIssuerId":{"type":"string","description":"Google Issuer Id","example":"123524234"},"redemptionIssuers":{"type":"array","description":"List of Redemption Issuers","items":{"type":"string"},"example":["33315096237343"]}}},"samsungSettings":{"type":"object","description":"Samsung specific settings. If certId is not set, cards are issued under Litecard's own Samsung partner account (the freetier-samsung certificate), since Samsung allows aggregators to create passes on behalf of other businesses.","properties":{"certId":{"type":"string","description":"Samsung Certificate Litecard Id","example":"sk4jfkASh7"}}},"nfc":{"type":"object","description":"NFC settings","properties":{"isNFCEnabled":{"type":"boolean","description":"Checks if NFC cert is provided for this template"},"payloadType":{"type":"string","description":"Payload type sent over nfc e.g. `LITECARD_ID` means the nfc payload will be the same as the litecard card ID","enum":["BARCODE_VALUE"],"example":"BARCODE_VALUE"}}}}},"TemplateColoursV1":{"type":"object","description":"Colours of the template","properties":{"background":{"type":"string","description":"Background colour of the pass","example":"#ffffff"},"label":{"type":"string","description":"Label colour of the apple pass","example":"#ffffff"},"foreground":{"type":"string","description":"Text colour of the apple pass","example":"#ffffff"},"strip":{"type":"string","description":"Text colour on top of strip image of the apple pass","example":"#ffffff"},"samsungBlink":{"type":"string","description":"Colour of the blinking indicating area in samsung wallet","example":"#00ffaa"},"samsungFont":{"type":"string","description":"Colour of the font on the Samsung card art","enum":["dark","light"],"example":"dark"}}},"TemplateDataFieldV1":{"type":"object","description":"Data field object","properties":{"key":{"type":"string","description":"Unique key","example":"firstName"},"backupKey":{"type":"string","description":"Backup field to display in this location if the field value has not been provided yet"},"defaultCountryCode":{"type":"string","description":"Default country code for phone number field","example":"au"},"dateFormat":{"type":"string","description":"If field is a date data type and it is to be displayed on the pass. Use this key to determine the date format using dayjs syntax","example":"DD/MM/YYYY"},"defaultValue":{"title":"Default Value","description":"Set a default value if you want a static value for this field. Overridden, if dataField[].value is set","example":10,"oneOf":[{"type":"string","nullable":true},{"type":"number","nullable":true}]},"label":{"type":"string","title":"Field Label","description":"The label of the field on the card/form","default":"","example":"Member Since"},"labelWithExpression":{"type":"string","title":"Field Label With Expressions","description":"Label template that can pull in the values of other data fields with {{key}} placeholders, so one pass field can carry text that would otherwise need a second field position. {{label}} and {{value}} are reserved and mean this field's own plain label and value; every other placeholder is another data field's key and resolves to the value that field renders with (a date field uses its own dateFormat). Apple and Google only, and only on a field with userCanEdit true: Google renders a non-editable field from the shared class, which carries no per-card values. All or nothing: if any placeholder in labelWithExpression or valueWithExpression has no value to fill in (unknown key, blank, or the \"-\" placeholder), the field falls back to plain label and value on both. Front-of-pass fields truncate rather than wrap, and the width is split between the fields sharing the row, so keep the rendered text short.","example":"{{label}} (Exp on {{expDate}})"},"valueWithExpression":{"type":"string","title":"Field Value With Expressions","description":"Value template using the same {{key}} placeholders and the same all-or-nothing fallback as labelWithExpression. Set it to compose a value out of several fields; leave it unset to render this field's own value unchanged.","example":"{{value}} - use in store"},"helpText":{"type":"string","description":"Help text to be displayed next to the field label","example":"Date the member joined","nullable":true},"description":{"type":"string","description":"Description of the field","example":"This is the full name"},"formatter":{"type":"string","description":"Formatter to be used for the field","example":"currency","enum":["initials"]},"usage":{"$ref":"#/components/schemas/TemplateUsageV1"},"currencyCode":{"type":"string","description":"ISO 4217 currency code","example":"AUD"},"userCanEdit":{"type":"boolean","title":"User Editable","description":"Value is true if the field appears on the form","example":true},"appleFieldSettings":{"type":"object","properties":{"attributedValue":{"type":"string","title":"Apple Attributed Value","description":"Overrides the apple field value key, can contain HTML a tags","example":"<a href='http://google.com'>Google</a>"},"position":{"type":"integer","title":"Apple Field Position","description":"The position in the pass structure array. e.g. For a secondary field, a position of 0 means it is first in order to be rendered in that section. Positions start from 0.","example":1,"minimum":0},"location":{"type":"string","title":"Apple Field Type","description":"The pass structure locations, each value pertains to a certain region/part of the apple pass","example":"HEADER_FIELD","enum":["PRIMARY_FIELD","HEADER_FIELD","SECONDARY_FIELD","AUXILIARY_FIELD","BACK_FIELD"]},"changeMessage":{"type":"string","title":"Apple Change Message","description":"This is the formatted string, that will be shown as a push notification message. The format string must contain %@, which is replaced with the field's new content. If no change message is set, the user isn't notified when the field changes.","example":"Gate changed to %@"},"dateStyle":{"$ref":"#/components/schemas/AppleDateTimeStyleV1"},"timeStyle":{"$ref":"#/components/schemas/AppleDateTimeStyleV1"},"numberStyle":{"$ref":"#/components/schemas/AppleNumberStyleV1"},"textAlignment":{"type":"string","description":"Optional argument that sets the text alignment of the field text. This value is not allowed on primary or back fields.","example":"PKTextAlignmentRight","enum":["PKTextAlignmentLeft","PKTextAlignmentCenter","PKTextAlignmentRight","PKTextAlignmentNatural"]},"ignoreTimeZone":{"type":"boolean","title":"Apple Ignore Time Zone","description":"Optional. Always display the time and date in the given time zone, not in the user’s current time zone. The default value is false.","example":true},"isRelativeDate":{"type":"boolean","title":"Apple Relative Date","description":"Optional. If true, the label’s value is displayed as a relative date; otherwise, it is displayed as an absolute date. The default value is false.","example":true}},"required":["position","location"]},"googleFieldSettings":{"type":"object","description":"Google wallet settings","properties":{"location":{"type":"string","nullable":true,"title":"Google Field Type","description":"The type of google field","example":"TEXT_MODULE_DATA","enum":["NOT_USED","ACCOUNT_NAME","LOYALTY_POINTS","TEXT_MODULE_DATA","MESSAGES","OFFER_FINEPRINT","OFFER_DETAILS","EVENT_VENUE","EVENT_SECTION","EVENT_SEAT","EVENT_ROW","EVENT_GATE","EVENT_DOORS_OPEN","EVENT_START","EVENT_END","EVENT_TICKET_TYPE","EVENT_TICKET_MONEY","EVENT_CONFIRMATION_CODE","PASSENGER_NAME","SEAT_NUMBER","BOARDING_GROUP","SEAT_CLASS","CONFIRMATION_CODE"]},"position":{"type":"integer","title":"Google Text Module Field Position","description":"The position in the text modules data array. e.g. A position of 0 means it is first in order to be rendered in that section. Positions start from 0.","example":1,"minimum":0},"cardRow":{"type":"boolean","description":"If this field should be displayed on the front of the pass"},"fieldType":{"type":"string","description":"If using MESSAGES location, what type of MESSAGE field it is","enum":["TEXT","TEXT_AND_NOTIFY"]},"expiresAt":{"type":"string","format":"date-time","description":"Time for the Google Wallet notification to expire. Uses UTC time."}},"required":["location"]},"samsungFieldSettings":{"type":"object","description":"Samsung field settings","properties":{"section":{"type":"string","title":"Samsung Flexible Layout Section","description":"Which Flexible Layout section (see samsungWalletSettings.layout) this field fills a slot in, or BACK for a field shown only on the full-view detail screen (Samsung's extendedFields), not on the compact card.","example":"HEADER","enum":["HEADER","PRIMARY","AUXILIARY","BACK"]},"entryKey":{"type":"string","title":"Samsung Flexible Layout Entry Key","description":"Which slot (Samsung entryName) within the section's chosen preset this field fills, e.g. 'field_01'. See samsungWalletSettings.layout[section].preset for the preset that defines valid entryNames. Not used when section is BACK.","example":"field_01"}}},"formFieldSettings":{"type":"object","description":"Form field settings (Used for Litecard's Web UI based card creation workflow)","properties":{"position":{"type":"number","description":"Order of the form field, all form fields will be ordered in ascending order (e.g. 1,2,3)","example":0},"placeHolderText":{"type":"string","description":"Place owner text","example":"Full Name"},"dataType":{"type":"string","description":"Form field data type","enum":["radio","select","text","textArea","number","phone","date","dateTime","image","nationality","address"]},"rules":{"type":"array","description":"Validation rules that will be run on this field","items":{"type":"string","enum":["required","phone","email","integer"]}},"hideWhen":{"type":"object","description":"Conditionally removes this field from the pass, based on the value of other data fields on the same card. Evaluated each time the pass is built, so a date condition takes effect on the next pass update after it passes. Omitted entirely rather than rendered blank.","properties":{"truthyIn":{"type":"string","description":"Key of another data field. Hide this field when that field's value is truthy (true, 1, yes).","example":"voucherRedeemed"},"emptyIn":{"type":"string","description":"Key of the data field. Hide this field when the specified data field is empty, missing, or has the - placeholder.","example":"voucherCode"},"containsIn":{"type":"object","description":"Hide this field when another data field's value contains a piece of text. Matching ignores case, a list matches when any item does, and an empty field or the - placeholder never matches.","properties":{"key":{"type":"string","description":"Key of the data field to look in.","example":"tier"},"value":{"type":"string","description":"Text that hides this field when found.","example":"GOLD"}},"required":["key","value"]},"afterDateIn":{"type":"string","description":"Key of another data field holding a date. Hide this field once that date has passed. An absent or unparseable date means no expiry.","example":"voucherExpiry"}}},"usage":{"$ref":"#/components/schemas/FormFieldUsage"},"options":{"type":"array","description":"List of options for drop down list","items":{"type":"object","properties":{"value":{"type":"string","description":"Value of the selected option"},"title":{"type":"string","description":"Label that front end displays"},"order":{"type":"number","description":"Numbered by priority, the final order will be in ascending order","example":1,"minimum":0}}}},"allowOauthValidation":{"type":"boolean","description":"Enable validation of field values using social account authentication (OAuth sign-in)","example":true},"externalProviderMapping":{"type":"string","description":"If the field is mapped to an external provider, the key of the external provider","enum":["LOYALTY_MEMBER_ID","LOYALTY_POINTS","CUSTOMER_ID","VOUCHER_CODE","VALID_EMAIL","VALID_EMAIL_MAPPING_CODE","VALID_EMAIL_NO_SIGNUP"]},"externalMappingKey":{"type":"string","description":"If the field is mapped to an external provider, the key of the field in the external provider","example":"memberId"}},"required":["position","placeHolderText","dataType"]},"frontEndMapping":{"type":"array","items":{"type":"string","enum":["SCAN","SCAN_TABLE","MEMBER_TABLE"]},"title":"Front End Mapping","description":"List to decide that decide whether the field should be rendered in the Litecard UI on scan previews or table views","example":["SCAN"]},"uniqueValue":{"type":"boolean","description":"Only used for segment keys - segment1, segment2 etc. Default: false","example":true},"fieldMapping":{"type":"object","description":"General mapping rules to construct this field value from another payload field","properties":{"source":{"type":"string","description":"Source of the value","enum":["INTERNAL"]},"sourceKey":{"type":"string","description":"Key of the source payload field","example":"points"},"name":{"type":"string","description":"Optional target payload key when writing converted value during payload preprocessing","example":"rewardBalance"},"prefix":{"type":"string","description":"Optional value prepended to the converted output","example":"$"},"suffix":{"type":"string","description":"Optional value appended to the converted output","example":"%"},"multiplier":{"type":"number","description":"Multiply numeric source values before formatting","example":0.01},"divider":{"type":"number","description":"Divide numeric source values before formatting","example":100},"formatter":{"type":"string","description":"Optional formatter applied after numeric operations","enum":["AUTO","FIXED_0","FIXED_2","UPPERCASE","LOWERCASE"],"example":"AUTO"},"type":{"type":"string","description":"Type hint for conversion","enum":["STRING","NUMBER"],"example":"NUMBER"},"ranges":{"type":"array","description":"Map a numeric source value to a fixed output string based on inclusive min/max bands. The first matching range wins. A range without a max matches any value >= min. When ranges is present, multiplier/divider/formatter are ignored and prefix/suffix still apply.","items":{"type":"object","properties":{"min":{"type":"number","description":"Inclusive lower bound of the range","example":100},"max":{"type":"number","description":"Inclusive upper bound of the range (omit for open-ended)","example":199},"value":{"type":"string","description":"Output value when the source falls in this range","example":"5"}},"required":["min","value"]}}}},"isSecure":{"type":"boolean","title":"Secure Field","description":"Indicates if this field contains sensitive/secure data","example":false},"historyViewEnabled":{"type":"boolean","title":"History View Enabled","description":"If true, values superseded by an update are retained against this field's key and rendered under the latest value, most recent first. Requires maxHistoryRecords to be 1 or more to retain anything. Each wallet decides separately whether to stack them: history renders only where the field sits on the back of that pass (appleFieldSettings.location BACK_FIELD, googleFieldSettings.location TEXT_MODULE_DATA without cardRow). A field on the front of a pass shows the latest value alone there, since front fields are single line and truncate, so the same field can stack on one wallet and not the other. Default: false","example":true},"maxHistoryRecords":{"type":"integer","title":"Max History Records","description":"How many previous values are retained for this field. The latest value is never part of the history - it stays in the card's dataFields - so the pass renders up to maxHistoryRecords + 1 values in total. Defaults to 0, which keeps no history and shows the latest value only","default":0,"minimum":0,"maximum":10,"example":5}},"required":["key","label","usage","userCanEdit"]},"TemplateGoogleAutoLinkV1":{"type":"object","description":"Adds each new pass on this template to the holder's Google Wallet when they already hold a pass on the primary template. Ref: https://developers.google.com/wallet/retail/offers/use-cases/auto-linked-passes","properties":{"primaryTemplateId":{"type":"string","description":"Template whose pass this one is linked to. Must belong to the same business and Google issuer, and must not have an autoLink of its own.","example":"Kd8_3nQpXw2LmZ7vRt1Yh"},"passIdentifierKey":{"type":"string","description":"Card field that matches a new pass to the holder's pass on the primary template. `cardOwnerId` (default) matches the same card owner; `externalId` matches the card with the same externalId, for passes issued against a partner's own customer id.","enum":["cardOwnerId","externalId"],"example":"cardOwnerId"}},"required":["primaryTemplateId"],"additionalProperties":false},"TemplateGoogleWalletSettingsV1":{"type":"object","description":"Settings specific to google wallet","properties":{"passType":{"type":"string","description":"Google wallet card type","example":"LOYALTY","enum":["EVENT_TICKET","FLIGHT","GIFT_CARD","LOYALTY","OFFER","TRANSIT","GENERIC"]},"offer":{"type":"object","description":"Google offer settings","properties":{"redemptionChannel":{"type":"string","description":"The redemption channel application to the coupon (i.e. where the coupon can be redeemmed on)","enum":["INSTORE","ONLINE","BOTH","TEMPORARY_PRICE_REDUCTION"]},"shortTitle":{"type":"string","description":"Short offer title shown in the Google Wallet list view. Google recommends at most 20 characters.","example":"20% off"},"provider":{"type":"string","description":"The merchant offering the deal. Defaults to the template businessName.","example":"Acme Store"},"details":{"type":"string","description":"Offer details shown on the pass. Class level, so every holder sees the same text. Takes precedence over a data field with location OFFER_DETAILS.","example":"Valid on any full priced t-shirt."},"finePrint":{"type":"string","description":"Offer terms and conditions. Class level, so every holder sees the same text. Takes precedence over a data field with location OFFER_FINEPRINT.","example":"One use per customer. Not valid with any other offer."}}},"eventTicket":{"type":"object","description":"Event Ticket settings for google. Ref: https://developers.google.com/wallet/tickets/events/resources/template","properties":{"confirmationCodeLabel":{"type":"string","description":"The confirmation code of the event reservation. This may also take the form of an order number, confirmation number, reservation number, or other equivalent.","enum":["CONFIRMATION_CODE_LABEL_UNSPECIFIED","CONFIRMATION_CODE","CONFIRMATION_NUMBER","ORDER_NUMBER","RESERVATION_NUMBER"]},"gateLabel":{"type":"string","description":"Gate Label, it could be gate, doors or entrace","enum":["GATE","DOORS","ENTRANCE"]},"sectionLabel":{"type":"string","description":"Section label, it could be section or theater","enum":["SECTION","THEATER"]},"rowLabel":{"type":"string","description":"Row label, it could be row or unspecified","enum":["ROW","ROW_LABEL_UNSPECIFIED"]},"seatLabel":{"type":"string","description":"Seat label, it could be seat or unspecified","enum":["SEAT","SEAT_LABEL_UNSPECIFIED"]}}},"flight":{"type":"object","description":"Flight settings for google","properties":{"carrierIataCode":{"type":"string","description":"Carrier IATA code","example":"QF"},"flightNumber":{"type":"string","description":"Flight number","example":"123456"},"localScheduledDepartureDateTime":{"type":"string","description":"Local scheduled departure date and time","example":"2025-01-01T12:00:00Z"},"origin":{"type":"object","description":"Origin airport settings for google","properties":{"airportIataCode":{"type":"string","description":"Airport IATA code","example":"SYD"},"terminal":{"type":"string","description":"Terminal"},"gate":{"type":"string","description":"Gate","example":"12"}}},"destination":{"type":"object","description":"Origin airport settings for google","properties":{"airportIataCode":{"type":"string","description":"Airport IATA code","example":"SYD"},"terminal":{"type":"string","description":"Terminal"},"gate":{"type":"string","description":"Gate","example":"12"}}}}},"securityShimmer":{"type":"boolean","description":"Enable Google Barcode Shimmer for in-person screenshot protection","example":true},"multipleDevicesAndUsersStatus":{"type":"string","description":"Identifies whether multiple users and devices can save the same pass","enum":["MULTIPLE_HOLDERS","ONE_USER_ALL_DEVICES","ONE_USER_ONE_DEVICE"]},"title":{"type":"string","description":"Text that appears beside your logo on the pass","example":"Litecard"},"header":{"type":"string","description":"Text that appears within your pass before expanding","example":"Your membership Pass"},"subHeader":{"type":"string","description":"The Label for the header section","example":"Welcome!"},"enforceClassFields":{"type":"boolean","description":"If true, the class fields are copied to the object and the object fields are ignored. If false, the class fields are ignored and the object fields are used.","example":false},"defaultMessageExpirationSeconds":{"type":"integer","description":"Default message expiration in seconds","example":86400},"autoLink":{"$ref":"#/components/schemas/TemplateGoogleAutoLinkV1"}},"required":["passType"]},"TemplateImagesV1":{"type":"object","description":"Images of the template","properties":{"logo":{"type":"string","description":"URL of uploaded a logo image (square), used by Apple, Google & Samsung, 300px by 300px.","example":"https://s3bucketlocation/business-name/logo.png"},"logoDarkMode":{"type":"string","description":"URL of uploaded a logo image (square), used by Samsung, 300px by 300px. Used specifically for dark mode.","example":"https://s3bucketlocation/business-name/logo.png"},"logoLightMode":{"type":"string","description":"URL of uploaded a logo image (square), used by Samsung, 300px by 300px. Used specifically for light mode.","example":"https://s3bucketlocation/business-name/logo.png"},"hero":{"type":"string","description":"URL of uploaded hero image, used by Google. 1032px x 336px","example":"https://s3bucketlocation/business-name/hero.png"},"strip":{"type":"string","description":"URL of uploaded strip image, used by Apple. 1125px by 432px. Will overwrite Apple's thumbnail image.","example":"https://s3bucketlocation/business-name/hero.png"},"appleLogoOverride":{"type":"string","description":"URL of uploaded logo image used by Apple. This will replace the shared logo image. 150px height with max width of 480px","example":"https://s3bucketlocation/business-name/hero.png"},"icon":{"type":"string","description":"URL of uploaded icon image, used by Apple","example":"https://s3bucketlocation/business-name/icon.png"},"thumbnail":{"type":"string","description":"URL of uploaded thumbnail image. 270px by 270px. Only available on Apple Event Ticket and Generic card types and certain Samsung Card Types.","example":"https://s3bucketlocation/business-name/thumbnail.png"},"background":{"type":"string","description":"URL of uploaded background image. 270px by 270px. Only available on Apple Event Ticket card types","example":"https://s3bucketlocation/business-name/background.png"}}},"TemplateLinkV1":{"type":"object","description":"Links","properties":{"id":{"type":"string","description":"Link ID"},"uri":{"type":"string","description":"URI"},"hyperlinkText":{"type":"string","description":"Add block of text with hyperlink by surrounding text with ${}, used by Apple Only","example":"Click ${here} to visit the litecard website"},"title":{"type":"string","description":"Link title"},"type":{"type":"string","description":"Type of link, used by Google Only","enum":["NOT_USED","URI_WEB","URI_TEL","URI_EMAIL","HOMEPAGE_URI","HELP_URI"]},"usage":{"$ref":"#/components/schemas/TemplateUsageV1"},"position":{"type":"number","description":"Order of the links. The order will be sorted in ascending order e.g. 1,2,3"},"samsungLinkSettings":{"type":"object","description":"Samsung-specific link settings. `usage` including SAMSUNG_WALLET only makes a link eligible for Samsung - `included` is the explicit choice, made in the Samsung Pass Designer's own Links section, of whether it actually appears on the Samsung card.","properties":{"included":{"type":"boolean","description":"Whether this link is included in the Samsung card's Customer Service Info (or Coupon's Notices contact block)."},"csInfoKey":{"type":"string","enum":["website","instagram","pinterest","x","facebook"],"description":"Optional Samsung Loyalty csInfo key for website links. Defaults to website."}}}}},"TemplateLocationsV1":{"type":"array","description":"Places that make the pass surface on the owner's lock screen when they are nearby. Apple stores at most 10 per pass. On templateOverrides these are merged with the template's list rather than replacing it, matched by id, then externalId, then order; your entries take the 10 slots first and leftover template locations fill the rest. An entry of { externalId } alone is enough: Litecard resolves the coordinates from your registered locations.","items":{"type":"object","description":"Location","properties":{"id":{"type":"string","description":"Location ID"},"externalId":{"type":"string","description":"External system identifier for the location","example":"ext-location-456"},"lat":{"type":"string","description":"Latitude value","example":"-37.8140000"},"lon":{"type":"string","description":"Longitude value","example":"144.9633200"},"apple":{"type":"object","description":"Apple specific location fields","properties":{"lockScreenMessage":{"type":"string","description":"Lock screen message displayed when a pass within proximity of the location","example":"Buy 1 get 1 free today only"}}},"beaconId":{"type":"string","description":"proximityUUID of the Beacon. This is an alternative to long/lat","example":"F8F589E9-C07E-58B0-AEAB-A36BE4D48FAC"},"samsung":{"type":"object","description":"Samsung specific location fields","properties":{"address":{"type":"string","description":"Address of the location","example":"123 Broad Street"},"name":{"type":"string","description":"Name of the location","example":"Joe's Pizza"},"geofenceType":{"type":"string","enum":["100","200","300","400","500"],"description":"Geofence radius in metres that triggers the notification","example":"100"},"message":{"type":"string","description":"Notification message shown when a card enters the geofence","example":"Welcome to Joe's Pizza! Check your rewards."}}},"order":{"type":"number","description":"Order of the locations. The order will be sorted in ascending order e.g. 1,2,3"},"usage":{"$ref":"#/components/schemas/TemplateUsageV1"}}}},"TemplateOverridesV1":{"type":"object","description":"Per-pass design changes, applied on top of the template at issue time.\n\n**The problem it solves:** a template is shared by everyone holding that pass type, but some things are true of one customer only. Overrides let you keep **one** template and vary just those parts.\n\nThe four that matter most:\n\n- **`locations`, because a pass can only carry 10.** Apple caps geo-location messages at 10 locations per pass, so a chain with 200 stores cannot put them all on the template. Send the ten each customer actually visits.\n- **`links`, because the useful link is per customer.** Their rewards page, their booking, their referral code. A template can only carry a link true for everybody.\n- **`images`, because generic artwork is forgettable.** Override `hero` or `strip` with a rendering carrying the customer's name, tier or balance.\n- **`cardExpiry`, because expiry dates rarely line up.** Override it when the date belongs to the customer: a membership anniversary, a trial that began at signup, a voucher valid 30 days from issue.\n\n**You rarely need to override `barcode`.** A template's barcode can be mapped to a form field in the dashboard (Scan › Override QR / Barcode?), after which each pass's barcode is built from that field and follows it on update. The case that does justify an override is a different barcode **type** per pass, for example `QR_CODE` for most customers and `CODE_128` for older scanners, since type cannot come from a form field.\n\n**If a tier solves your problem, use a tier instead.** Overrides are per-pass state you have to get right at issue time and remember when reissuing. A tier is designed into the template once, named, and reused. Reach for an override when the value is genuinely unique to one customer, not when you have a handful of known variants.\n\n**How each field merges:**\n\n| Field | Behaviour |\n| --- | --- |\n| `images`, `colours`, `cardExpiry`, `businessName`, `googleWalletSettings` | Deep merge. The override wins field by field; anything you omit keeps the template's value. |\n| `links` | Merged by `position`. When every link on both sides has a `position`, the override replaces the template link at that position and the result is sorted ascending. Otherwise the override links are appended after the template's. |\n| `locations` | Merged with the template's, then capped at **10**. Slots go to overrides matching a template location, then your remaining overrides, then leftover template locations, so your ten always win. `{ \"externalId\": \"store-123\" }` on its own is enough: Litecard resolves the coordinates from your registered locations. |\n| `barcode` | Deep merged, then the barcode value is recomputed for this pass. |\n\nOverrides are stored on the pass. Editing the template later still updates every pass, but a field this pass has overridden stays overridden.\n\n**Rule of thumb, in order:** true for everyone holding this pass type, put it on the template. True for a known, reusable group, make it a tier. True for one customer only, put it here.","properties":{"images":{"$ref":"#/components/schemas/TemplateImagesV1"},"barcode":{"$ref":"#/components/schemas/TemplateBarcodeV1"},"locations":{"$ref":"#/components/schemas/TemplateLocationsV1"},"cardExpiry":{"$ref":"#/components/schemas/TemplateCardExpiryV1"},"links":{"type":"array","description":"Template Links","items":{"$ref":"#/components/schemas/TemplateLinkV1"}},"colours":{"$ref":"#/components/schemas/TemplateColoursV1"},"businessName":{"type":"string","description":"Business name to be displayed on the pass","example":"Joe's Pizza"},"googleWalletSettings":{"type":"object","description":"Google wallet settings","properties":{"title":{"type":"string","description":"Text that appears beside your logo on the pass","example":"Litecard"},"header":{"type":"string","description":"Text that appears within your pass before expanding","example":"Your membership Pass"}}}},"additionalProperties":false},"TemplatePostActionPushNotificationPayloadV1":{"type":"object","required":["title","message"],"properties":{"title":{"type":"string","description":"Push notification title","example":"Reward redeemed"},"message":{"type":"string","description":"Push notification body","example":"Thanks for your loyalty!"}},"additionalProperties":false},"TemplateRuleActionV1":{"type":"object","description":"Card mutation applied when every template rule condition is met","required":["operation","field","value"],"properties":{"operation":{"type":"string","description":"How to update the target field: SET replaces the value, INCREMENT and DECREMENT adjust numeric fields","enum":["SET","INCREMENT","DECREMENT"]},"field":{"type":"string","description":"JSONPath to the card field to update (e.g. $.tier, $.status, $.dataFields.pointsKey)","example":"$.dataFields.pointsKey"},"value":{"description":"Value for SET, or delta for INCREMENT/DECREMENT on numeric fields","anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"}]}},"additionalProperties":false},"TemplateRuleConditionV1":{"type":"object","description":"Template rule condition","required":["field","operator","value"],"properties":{"field":{"type":"string","description":"JSONPath to the card field under test (e.g. $.numOfScans, $.status, $.tier, $.dataFields.pointsKey)","example":"$.numOfScans"},"operator":{"type":"string","description":"Comparison operator applied to the field value. GT: Greater than, GTE: Greater than or equal to, EQ: Equal to","enum":["GT","GTE","EQ"]},"value":{"description":"Rule will be applied if the field value matches the condition value","anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"}]}},"additionalProperties":false},"TemplateSamsungWalletSettingsV1":{"type":"object","description":"Settings specific to samsung wallet","properties":{"passType":{"type":"string","description":"Samsung wallet card type. Only types implemented in samsung-web-service's card mapper are accepted - BOARDING_PASS, GIFT_CARD, and ID are defined in Samsung's API but not yet supported here.","example":"EVENT_TICKET","enum":["EVENT_TICKET","LOYALTY","GENERIC","COUPON"]},"walletCardId":{"type":"string","description":"Samsung card template ID (cardId). Server-generated on first save via the Samsung card template API - not merchant-provided.","example":"3ftjqoit67i00"},"notificationTemplateId":{"type":"string","description":"Samsung notification template ID, registered alongside the card template. Server-generated, not merchant-provided.","example":"4155295937781138496"},"status":{"type":"string","description":"Samsung card template publish status. NOT_CREATED/absent until first save, VERIFYING while the background create job runs, READY_TO_LAUNCH once the card template + notification template exist on Samsung but haven't been launched, ACTIVE once explicitly launched, FAILED if the background create step errors out. Server-generated, not merchant-provided.","example":"VERIFYING","enum":["VERIFYING","READY_TO_LAUNCH","ACTIVE","FAILED"]},"partnerId":{"type":"string","description":"Samsung Partner ID. Resolved from the business's Samsung certificate, not merchant-provided.","example":"4847413840325458240"},"appLinkLogo":{"type":"string","description":"Logo of the relevant app. The max image size should not exceed 256kB","example":"https://image"},"appLinkName":{"type":"string","description":"Name of the relevant app","example":"Joe's Pizza"},"appLinkData":{"type":"string","description":"Link to the application or website","example":"https://joespizza.com"},"merchantId":{"type":"string","description":"Ignored - server-derived from the template's businessId, not merchant-provided.","example":"merchant-001"},"merchantName":{"type":"string","description":"Ignored - server-derived from the template's businessName, not merchant-provided.","example":"Joe's Pizza"},"bgImage":{"type":"string","description":"Background art image URL for loyalty cards. The recommended size for image resources is 888 x 555 px","example":"https://cdn.example.com/bg.png"},"preventCaptureYn":{"type":"string","description":"Prevent screen capture of the card ('Y' or 'N')","enum":["Y","N"],"default":"N","example":"N"},"disableSharing":{"type":"boolean","description":"Disables the share button on the Samsung card template (Samsung's shareButtonExposureYN, inverted). Set independently of appleWalletSettings.disableSharing - Samsung's sharing control is not tied to Apple's."},"noticeDescription":{"type":"string","description":"Free text (HTML supported) shown in the card's notice/description area. Known as 'noticeDesc' on Samsung's card.","example":"Terms and conditions apply."},"subtitle":{"type":"string","description":"Subtitle text shown near the card title. Known as 'subtitle1' on Samsung's card.","example":"Gold Member"},"customerServiceInfo":{"type":"object","description":"Customer service contact details. Known as 'csInfo' on samsung fields","properties":{"call":{"type":"string","description":"Phone number","example":"+61 (03) 9999 9000"},"email":{"type":"string","description":"Email address","example":"restaurant-name@gmail.com"},"website":{"type":"string","description":"Website url","example":"https://www.restaurant-name.com/"}}},"layout":{"type":"object","description":"Samsung Flexible Layout configuration. Optional - when unset the card uses Samsung's Default/Fixed layout instead. Each section just names which preset arrangement is in use; which data field fills which slot in that preset lives on the field itself, via TemplateDataFieldV1.samsungFieldSettings.section/entryKey.","properties":{"header":{"$ref":"#/components/schemas/SamsungLayoutSectionV1"},"primary":{"$ref":"#/components/schemas/SamsungLayoutSectionV1"},"auxiliary":{"$ref":"#/components/schemas/SamsungLayoutSectionV1"}}}},"required":["passType"]},"TemplateScanRuleV1":{"description":"Rule evaluated automatically on each scan action. Eligible rules run when the card's current numOfScans and other conditions match","allOf":[{"$ref":"#/components/schemas/TemplateBaseRuleV1"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","description":"Scan rule category determining which card surface the action targets","enum":["TIER_UPDATE","STATUS_UPDATE","TOGGLE_BARCODE_VIEW","DATA_UPDATE","EXPIRY_UPDATE"]}}}]},"TemplateUsageV1":{"type":"array","description":"List of strings to indicate where a field is rendered","items":{"type":"string","enum":["NOT_USED","APPLE_WALLET","GOOGLE_WALLET","SAMSUNG_WALLET"]}},"TemplateV1":{"type":"object","allOf":[{"type":"object","properties":{"id":{"type":"string","description":"Template ID","example":"V1StGXR8_Z5jdHi6B-myT"},"formId":{"type":"string","description":"Id for field input form","example":"V1StGXR8_Z5jdHi6B-myT"},"version":{"type":"number","description":"Schema Version used by the template","default":1},"businessId":{"type":"string","description":"Identifies the owner of the record","example":"R9xh12dga8i"},"status":{"type":"string","description":"Template Status","enum":["ACTIVE","INACTIVE","DELETED"]},"cardType":{"type":"string","description":"Card type"},"uiConfig":{"$ref":"#/components/schemas/UIConfig"}}},{"$ref":"#/components/schemas/BaseTemplateV1"},{"$ref":"#/components/schemas/DatabaseMetaData"}],"required":["id","formId","status","businessId","version","name","description","businessName","appleWalletSettings","googleWalletSettings","colours","images","barcode","createdAt","updatedAt"]},"TierTemplateSettings":{"type":"object","description":"The unique settings for each membership tier","properties":{"default":{"type":"boolean","description":"default tier when tier isn't provided during card creation. There must be only 1"},"googleClassId":{"type":"string","description":"Google Class ID for the tier","example":"V1StGXR8_Z5jdHi6B-myT"},"cardExpiry":{"$ref":"#/components/schemas/TemplateCardExpiryV1"},"colours":{"$ref":"#/components/schemas/TemplateColoursV1"},"images":{"$ref":"#/components/schemas/TemplateImagesV1"},"barcode":{"$ref":"#/components/schemas/TemplateBarcodeV1"},"locations":{"$ref":"#/components/schemas/TemplateLocationsV1"},"links":{"type":"array","description":"Template Links","items":{"$ref":"#/components/schemas/TemplateLinkV1"}},"dataFields":{"type":"array","description":"Data Fields of the template","items":{"$ref":"#/components/schemas/TemplateDataFieldV1"}},"googleWalletSettings":{"type":"object","description":"Google Wallet Settings for the tier","properties":{"title":{"type":"string","description":"Text that appears beside your logo on the pass","example":"Litecard"},"header":{"type":"string","description":"Text that appears within your pass before expanding","example":"Your membership Pass"},"subHeader":{"type":"string","description":"The Label for the header section","example":"Welcome!"}}},"samsungWalletSettings":{"$ref":"#/components/schemas/TemplateSamsungWalletSettingsV1"}},"required":["default"]},"UIConfigResponse":{"type":"object","description":"Business UI Config for welcome page","properties":{"uiConfig":{"$ref":"#/components/schemas/UIConfig"}},"required":["uiConfig"]},"UpdateIntegrationConfig":{"description":"Create-or-update body for a form integration, selected by providerName","oneOf":[{"$ref":"#/components/schemas/KlaviyoUpdateIntegrationConfig"},{"$ref":"#/components/schemas/SquareUpdateIntegrationConfig"},{"$ref":"#/components/schemas/TalonOneUpdateIntegrationConfig"},{"$ref":"#/components/schemas/ShopifyUpdateIntegrationConfig"},{"$ref":"#/components/schemas/BloomreachUpdateIntegrationConfig"},{"$ref":"#/components/schemas/GenericUpdateIntegrationConfig"}]},"UpdateLocationRequestBody":{"type":"object","properties":{"lat":{"type":"string","description":"Latitude coordinate of location","example":"-37.80690913039505"},"lon":{"type":"string","description":"Longitude coordinate of location","example":"144.986263128836"},"locationName":{"type":"string","description":"Name of location","example":"Litecard HQ"},"address":{"type":"string","description":"Street address of the location. Shown in Samsung Wallet's Geofence Notification location details.","example":"123 Broad Street"},"notificationSettings":{"$ref":"#/components/schemas/LocationNotificationSettings"}},"required":["locationName","lon","lat"],"additionalProperties":false},"UpdateTemplateNotification":{"description":"Request body for updating a template notification setting","allOf":[{"$ref":"#/components/schemas/BaseNotificationSettings"},{"type":"object","required":["trigger","type","title","message","delay","unit"]}]},"UpdateTemplateV1":{"type":"object","description":"Edit a template.\n\nThis repaints **every pass already issued from it**, on owners' phones, at the next refresh. That is the point, and the risk: a typo fixed here corrects thousands of wallets, and an accidental change reaches just as far.\n\nFields a pass has overridden through `templateOverrides` keep their overridden value.","allOf":[{"type":"object","properties":{"id":{"type":"string","description":"Template ID","example":"V1StGXR8_Z5jdHi6B-myT"},"status":{"type":"string","description":"Template Status","enum":["ACTIVE","INACTIVE","DELETED"]},"updateExistingCards":{"type":"boolean","description":"Push this edit to passes already in wallets. Defaults to false, which saves the template but leaves live passes alone. When true, every active pass is queued for an over-the-air update and the response carries a groupId for tracking it."}}},{"$ref":"#/components/schemas/BaseTemplateV1"}],"required":["id","name","description","businessName","businessId","appleWalletSettings","googleWalletSettings","colours","images","barcode"]},"UpdateUserGroupRequestBody":{"type":"object","properties":{"name":{"type":"string","description":"Updated name of the user group"},"roles":{"type":"array","items":{"type":"string"},"description":"Updated roles for the group","example":["Employee"]}},"additionalProperties":false},"UpdateUserRequestBody":{"type":"object","required":["userGroupId"],"properties":{"userGroupId":{"type":"string","description":"New user group ID to assign to the user"}},"additionalProperties":false},"UserGroup":{"type":"object","description":"A user group belonging to a business","properties":{"id":{"type":"string","description":"User group ID"},"businessId":{"type":"string","description":"Owning business ID"},"name":{"type":"string","description":"Display name","example":"Store Managers"},"roles":{"type":"array","items":{"type":"string"},"description":"Roles granted to members of this group","example":["Business"]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Webhook":{"type":"object","allOf":[{"$ref":"#/components/schemas/WebhookRegistrationRequestBody"},{"type":"object","properties":{"webhookId":{"type":"string","description":"Id of webhook","example":"webhook-123"},"authConfig":{"$ref":"#/components/schemas/WebhookAuthConfigPublic"},"type":{"type":"string","enum":["WEBHOOK","TALONONE","BLOOMREACH","KLAVIYO","DOTDIGITAL","INSIDER"],"description":"How the event is delivered. WEBHOOK posts Litecard's own payload to your URL; the rest call that provider's API instead and are set up on the Third Party Integrations page.","example":"WEBHOOK"}}}]},"WebhookAuthConfig":{"type":"object","description":"The properties we need to use for Authentication","properties":{"securityHeader":{"type":"string","description":"The header to use in the request when entering the key","example":"X-API-KEY"},"apiKey":{"type":"string","description":"API Key to use to authenticate with the webhook","example":"abc123"},"basicUsername":{"type":"string","description":"Username to use to authenticate with the webhook","example":"username"},"basicPassword":{"type":"string","description":"Password to use to authenticate with the webhook","example":"password"},"tokenUserName":{"type":"string","description":"Username to use to authenticate with the webhook","example":"username"},"tokenPassword":{"type":"string","description":"Password to use to authenticate with the webhook","example":"password"},"tokenEndpoint":{"type":"string","description":"Endpoint to use to authenticate with the webhook","example":"https://example.com/oauth"},"httpUserName":{"type":"string","description":"HTTP Username to use to authenticate with the webhook","example":"username"},"httpPassword":{"type":"string","description":"HTTP Password to use to authenticate with the webhook","example":"password"}}},"WebhookAuthConfigPublic":{"type":"object","description":"The properties we need to use for Authentication (public, without sensitive keys)","properties":{"securityHeader":{"type":"string","description":"The header to use in the request when entering the key","example":"X-API-KEY"},"tokenEndpoint":{"type":"string","description":"Endpoint to use to authenticate with the webhook","example":"https://example.com/oauth"}}},"WebhookRegistrationRequestBody":{"type":"object","properties":{"webhookUrl":{"type":"string","description":"Where Litecard sends the events. For providers with their own event API this is the base URL, with no path.","example":"https://example.com/webhook"},"method":{"type":"string","description":"Method to use","enum":["POST"],"example":"POST"},"authType":{"type":"string","enum":["API_KEY","BASIC","TOKEN","NONE"],"description":"The type of Auth to use for the webhook"},"authConfig":{"$ref":"#/components/schemas/WebhookAuthConfig"},"events":{"type":"array","description":"Types of events the webhook supports","items":{"enum":["CARD_DOWNLOAD","CARD_REMOVE","REGISTER_WEBHOOK","CARD_CREATE","CARD_UPDATE"],"example":"CARD_DOWNLOAD"}},"provider":{"type":"string","enum":["TALONONE","BLOOMREACH","KLAVIYO","DOTDIGITAL","INSIDER","HANES","SQUARE","SHOPIFY","YOTPO","STRIPE","RIVO","GROWAVE","FYND","OKENDO","FARE_HARBOR","LC_INBOUND_LISTENER"],"description":"Optional. Only TALONONE changes anything: it sends events through Talon.One's own event API, where webhookUrl is their base URL with no path. Omit it for a plain webhook POST of Litecard's payload, which is what this endpoint creates in every other case. The remaining values appear on subscriptions set up through the Third Party Integrations page and are listed so those records validate on read.","example":"TALONONE"},"eventNameMapping":{"type":"object","description":"Litecard event to the name the provider knows it by. Keys must be CARD_CREATE, CARD_UPDATE, CARD_DOWNLOAD or CARD_REMOVE; an event with no entry is not forwarded.","additionalProperties":{"type":"string"},"minProperties":1,"example":{"CARD_DOWNLOAD":"litecard_card_downloaded","CARD_REMOVE":"litecard_card_removed"}},"templateIds":{"type":"array","description":"Restrict the webhook to these card templates. Omit to receive events for every template of the business.","minItems":1,"items":{"type":"string","description":"card template id","example":"AyIbjHCCGnYZwq9PdkB79"}},"batching":{"type":"boolean","description":"When true, card events for this external call are micro-batched (one vendor import per time bucket) instead of sent one HTTP call per event. Defaults to false.","example":false}},"required":["webhookUrl","method","events","authType"]},"createPassTemplateRequestBody":{"type":"object","properties":{"templatePayload":{"$ref":"#/components/schemas/CreateTemplateV1"},"cardPayload":{"$ref":"#/components/schemas/BaseCardPayload"},"options":{"$ref":"#/components/schemas/SignUpOptions"}},"additionalProperties":false,"required":["templatePayload","cardPayload"]},"createPassTemplateResponseBody":{"type":"object","properties":{"cardDetails":{"type":"object","properties":{"cardId":{"type":"string","description":"Id of created card","example":"abc123"},"cardOwnerId":{"type":"string","description":"Id of created card owner","example":"efg345"},"downloadId":{"type":"string","description":"Id used for hosted litecard landing page e.g. https://app.dev.litecard.io/welcome/?id=5c_Wc9h-WCng0oxe8nHNn","example":"5c_Wc9h-WCng0oxe8nHNn"},"appleLink":{"type":"string","description":"URL apple wallet card","example":"https://litecard-demo-pkpass.s3-ap-southeast-2.amazonaws.com/coldbrew.pkpass"},"googleLink":{"type":"string","description":"URL google wallet card","example":"https://pay.google.com/gp/v/save/eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJsaXRlY2FyZC10ZXN0QGNsZXZlci1hYmJleS0zMDg1MDcuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJhdWQiOiJnb29nbGUiLCJ0eXAiOiJzYXZldG9hbmRyb2lkcGF5IiwiaWF0IjoxNjIwMTE0NDkxLCJwYXlsb2FkIjp7ImxveWFsdHlPYmplY3RzIjpbeyJpZCI6IjMzODgwMDAwMDAwMTA2MDYyNDEuTE9ZQUxUWV9PQkpFQ1RfNTRkYmMwNTUtOWRlMy00YWZjLTg0YmUtZDhiNTZjN2U5Mzc5IiwiY2xhc3NJZCI6IjMzODgwMDAwMDAwMTA2MDYyNDEuTE9ZQUxUWV9DTEFTU185OTliOTVlYi00ZDVjLTQzZDEtOGFhOC05Zjg1ZjI0YzlmODQiLCJzdGF0ZSI6IkFDVElWRSIsImFjY291bnROYW1lIjoiSmFzb24iLCJhY2NvdW50SWQiOiIxMjMxMjMiLCJiYXJjb2RlIjp7ImFsdGVybmF0ZVRleHQiOiIxMjMxMjMiLCJ2YWx1ZSI6IjEyMzEyMyIsInR5cGUiOiJDT0RFXzEyOCJ9LCJsb2NhdGlvbnMiOlt7ImxhdGl0dWRlIjoiLTM3LjgwNjIzMTU2ODQyODgxIiwibG9uZ2l0dWRlIjoiMTQ0Ljk4OTkwMjQwODE0ODcifV0sInZhbGlkVGltZUludGVydmFsIjp7InN0YXJ0Ijp7ImRhdGUiOiIyMDIxLTA0LTIwVDAwOjAwOjAwLjAwKzEwOjAwIn0sImVuZCI6eyJkYXRlIjoiMjAyNS0wNC0yMFQyMzo1OTowMC4wMCsxMDowMCJ9fSwibGlua3NNb2R1bGVEYXRhIjp7InVyaXMiOlt7InVyaSI6InRlbDo2NTA1NTU1NTU1IiwiZGVzY3JpcHRpb24iOiJNb2JpbGUgTnVtYmVyIn0seyJ1cmkiOiJtYWlsdG86am9obmRvZUBsaXRlY2FyZC5jb20uYXUiLCJkZXNjcmlwdGlvbiI6IkVtYWlsIn1dfX1dfSwib3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjgwODAiXX0.bR5nSNqMPH6KUwiaqMP-E-nlyjzllOR9-rxUCVgPhqvra_qZ54kc3ZHX64LyViABgSbLIqFEG_EsfA9FCVunAs3ikr1yB5Tk3WiwQ13zt-rH7443_XTI078mtzESl930AL5mVhyrteTMfrhHHV0rrzChXDPgqBjPzsaqs4oGIeW7bVu7S1SC_M8EC-pxTUwqkvvv17xpBVBY_BKzDaK1uEykqdQp2XFHjjRT_esBc6xbiMTDGz11sp395v0RO-9jpp4z-L5igi7prFDgm7ZWBcbSZSKMBckp1lzctLIZ3jidJ9pg-lwgaZyyksmWWnDkVFKE0CWAQV3wFg6M2PXXyA"},"paymentRequired":{"type":"boolean","description":"Require payment after form submission"}}},"success":{"type":"boolean","description":"Returns whether the request was successful","example":true},"templateId":{"type":"string","description":"Identifier for the template used to create the card","example":"test_business"},"formId":{"type":"string","description":"Id for field input form","example":"V1StGXR8_Z5jdHi6B-myT"}}},"getBacklinksResponse":{"type":"object","description":"Download Urls for welcome page","properties":{"backlink":{"type":"string","description":"URL belonging to the backlinkId","example":"https://example.com"}},"required":["backlink"]},"CreateOrderTemplateV1":{"type":"object","description":"Create a reusable order template: Apple order type, merchant, and optional links. Per-order data is supplied via CreateWalletOrderV1.","additionalProperties":false,"required":["name","description","merchant","fulfillment"],"properties":{"name":{"type":"string","description":"Human-readable order template name.","example":"Litecard"},"description":{"type":"string","description":"Human-readable order template description.","example":"Litecard Shipping Order"},"certificates":{"type":"object","description":"Apple certificate settings for signing Wallet order packages from this template.","properties":{"appleSettings":{"type":"object","description":"Apple Wallet signing settings (`certId` maps to the template's pass type / order type identifier).","properties":{"certId":{"type":"string","description":"Apple signing certificate id used to sign `.order` packages for this template."}}}}},"merchant":{"$ref":"#/components/schemas/OrderTemplateMerchantV1"},"barcode":{"$ref":"#/components/schemas/OrderTemplateBarcodeV1"},"settings":{"$ref":"#/components/schemas/OrderTemplateSettingsV1"},"fulfillment":{"$ref":"#/components/schemas/OrderTemplateFulfillmentV1"},"status":{"$ref":"#/components/schemas/OrderStatusV1"}}},"CreateWalletOrderResponseV1":{"type":"object","description":"Apple `orderIdentifier` (orderId), merchant order number, download URL, and the signed `order.json` payload.","required":["orderId","orderNumber","appleLink"],"properties":{"success":{"type":"boolean","description":"Whether the API operation completed successfully."},"orderId":{"type":"string","description":"Apple Wallet `orderIdentifier`, Litecard order record id (`order#…` partition key), and value written to `order.json`."},"orderNumber":{"type":"string","description":"Merchant-visible order number displayed in Wallet and customer emails."},"appleLink":{"type":"string","description":"Public HTTPS URL of the signed Apple Wallet `.order` package."}}},"CreateWalletOrderV1":{"type":"object","description":"Create one Wallet order instance: references an order template for static fields and supplies per-order payment and identifiers.","additionalProperties":false,"required":["orderTemplateId","payment","fulfillment","orderManagementURL"],"properties":{"orderTemplateId":{"type":"string","description":"Id of the order template (`OrderTemplateV1`).","example":"ot_V1StGXR8_Z5jdHi6B-myT"},"orderManagementURL":{"type":"string","format":"uri","description":"URL where the customer can view or manage this order in your order-management system.","example":"https://example.com/order-management"},"orderType":{"type":"string","enum":["ecommerce"],"description":"Apple `Order.orderType`. Defaults to `ecommerce` when omitted on create.","example":"ecommerce"},"orderNumber":{"type":"string","description":"Merchant-visible order number. If omitted, the server will generate one.","example":"ORD-10042"},"fulfillment":{"allOf":[{"$ref":"#/components/schemas/WalletOrderFulfillmentV1"}],"description":"Initial fulfillment state for the order, written to `order.json` on create."},"externalReference":{"type":"string","description":"Optional id from your ERP or OMS for correlation.","example":"erp-invoice-998877"},"payment":{"$ref":"#/components/schemas/WalletOrderPaymentV1"},"lineItems":{"type":"array","description":"Optional `Order.lineItems` — products or services on the order.","items":{"$ref":"#/components/schemas/WalletOrderLineItemV1"}},"customer":{"$ref":"#/components/schemas/WalletOrderCustomerV1"},"barcode":{"$ref":"#/components/schemas/WalletOrderBarcodeV1"}}},"ExtendedWalletOrderV1":{"type":"object","description":"Full Wallet order detail including merchant, payment, line items, and fulfillment history from `order.json`.","additionalProperties":true,"required":["id","orderTemplateId","orderManagementURL","orderNumber","status"],"properties":{"schemaVersion":{"type":"string","description":"Apple Wallet Orders `schemaVersion` embedded in `order.json` (e.g. `1.0`).","example":"1.0"},"orderTypeIdentifier":{"type":"string","description":"Apple Wallet `orderTypeIdentifier` from the order template signing certificate."},"orderManagementURL":{"type":"string","format":"uri","description":"URL where the customer can view or manage this order in your order-management system."},"id":{"type":"string","description":"Apple Wallet `orderIdentifier`, Litecard order record id (`order#…` partition key), and value written to `order.json`."},"orderTemplateId":{"type":"string","description":"Id of the order template (`OrderTemplateV1`)."},"orderNumber":{"type":"string","description":"Merchant-visible order number displayed in Wallet and customer emails."},"createdAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the record was created."},"updatedAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the record was last updated."},"status":{"$ref":"#/components/schemas/OrderStatusV1"},"fulfillmentType":{"type":"string","description":"Current fulfillment method on the order (`shipping` or `pickup`)."},"fulfillmentStatus":{"type":"string","description":"Current fulfillment status on the order."},"barcode":{"$ref":"#/components/schemas/WalletOrderBarcodeV1"},"appleStatus":{"type":"string","description":"Apple Order state in Apple Wallet: `ACTIVE` when added to Wallet, `INACTIVE` when removed from Wallet.","enum":["ACTIVE","INACTIVE"]},"merchantName":{"type":"string","description":"Displayed name shown in the merchant section"},"merchant":{"$ref":"#/components/schemas/OrderTemplateMerchantV1"},"payment":{"$ref":"#/components/schemas/WalletOrderPaymentV1"},"lineItems":{"type":"array","description":"Optional `Order.lineItems` returned in `order.json`.","items":{"$ref":"#/components/schemas/WalletOrderLineItemV1"}},"fulfillmentHistory":{"type":"array","description":"Chronological fulfillment events stored on the order and embedded in `order.json`.","items":{"$ref":"#/components/schemas/WalletOrderFulfillmentV1"}},"customer":{"$ref":"#/components/schemas/WalletOrderCustomerV1"},"carrier":{"type":"string","description":"Shipping carrier name (e.g. Australia Post)."},"trackingNumber":{"type":"string","description":"Carrier tracking number for the shipment."},"trackingURL":{"type":"string","description":"Customer-facing HTTPS tracking URL for the shipment."},"shippingType":{"type":"string","enum":["shipping","delivery"],"description":"Apple `shippingType`: `shipping` (carrier shipment) or `delivery` (last-mile)."},"shippedAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the order was shipped."},"deliveredAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the order was delivered."},"pickupAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the order is ready for pickup."},"pickedUpAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the customer collected the order."},"pickupWindowDuration":{"type":"string","format":"duration","description":"Pickup window duration in ISO 8601-1 format (e.g. `PT2H`)."},"pickupLocation":{"allOf":[{"$ref":"#/components/schemas/WalletOrderLocationV1"}],"description":"Geographic coordinates of the pickup location (`PickupFulfillment.location`)."},"pickupBarcode":{"allOf":[{"$ref":"#/components/schemas/WalletOrderBarcodeV1"}],"description":"Barcode presented at pickup; may differ from the order-level barcode."}}},"OrderStatusV1":{"type":"string","enum":["ACTIVE","PENDING","DELETED"],"description":"Litecard lifecycle status for orders and order templates."},"OrderTemplateBarcodeV1":{"type":"object","description":"Order template barcode configuration","required":["type"],"properties":{"type":{"type":"string","enum":["NOT_USED","QR_CODE","CODE_128","PDF_417","AZTEC"],"description":"The type of barcode to use for the order template."},"messageEncoding":{"type":"string","example":"iso-8859-1","description":"The encoding to use for the barcode message."},"altText":{"type":"string","example":"Scan Here","description":"The alternative text to use for the barcode."},"disabledAltText":{"type":"boolean","example":false,"description":"Disable the alternative text for the barcode. If true, the alternative text will not be displayed in Wallet."}}},"OrderTemplateBaseFulfillmentFormV1":{"type":"object","description":"Base fulfillment form for the order template. It is used as default description and notes for order. It can be overriden during order creation or update.","properties":{"status":{"type":"string","description":"Apple fulfillment status (`open`, `processing`, `shipped`, `delivered`, `readyForPickup`, etc.)."},"statusDescription":{"type":"string","description":"Human-readable fulfillment status shown in Wallet."},"notes":{"type":"string","description":"Additional fulfillment notes shown in Wallet."}}},"OrderTemplateFulfillmentV1":{"type":"object","description":"Fulfillment type and forms for the order template.","required":["type"],"properties":{"type":{"type":"string","enum":["shipping","pickup"],"description":"Order template fulfillment type. It can be either `shipping` or `pickup`."},"forms":{"type":"array","description":"Fulfillment forms for the order template. It can be used as default description and notes for order.","items":{"$ref":"#/components/schemas/OrderTemplateBaseFulfillmentFormV1"}}}},"OrderTemplateMerchantV1":{"type":"object","description":"Merchant information for the order template. It will be shared for all orders created within the same template.","additionalProperties":false,"required":["merchantName","logo","url"],"properties":{"merchantName":{"type":"string","description":"Displayed name shown in the merchant section","example":"Example Store"},"merchantIdentifier":{"type":"string","description":"Apple Merchant Identifier. If not provided, it will be default to Litecard's merchant identifier."},"logo":{"type":"string","format":"uri","description":"Public HTTPS URL of a PNG logo for the merchant."},"url":{"type":"string","format":"uri","description":"Merchant homepage URL."}}},"OrderTemplateSettingsV1":{"type":"object","description":"Order template additional settings.","properties":{"sendInvitationEmail":{"type":"boolean","description":"Order template setting to send an invitation email when the order is created.","example":false},"emailTemplate":{"type":"string","description":"The email template to use when sending an invitation email. If not provided, the default email template will be used."}}},"OrderTemplateV1":{"type":"object","description":"Stored order template including id and tenancy metadata.","additionalProperties":false,"required":["id","businessId","name","merchant"],"properties":{"id":{"type":"string","description":"Stable id for this order template (use as `orderTemplateId` when creating orders).","example":"ot_V1StGXR8_Z5jdHi6B-myT"},"businessId":{"type":"string","description":"Owning business id (set from auth context when created)."},"name":{"type":"string","description":"Human-readable order template name."},"certificates":{"type":"object","description":"Apple certificate settings for signing Wallet order packages from this template.","properties":{"appleSettings":{"type":"object","description":"Apple Wallet signing settings (`certId` maps to the template's pass type / order type identifier).","properties":{"certId":{"type":"string","description":"Apple signing certificate id used to sign `.order` packages for this template."}}}}},"description":{"type":"string","description":"Human-readable order template description."},"merchant":{"$ref":"#/components/schemas/OrderTemplateMerchantV1"},"barcode":{"$ref":"#/components/schemas/OrderTemplateBarcodeV1"},"settings":{"$ref":"#/components/schemas/OrderTemplateSettingsV1"},"fulfillment":{"$ref":"#/components/schemas/OrderTemplateFulfillmentV1"},"createdAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the record was created."},"updatedAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the record was last updated."},"uiConfig":{"$ref":"#/components/schemas/UIConfig"}}},"PatchWalletOrderResponseV1":{"type":"object","description":"Response after patching a Wallet order — updated `orderId` and re-signed `.order` package URL.","required":["orderId","appleLink"],"properties":{"success":{"type":"boolean","description":"Whether the API operation completed successfully."},"orderId":{"type":"string","description":"Apple Wallet `orderIdentifier`, Litecard order record id (`order#…` partition key), and value written to `order.json`."},"appleLink":{"type":"string","description":"Public HTTPS URL of the signed Apple Wallet `.order` package."}}},"PatchWalletOrderV1":{"type":"object","description":"Patch fulfillment on an existing Wallet order, re-sign the `.order` package, and notify registered devices.","properties":{"fulfillment":{"allOf":[{"$ref":"#/components/schemas/WalletOrderFulfillmentV1"}],"description":"Updated fulfillment event appended to the order and `order.json`."},"payment":{"type":"object","description":"Partial payment update for the signed `order.json` package.","required":["status"],"properties":{"status":{"type":"string","description":"Apple `Order.payment.status` shown in Wallet (`authorized`, `paid`, `voided`, `refunded`, `pending`).","enum":["authorized","paid","voided","refunded","pending"],"example":"pending"}}}}},"UpdateOrderTemplateV1":{"type":"object","description":"Partial update of an order template. Include `id` and only fields to change.","additionalProperties":false,"required":["name","description","merchant","fulfillment","id"],"properties":{"id":{"type":"string","description":"Id of the order template (`OrderTemplateV1`)."},"name":{"type":"string","description":"Human-readable order template name.","example":"Litecard"},"description":{"type":"string","description":"Human-readable order template description.","example":"Litecard Shipping Order"},"certificates":{"type":"object","description":"Apple certificate settings for signing Wallet order packages from this template.","properties":{"appleSettings":{"type":"object","description":"Apple Wallet signing settings (`certId` maps to the template's pass type / order type identifier).","properties":{"certId":{"type":"string","description":"Apple signing certificate id used to sign `.order` packages for this template."}}}}},"merchant":{"$ref":"#/components/schemas/OrderTemplateMerchantV1"},"barcode":{"$ref":"#/components/schemas/OrderTemplateBarcodeV1"},"settings":{"$ref":"#/components/schemas/OrderTemplateSettingsV1"},"fulfillment":{"$ref":"#/components/schemas/OrderTemplateFulfillmentV1"},"status":{"$ref":"#/components/schemas/OrderStatusV1"}}},"WalletOrderBarcodeV1":{"type":"object","description":"Order-level barcode shown in Wallet (`Order.barcode` in `order.json`).","required":["barcodeValue"],"properties":{"barcodeValue":{"type":"string","description":"Encoded barcode payload written to `order.json` (e.g. order or pickup reference)."},"altText":{"type":"string","description":"Accessible alternative text for the barcode in Wallet."}}},"WalletOrderCustomerV1":{"type":"object","description":"Customer contact details embedded in `order.json` and used for invitation emails.","properties":{"firstName":{"type":"string","description":"The first name of the customer.","example":"John"},"lastName":{"type":"string","description":"The last name of the customer.","example":"Doe"},"emailAddress":{"type":"string","format":"email","description":"The email address of the customer.","example":"john.doe@example.com"},"phoneNumber":{"type":"string","description":"The phone number of the customer.","example":"+61412345678"}}},"WalletOrderFulfillmentV1":{"description":"Fulfillment event for a Wallet order — either shipping or pickup (`anyOf` in `order.json`).","anyOf":[{"$ref":"#/components/schemas/WalletOrderShippingFulfillmentV1"},{"$ref":"#/components/schemas/WalletOrderPickupFulfillmentV1"}]},"WalletOrderLineItemV1":{"type":"object","description":"An item on the order. `title` / `subtitle` may be localization keys resolved via `*.lproj/order.strings`.","additionalProperties":false,"required":["title","quantity","price"],"properties":{"title":{"type":"string","description":"Primary label (plain string or strings-file key, e.g. LineItemTitle)."},"subtitle":{"type":"string","description":"Secondary line (plain string or strings-file key)."},"quantity":{"type":"integer","description":"Integer quantity purchased (Apple sample orders use integers).","example":2,"minimum":0},"lineItemIdentifier":{"type":"string","description":"Stable id for this line (SKU, cart line id, etc.)."},"price":{"allOf":[{"$ref":"#/components/schemas/WalletOrderMoneyV1"}],"description":"Apple `LineItem.price` — `{amount, currency}` per line item."},"imageUrl":{"type":"string","format":"uri","description":"The URL of the image of the item."},"createdAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the record was created."},"updatedAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the record was last updated."}}},"WalletOrderLocationV1":{"type":"object","description":"Geographic coordinates for pickup or delivery location in Apple Wallet Orders.","additionalProperties":false,"required":["latitude","longitude"],"properties":{"latitude":{"type":"number","description":"The latitude of the location."},"longitude":{"type":"number","description":"The longitude of the location."},"altitude":{"type":"number","description":"The altitude of the location."}}},"WalletOrderMoneyV1":{"type":"object","description":"Money value used by Apple Wallet Orders (e.g. `payment.summaryItems[].value`).","additionalProperties":false,"required":["amount","currency"],"properties":{"amount":{"type":"number","description":"Numeric amount (Apple uses decimals, not strings).","example":24.99},"currency":{"type":"string","description":"ISO 4217 currency code.","example":"AUD"}}},"WalletOrderPaymentSummaryItemV1":{"type":"object","description":"Single line in `payment.summaryItems`. Apple sample orders wrap the amount in a `value` object with explicit currency.","additionalProperties":false,"required":["label","value"],"properties":{"label":{"type":"string","description":"Label for a payment summary line (e.g. `Subtotal`, `Tax`, `Total`).","example":"Subtotal"},"value":{"$ref":"#/components/schemas/WalletOrderMoneyV1"}}},"WalletOrderPaymentV1":{"type":"object","description":"Payment block for `order.json`.","additionalProperties":false,"required":["summaryItems"],"properties":{"summaryItems":{"type":"array","description":"Itemized payment lines shown in Wallet (`Order.payment.summaryItems`).","items":{"$ref":"#/components/schemas/WalletOrderPaymentSummaryItemV1"}},"status":{"type":"string","description":"Apple `Order.payment.status` shown in Wallet (`authorized`, `paid`, `voided`, `refunded`, `pending`).","enum":["authorized","paid","voided","refunded","pending"],"example":"pending"},"total":{"allOf":[{"$ref":"#/components/schemas/WalletOrderMoneyV1"}],"description":"Apple `Order.payment.total` — grand total displayed by Wallet."},"paymentMethods":{"type":"array","description":"Apple `Order.payment.paymentMethods` — masked descriptors (e.g. `\"•••• 1234\"`).","items":{"type":"string"},"example":["•••• 1234"]}}},"WalletOrderPickupFulfillmentV1":{"type":"object","description":"Pickup fulfillment event for Apple Wallet Orders (`PickupFulfillment` in `order.json`).","additionalProperties":false,"required":["status"],"properties":{"displayName":{"type":"string","description":"Pickup location name shown in Wallet."},"fulfillmentIdentifier":{"type":"string","description":"Stable id for this fulfillment event in `order.json` fulfillments history."},"fulfillmentType":{"type":"string","enum":["pickup"],"description":"Fulfillment method for this order: `shipping` or `pickup` (Apple `fulfillmentType`)."},"status":{"type":"string","description":"Apple fulfillment status (`open`, `processing`, `shipped`, `delivered`, `readyForPickup`, etc.).","enum":["open","processing","readyForPickup","pickedUp","issue","cancelled"]},"statusDescription":{"type":"string","description":"Human-readable fulfillment status shown in Wallet."},"notes":{"type":"string","description":"Additional fulfillment notes shown in Wallet."},"pickupAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the order is ready for pickup."},"pickedUpAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the customer collected the order."},"pickupWindowDuration":{"type":"string","format":"duration","description":"Pickup window duration in ISO 8601-1 format (e.g. `PT2H`)."},"pickupLocation":{"allOf":[{"$ref":"#/components/schemas/WalletOrderLocationV1"}],"description":"Geographic coordinates of the pickup location (`PickupFulfillment.location`)."},"pickupBarcode":{"allOf":[{"$ref":"#/components/schemas/WalletOrderBarcodeV1"}],"description":"Barcode presented at pickup; may differ from the order-level barcode."},"createdAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the record was created."},"updatedAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the record was last updated."}}},"WalletOrderShippingFulfillmentV1":{"type":"object","description":"Shipping fulfillment event for Apple Wallet Orders (`ShippingFulfillment` in `order.json`).","additionalProperties":false,"required":["status"],"properties":{"fulfillmentIdentifier":{"type":"string","description":"Stable id for this fulfillment event in `order.json` fulfillments history."},"fulfillmentType":{"type":"string","enum":["shipping"],"description":"Fulfillment method for this order: `shipping` or `pickup` (Apple `fulfillmentType`)."},"status":{"type":"string","description":"Apple fulfillment status (`open`, `processing`, `shipped`, `delivered`, `readyForPickup`, etc.).","enum":["open","processing","onTheWay","outForDelivery","delivered","shipped","issue","cancelled"]},"statusDescription":{"type":"string","description":"Human-readable fulfillment status shown in Wallet."},"notes":{"type":"string","description":"Additional fulfillment notes shown in Wallet."},"carrier":{"type":"string","description":"Shipping carrier name (e.g. Australia Post)."},"trackingNumber":{"type":"string","description":"Carrier tracking number for the shipment."},"trackingURL":{"type":"string","description":"Customer-facing HTTPS tracking URL for the shipment."},"shippingType":{"type":"string","enum":["shipping","delivery"],"description":"Apple `shippingType`: `shipping` (carrier shipment) or `delivery` (last-mile)."},"shippedAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the order was shipped."},"deliveredAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the order was delivered."},"createdAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the record was created."},"updatedAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the record was last updated."}}},"WalletOrderV1":{"type":"object","description":"Summary Wallet order returned from list endpoints (identifiers, status, fulfillment summary).","additionalProperties":true,"required":["id","orderTemplateId","orderManagementURL","orderNumber","status"],"properties":{"schemaVersion":{"type":"string","description":"Apple Wallet Orders `schemaVersion` embedded in `order.json` (e.g. `1.0`).","example":"1.0"},"orderTypeIdentifier":{"type":"string","description":"Apple Wallet `orderTypeIdentifier` from the order template signing certificate."},"orderManagementURL":{"type":"string","format":"uri","description":"URL where the customer can view or manage this order in your order-management system."},"id":{"type":"string","description":"Apple Wallet `orderIdentifier`, Litecard order record id (`order#…` partition key), and value written to `order.json`."},"orderTemplateId":{"type":"string","description":"Id of the order template (`OrderTemplateV1`)."},"orderNumber":{"type":"string","description":"Merchant-visible order number displayed in Wallet and customer emails."},"createdAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the record was created."},"updatedAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the record was last updated."},"status":{"$ref":"#/components/schemas/OrderStatusV1"},"fulfillmentType":{"type":"string","description":"Current fulfillment method on the order (`shipping` or `pickup`)."},"fulfillmentStatus":{"type":"string","description":"Current fulfillment status on the order."},"barcode":{"$ref":"#/components/schemas/WalletOrderBarcodeV1"},"appleStatus":{"type":"string","description":"Apple Order state in Apple Wallet: `ACTIVE` when added to Wallet, `INACTIVE` when removed from Wallet.","enum":["ACTIVE","INACTIVE"]},"merchantName":{"type":"string","description":"Displayed name shown in the merchant section"}}}},"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"tags":[{"name":"Authentication","description":"**Start here.** Every request outside the public download pages carries a bearer token you mint yourself.\n\nPost your **dashboard username and password** to `POST /api/v1/token` and you get a JWT back. Send it as `Authorization: Bearer <token>`. That is the recommended route and needs no setup beyond having an account.\n\nTokens last **24 hours** by default, so cache one and refresh on `401` rather than minting per request.\n\nOAuth2 `client_credentials` is also accepted, for systems that require that spec. Those credentials are issued on request rather than self-service. See [Authentication](#description/authentication).\n\nRunning a master account? Add the `x-active-business-id` header to name the sub-business you are acting on. A sub user's token is already scoped and needs no header. See [Master and sub-business accounts](#description/master-and-sub-business-accounts)."},{"name":"Health Check","description":"Liveness probe for uptime monitors and load-balancer checks. No auth, no side effects.\n\n**Two operations, one handler.** `GET` returns a small JSON body you can assert on. `HEAD` returns the status line alone, for probes that read nothing else.\n\nBoth answer \"is the API reachable\", not \"is my integration working\". Either returns `200` whether or not you send a token."},{"name":"Documentation","description":"**The spec behind this page.** Point your code generator here.\n\n`GET /api/v1/docs` is the raw OpenAPI 3.0 contract: paths, schemas, status codes, nothing else. `GET /api/v1.0/docs` is the same contract with this page's prose folded in, which makes it larger and noisier for tooling.\n\nBoth describe the same v1 API. `v1.0` names this reference, not a second version of the API."},{"name":"Pass Templates CRUD","description":"**The design, not the pass.** A template is the reusable blueprint every pass is stamped from: colours, images, barcode format, data fields, expiry rules, and which wallets it targets.\n\n**Build your first one in the dashboard, not here.** A template is a large, fiddly document, and the dashboard validates and previews it as you go. [Prerequisite 2](#description/2-a-pass-template) walks through it, and shows how the eye icon hands you a ready-made pass payload for any template.\n\nYou typically create a handful of templates and then issue thousands of passes against them. When one pass needs to differ (a different store address, a member-tier colour), do not clone the template. Send `templateOverrides` on the pass instead. See [Templates, passes and overrides](#description/templates-passes-and-overrides).\n\n**Editing a template does not rewrite live passes by default.** `PUT /api/v1/template` saves the design and refreshes the Google class behind existing passes, but the fields on passes already in wallets keep their values unless you send `updateExistingCards: true`. That flag is what triggers the over-the-air push."},{"name":"Pass Templates Analytics","description":"**How one template is performing.** Two endpoints over the same data, and the difference is the shape you get back.\n\n| Endpoint | Returns | Use it for |\n| --- | --- | --- |\n| `/downloads/count` | **One total** per counter, summed over the period | A number on a dashboard tile |\n| `/downloads/trend` | **One row per day**, oldest first | Plotting a curve |\n\nBoth read the same daily records: passes created, downloads split by Apple and Google, and removals. Both are scoped to your active business, so pass `x-active-business-id` if you are on a master account and want a sub-business's template.\n\n### reference: what a day's row is counting\n\nThe trend takes a `reference`, and the two options count the same day differently.\n\n![CREATION and DOWNLOADS count the same day differently](/api/v1.0/assets/trend-reference.svg)\n\n**`CREATION` follows the cohort created that day**, however long a download takes to arrive, so it is the view for judging a send. **`DOWNLOADS` counts the downloads that happened that day**, whichever day those passes were issued. The operation below works through the numbers.\n\nFor the whole business rather than one template, the same pair lives at `GET /api/v1/pass/downloads/count` and `GET /api/v1/pass/downloads/trend`, and [Business Analytics](#tag/business-analytics) covers them."},{"name":"Pass","description":"**Create, update and query passes.** The centre of the API.\n\n`POST /api/v1/card` mints a pass from a template and returns a download link you send to its owner. `PATCH /api/v1/card` is the workhorse: change a data-field value and the update is pushed to Apple and Google Wallet automatically, on the device, without the owner doing anything. That push is the whole reason to use wallet passes over a PDF.\n\n`POST /api/v1/card/status` deactivates, reactivates or deletes. Deactivating leaves the pass in the wallet but marks it void when scanned.\n\nOn the read side, `POST /api/v1/pass` lists and pages with a cursor, and `POST /api/v1/pass/search` filters on data-field values, which is what you want when looking up one owner.\n\nIssuing many passes at once? Do not loop the create endpoint. Use [Pass Uploads](#tag/pass-uploads) or `POST /api/v1/card/bulk-fast-create`.\n\n### The downloadId\n\nCreating a pass returns a `downloadId`. **That is how the owner gets the pass**: it addresses the hosted download page, where one tap adds the pass to their wallet.\n\n```\n<dashboard-url>/welcome/?id=<downloadId>\n```\n\nSo on demo, `https://demo.litecard.io/welcome/?id=5c_Wc9h-WCng0oxe8nHNn`. Send that link by email, SMS or QR code and the owner needs nothing else, no app install and no account.\n\n**You can choose it yourself.** Pass `options.downloadId` when you create the pass and Litecard uses it; leave it out and Litecard mints a unique one. Setting your own lets you build the download URL before you call, so the link can go into a message you have already composed.\n\n**It is fixed once the pass exists.** Updating a pass never changes its `downloadId`, so a link you have already sent keeps working for the life of the pass.\n\nTo turn a `downloadId` back into wallet links, see [Access Passes by Download ID](#tag/access-passes-by-download-id).\n\n> **Note.** The paths say `card` and the payloads use `cardId` and `cardPayload`. That is the original naming, kept for compatibility. A card and a pass are the same object."},{"name":"Pass Owner","description":"**A person, not a pass.** A pass owner is Litecard's identity for someone across templates: a holder of a loyalty pass and an event ticket is one owner with two passes.\n\nAddress them by email or phone rather than by pass id. `POST /api/v1/card-owner` lists everything they hold, and `DELETE /api/v1/card-owner/delete` removes all of it, which is the shape a data-deletion request arrives in."},{"name":"Access Passes by Download ID","description":"**The owner-facing side.** Everything here takes a `downloadId` and hands back a way into the pass.\n\n`GET /api/v1/downloadId/{downloadId}` returns the Add-to-Wallet links behind the hosted page. `POST /api/v1/card/retrieve` resolves the same thing for the requesting device, so the owner lands on the right wallet, which is what you want when re-sending a pass to someone who lost their link.\n\nSee [Pass](#tag/pass) for where a `downloadId` comes from and how to set your own."},{"name":"Push Notifications","description":"**Reach the lock screen without an app.** `POST /api/v1/queue/notification` queues a message to an audience you pick.\n\n### What you send\n\nThree fields, all required.\n\n| Field | What it is |\n| --- | --- |\n| `message` | The line the owner reads. Write this one first. |\n| `title` | A short heading shown above the message. |\n| `dataFieldKey` | Always `\"notificationKey\"`. |\n\n`dataFieldKey` is always `\"notificationKey\"`. Litecard keeps one dedicated field on every pass for notification text, and that is its key. Send it as a constant and do not vary it.\n\nEmoji are safe in both fields, and a newline in `message` renders as a line break.\n\nEvery pass is stamped from a [pass template](#tag/pass-templates-crud), and the icon shown beside the notification is that template's `images.icon` (see `TemplateImagesV1`). Make it **square, and solid rather than transparent**: it is rendered small, against a lock screen you do not control.\n\n### What the owner sees\n\nThe two wallets behave differently, and the difference changes how you write.\n\nSo let us send one and follow it. The title carries a placeholder, so each owner gets their own:\n\n```json\n{\n  \"title\": \"Hi {{firstName}} 🎉\",\n  \"message\": \"Your 10th coffee is free ☕ Show this pass at the till before Sunday to claim it and start a fresh card 🎉\"\n}\n```\n\nThe screens below belong to John, so his copy reads `Hi John 🎉`.\n\n#### How does it reach Apple Wallet?\n\n![Apple Wallet: where your title and message land](/api/v1.0/assets/push-apple-wallet.svg)\n\n#### How does it reach Google Wallet?\n\n![Google Wallet: where your title and message land](/api/v1.0/assets/push-google-wallet.svg)\n\n### Personalising with {{placeholders}}\n\n**The dashboard is the playground.** Open **Notifications → Create Notifications**, pick a template, then type `@` in the Title or Message field. It lists every placeholder that template can resolve and inserts the token for you, so you can find the keys before writing a line of code. What it writes is exactly what this endpoint expects.\n\nThe list only appears once a single template is selected, for the same reason the API behaves that way: there is no correct set of keys until Litecard knows which template you mean.\n\n![Create Notifications in the Litecard dashboard, with the placeholder menu open](/api/v1.0/assets/create-notifications.svg)\n\n**What goes inside the braces is a data field key.** Every template carries a `dataFields` array, and each entry has a `key`:\n\n```json\n\"dataFields\": [\n  { \"key\": \"points\", \"label\": \"Stars\", \"usage\": [\"APPLE_WALLET\"], \"userCanEdit\": false }\n]\n```\n\nThat key is the placeholder. `points` becomes `{{points}}`. So the set you can use is decided by the [template](#tag/pass-templates-crud) you send to, which is why a personalised push should target one `templateId`.\n\n```json\n{\n  \"notification\": {\n    \"title\": \"Hi {{firstName}} 🎉\",\n    \"fallbackTitle\": \"Hi there 🎉\",\n    \"message\": \"You have {{points}} points waiting ☕\",\n    \"fallbackMessage\": \"You have points waiting ☕\",\n    \"dataFieldKey\": \"notificationKey\"\n  },\n  \"templateIds\": [\"tmpl_coffee\"],\n  \"options\": { \"pushNotification\": true }\n}\n```\n\n**Why fallbacks are mandatory.** An unresolved placeholder is not dropped, and it is not left blank. It ships literally. Without a fallback, an owner with no `points` on record reads this on their lock screen:\n\n```\nYou have {{points}} points waiting ☕\n```\n\nSo `fallbackTitle` is required whenever `title` contains a placeholder, and `fallbackMessage` whenever `message` does. A fallback may not contain a placeholder itself. Break either rule and the call fails with `400 INVALID_NOTIFICATION_PAYLOAD` before anything is queued.\n\n**A fallback replaces the whole line, not the gap.** If any placeholder in the message cannot be resolved for a pass, that pass gets `fallbackMessage` in full, not the message with a hole in it. Write each fallback as a complete sentence you would be happy to send on its own.\n\n### Choosing who it goes to\n\n![The five audience strategies and the order they are checked in](/api/v1.0/assets/push-audience.svg)\n\n**Five strategies, and you only ever get one.** They are mutually exclusive. Litecard walks them in a fixed order and stops at the first one you filled in, so sending two does not narrow the audience: the higher-precedence one wins and the other is ignored.\n\n| # | Strategy | Who it reaches |\n| --- | --- | --- |\n| 1 | `cardIds` | exactly the passes you list |\n| 2 | `templateIds` | every active pass issued from those templates |\n| 3 | `templateTier` | every active pass at those template + tier pairs |\n| 4 | `segments` | one template, narrowed by segment values |\n| 5 | `options.sendAll` | every active pass in the business |\n\n`cardIds` is a required property, so send `\"cardIds\": []` when you are using any of the other four.\n\n**1. `cardIds` — you already know exactly who.** An array of pass ids, up to **10,000 in a single call**. Nothing else is consulted. Batch a larger audience into successive calls of 10,000 rather than looping one id at a time, and use a list of one to notify a single pass. This is the one strategy that cannot be scheduled to repeat: combine it with `notification.recurringConfig` and the call fails with `400 INVALID_NOTIFICATION_PAYLOAD`.\n\n**2. `templateIds` — everyone holding a pass type.** The union of every active pass across the ids you list. **The best choice for a personalised push, with exactly one id in the array.** Placeholder keys come from `template.dataFields[].key`, so a second template that does not define `points` sends your fallback to all of its holders and the send still succeeds — nothing warns you.\n\n**3. `templateTier` — tiers, not whole templates.** An array of `{ templateId, tier }` pairs. Each is queried on its own and the results are unioned, so an owner matched twice is still one recipient.\n\n**The ids do not have to match, and nothing stops you mixing them.** There is no validation: a list spanning several templates is accepted and simply queried entry by entry. An entry naming a tier the template does not have matches nothing, equally silently.\n\n**Send to one template per call anyway — one of its tiers, or several.** That is what this strategy is built for, and what it is indexed for. Mix templates and the rule from strategy 2 applies again: placeholder keys are defined per template, so holders of any template in the list that does not define your key receive the fallback.\n\nThis is also **the one strategy that combines with another field.** Send `segments` alongside `templateTier` and each pair's passes are filtered by those segment values before sending. That is a narrowing, not a second strategy.\n\n**4. `segments` — one template, sliced.** Reached only when `templateTier` is absent. The object carries its own `templateId` plus `segment1` through `segment5`. A pass must match **every slot you fill**: values inside one slot are alternatives, and slots you leave out match everything. One template, so personalisation is dependable.\n\n**5. `options.sendAll` — every active pass in the business.** Every template, every owner. **We do not usually recommend it**: the copy has to suit all of them, and there is no unsend. It is also the worst case for a personalised push, because each template defines its own `dataFields` — most owners will fall through to your fallback. Reach for 2 first.\n\nLeave all five empty and the call is still accepted: the notification group is created, resolves to zero passes, and nobody hears anything."},{"name":"Location Based Notifications","description":"**The pass nudges its owner when they come near a place you configured on its template.** No send, no schedule, no API call at the moment it happens. You attach the place once, and the phone does the rest.\n\nThat is the real difference from a [push](#tag/push-notifications). A push is something **you** send, to an audience you choose, at a time you choose. A location message is something the **pass** does on its own, in a moment you cannot predict.\n\n**There is no endpoint for this feature.** Nothing to call, nothing to schedule, nothing to cancel. A location message exists because a `locations` array exists on the pass, and that array lives in exactly one of two places:\n\n| Where | Reaches | Use when |\n| --- | --- | --- |\n| `template.locations` | every pass issued from that template | the list is the same for everyone |\n| `templateOverrides.locations` | that one pass | each owner should get their own places |\n\nThe endpoints under `/api/v1/locations` are a separate thing: a registry of reusable coordinates, covered in step 3.\n\n### 1. Ten slots, and yours fill first\n\nApple keeps at most **10 locations per pass**, so the whole design is a question of which ten. Two rules apply, at two different layers, and mixing them up is the usual source of surprise.\n\n**Your override array replaces the stored one outright.** Send `templateOverrides.locations` on a create or update and that array *becomes* the pass's override list. It is not merged with whatever the pass had before. Omit the key and the stored list is kept untouched; send `[]` and it is cleared.\n\n**Then the override list and the template list are combined, yours first.** An override entry is matched against a template location by `id`, else `externalId`, else `order`. Where it matches, your values are layered on top of that location rather than wiping it, so `[{ \"externalId\": \"POS-042\", \"apple\": { \"lockScreenMessage\": \"...\" } }]` changes the wording for that one store and leaves its coordinates alone.\n\nThe ten slots are then filled in this order:\n\n1. your overrides that matched a template location\n2. your overrides that matched nothing\n3. whatever template locations are left over\n\n> **Send ten overrides and nothing from the template survives.** The slots are gone before the template list is reached. That is not an edge case, it is the normal outcome of giving an owner their own ten, and it is why the override so often behaves like a straight replacement.\n\n### 2. Why there are two levels at all\n\n**You have 80 stores. Apple will take 10.** That constraint is what the two levels exist to solve.\n\n**At template level, pick the best ten for everybody.** The flagship sites, the busiest, the ones any customer might walk past. One list to maintain, and every pass issued from the template carries it.\n\n**At pass level, do better than \"best ten\".** Once you know where an owner actually shops, put *their* ten nearest on *their* pass with `templateOverrides.locations`. Same template, same programme, but a Bondi customer carries the Bondi store and is never nudged about a Perth branch. This is how **one template serves an entire franchise**; see [Templates, passes and overrides](#description/templates-passes-and-overrides) for the wider pattern.\n\n### 3. Register coordinates once, then refer to them\n\nWithout the registry you repeat the same numbers into every template and every pass that mentions a store:\n\n```json\n\"locations\": [\n  { \"lat\": \"-33.8908\", \"lon\": \"151.2743\", \"apple\": { \"lockScreenMessage\": \"Bondi: your 10th is free\" } }\n]\n```\n\n`POST /api/v1/locations` stores the place against your business once, with `locationName`, `lat`, `lon` and optionally your own `externalId`:\n\n```json\n{ \"locationName\": \"Bondi store\", \"lat\": \"-33.8908\", \"lon\": \"151.2743\", \"externalId\": \"POS-042\" }\n```\n\nAfter that, an entry naming it is enough. Litecard looks up the coordinates for you:\n\n```json\n\"locations\": [{ \"externalId\": \"POS-042\", \"apple\": { \"lockScreenMessage\": \"Bondi: your 10th is free\" } }]\n```\n\n`GET`, `PUT` and `DELETE` on `/api/v1/locations/{locationId}` manage the record afterwards.\n\n**`externalId` is the id your own systems already use**, the one on your POS or store system. Set it and you never store a Litecard id anywhere: no mapping table, no lookup before an update, and a store that is re-onboarded keeps working because the key is yours, not ours. You can address the record by that name for the rest of its life:\n\n```\nPUT /api/v1/locations/external/POS-042\n```\n\nIt is settable **only at creation**. The update body does not accept it, which is deliberate: an identifier other systems key on would be worthless if it could drift.\n\n### 4. Coordinates are read when the pass is written, not when the owner walks past\n\n**Litecard resolves `id` and `externalId` into real coordinates at the moment a pass is created or updated, and stores the result on the pass.** Nothing reads the registry again afterwards.\n\nSo editing a location record moves nothing that already exists:\n\n| You do this | What happens |\n| --- | --- |\n| `PUT /api/v1/locations/{id}` with new coordinates | The record changes. **Every pass already issued keeps the old coordinates.** |\n| Update the template's `locations` | Litecard detects the change and pushes it to existing passes over the air |\n| Create or update a pass | That pass picks up the registry's coordinates as they are right then |\n\n**A store that moves therefore takes two steps:** correct the location record, then update the templates that use it so their passes are refreshed. The registry saves you from repeating coordinates; it does not push them.\n\n**Prefer updating a record over deleting it.** Templates and passes refer to locations by `id` and `externalId`, and a create or update naming a record you have deleted fails with `404 LOCATION_NOT_FOUND`. Keep the same id and change what is inside it.\n\n### 5. What the owner sees, and it is not the same on both\n\n![The same geofence on an Apple lock screen and a Google one](/api/v1.0/assets/location-notifications.svg)\n\n**Apple shows your words. Google shows one word of its own.** The same geofence, the same pass, two different notifications.\n\n| | Apple Wallet | Google Wallet |\n| --- | --- | --- |\n| **Message** | `apple.lockScreenMessage`, yours, different per store | **\"Nearby\".** Google writes it, and there is no field to put yours in |\n| **Left of the text** | The template's `images.icon` | Google Wallet's own icon, always |\n| **Right of the text** | A thumbnail of the pass, which Wallet renders itself | The template's `images.icon` |\n| **Name** | `template.businessName` | `template.businessName` |\n| **Locations kept** | 10 per pass | 10 per pass |\n| **Tapping it** | Opens the pass | Opens the pass |\n\nOnly the two icons and the name are yours to set. The Apple thumbnail is drawn from the pass artwork, so it follows whatever the template already looks like.\n\nSo the wording is worth doing, but it only ever pays off on Apple. On Google the geofence still fires and the pass still opens; the owner just sees your business name and the word \"Nearby\". Litecard sends Google the coordinates and nothing else, because `merchantLocations` carries no text field.\n\n`beaconId` is an alternative to coordinates, using a beacon's proximity UUID where a geofence is too coarse, such as one counter inside a shopping centre. Apple only. Samsung passes take `samsung.address` and `samsung.name` instead, and `order` controls the sequence when a pass carries several.\n\n### 6. How close is \"near\"?\n\n**Neither wallet lets you set the radius.** You register a point; each decides for itself what counts as nearby.\n\n![The same location with Apple's and Google's geofences drawn around it to scale](/api/v1.0/assets/location-radius.svg)\n\n**Apple: around 100 metres.** That is the figure for loyalty cards, coupons, store cards and gift cards. Ask for less and it may be ignored and treated as about 100 m anyway. The pass format has a `maxDistance` key, it can only ever narrow the radius rather than widen it, and Litecard does not set it.\n\n**Google: around 150 metres.** Google decides both how close the owner has to be and how long they have to stay there before anything fires, and publishes neither number.\n\nBoth figures are approximations and either can change. Two things follow. Register the coordinates of the **door**, not the car park. And never build behaviour that depends on the owner being an exact distance away."},{"name":"Notification Groups","description":"**Every send becomes a group, and the group is the record of it.** One row per send, whether you called [Send a notification](#tag/push-notifications) yourself, a [template notification](#tag/pass-template-notifications) fired on its own, or a recurring schedule came round again.\n\nTwo things you do with them: audit what went out, and stop something that has not gone out yet.\n\n### What a group tells you\n\n| Field | What it holds |\n| --- | --- |\n| `title`, `message` | The wording as sent, with `fallbackTitle` and `fallbackMessage` if placeholders were used |\n| `sendTime` | When it goes out, or went out |\n| `status` | `PENDING`, `QUEUED`, `IN_PROGRESS`, `SENT`, `FAILED` or `CANCELLED` |\n| `totalEligiblePasses` | How many **passes** the audience resolved to |\n| `sentVia` | One entry per platform, each with `count`, `successCount` and `failedCount` |\n| `recurringConfig`, `recurringCounter` | The repeat rule, and how many times it has run |\n\n### Why Apple + Google is more than totalEligiblePasses (push only, without email)\n\nThis is the question the history screen raises most often, and nothing is wrong when it happens.\n\n**`totalEligiblePasses` counts passes. `sentVia` counts sends.** A pass can be in an Apple wallet *and* a Google one at the same time, and the owner expects it to buzz on both phones. So that pass is one eligible pass and two push sends.\n\nCompare the **push** entries only. Leave any email entry out of the sum: email is a separate channel, not a wallet.\n\n```\n1,000 eligible passes\n  700 in Apple Wallet       apple  push count     700\n  327 in Google Wallet      google push count     327\n   27 in both                                    ----\n                            total push sends    1,027\n```\n\n**The gap is exactly the number of passes held in both wallets**, 27 here. Add the two push counts and you always get a number greater than or equal to `totalEligiblePasses`, never less.\n\nIf the send also went by email, `sentVia` carries an email entry too. That is one more send to the same person, so adding it in only widens the gap further. It says nothing about how many passes were reached.\n\n### Cancelling\n\n`PUT /api/v1/notification-groups/{notificationGroupId}/cancel` stops a group that has not gone out.\n\n**There is a ten-minute cutoff.** Once the send time is less than ten minutes away, the send is already in motion and cancelling is refused with `400 ALREADY_PROCESSED`. Cancel earlier than that, and there is no unsend afterwards."},{"name":"Pass Template Notifications","description":"Automated messages attached to a template, so every pass issued from it inherits them.\n\nUse these for lifecycle messaging that should apply to every owner: a welcome push once the pass is in the wallet, a reminder before it expires. For a one-off send to an audience you choose, use [Push Notifications](#tag/push-notifications) instead.\n\n### Three triggers, and what the delay counts from\n\nEach notification carries one `trigger`, a `delay` and a `unit`, and optionally a `scheduledTime` and `timezone`. The trigger decides which moment the delay is measured from, and therefore when the send is worked out at all.\n\n| Trigger | The delay counts from | Queued when |\n| --- | --- | --- |\n| `AFTER_CREATION` | the pass being created | the pass is created |\n| `AFTER_DOWNLOAD` | the pass landing in a wallet | the pass is first added to Apple or Google Wallet |\n| `BEFORE_EXPIRY` | the pass's expiry, counting backwards | the pass is created |\n\n**`AFTER_DOWNLOAD` is the one that waits for the owner.** A pass can sit uncollected for days: you create it, you send the link, and nothing happens until someone taps it. `AFTER_CREATION` starts counting anyway, so a \"welcome, here is how to use your pass\" can reach a phone that has no pass on it. `AFTER_DOWNLOAD` with `delay: 1` and `unit: \"HOURS\"` lands an hour after the pass is genuinely in the wallet.\n\n**It fires once, on the download that first activates the pass.** Adding the pass to a second phone does not queue another send, and neither does a wallet re-registering a device it already told us about, after an OS update or a restore.\n\nA send whose time has already passed by the time the trigger fires is dropped rather than sent late. So `AFTER_DOWNLOAD` with a negative or zero delay does nothing.\n\n### Editing one never reaches what is already queued\n\n**A pass takes its own copy the moment its trigger fires.** At that point Litecard works out the send time and copies the `title`, `message`, `dataFieldKey` and any fallbacks onto a send queued for that one pass. Nothing reads back from the template afterwards.\n\nSo editing or removing a notification changes what **future** passes inherit. Anything already queued fires exactly as it was.\n\nSay a template carries a reminder: `BEFORE_EXPIRY`, `delay: 7`, `unit: \"DAYS\"`, reading \"Your pass expires next week\".\n\n| When | What happens |\n| --- | --- |\n| **1 Jan** | Anna's pass is created, expiring 1 Mar. Her reminder is queued for **22 Feb**, with that wording. |\n| **1 Feb** | You edit the notification to read \"Renew now and keep your points\". |\n| **2 Feb** | Ben's pass is created, expiring 1 Apr. His reminder is queued for 25 Mar, with the **new** wording. |\n| **22 Feb** | Anna gets the **old** wording. Her send was settled on 1 Jan and the edit never touched it. |\n\nDelete it on 1 Feb instead and the same rule holds: Anna still gets her reminder on 22 Feb, and Ben never gets one at all.\n\n**`AFTER_DOWNLOAD` follows the same rule from a different moment.** Nothing is queued for a pass until someone downloads it, so an edit reaches every pass not yet downloaded, however long ago it was created. Delete one and any send already queued by a download that has happened still fires; **downloads from then on queue nothing at all.**\n\n**To change a send that is already queued, cancel it.** Each is recorded as a [notification group](#tag/notification-groups) and can be cancelled before it fires, then replaced with a one-off send."},{"name":"Scan","description":"**Redemption at the counter.** Scan the barcode on a pass, apply the actions that scan earns, and claim a reward once one is eligible.\n\n![How a scan becomes points, and points become a reward](/api/v1.0/assets/scan-redeem.svg)\n\n### Scan rules and business rules\n\nBoth are configured on the [template](#tag/pass-templates-crud), so every pass issued from it inherits them. What separates them is **what makes them fire**.\n\n| Compare | Scan rules | Business rules |\n| --- | --- | --- |\n| **When they fire** | on **every** scan, automatically | once a business condition is met **and** staff approve |\n| **Configured in** | `template.scanRules[]` | `template.businessRules[]` |\n| **Applied by** | `POST /api/v1/scans/actions` | `POST /api/v1/scans/business-rules/actions` |\n| **How many run** | every rule that is eligible | exactly one, the one you name |\n| **Chosen by** | Litecard, from the conditions | you, by `businessRuleId` |\n| **Types** | `TIER_UPDATE`, `STATUS_UPDATE`, `DATA_UPDATE` | `REWARD`, and nothing else |\n| **Counts as a scan** | yes, `numOfScans` goes up | **no**, and no scan is recorded |\n\nCounting the visit is a scan rule. Handing over the free drink is a business rule.\n\n#### Scan rules\n\nA scan rule runs by itself whenever `/scans/actions` is called and its conditions hold. Three types, differing in **which part of the pass they change**.\n\n| Type | Changes | Example |\n| --- | --- | --- |\n| `TIER_UPDATE` | `card.tier` | Upgrade the membership from `SILVER` to `GOLD` |\n| `STATUS_UPDATE` | `card.status` | Deactivate the membership after 100 visits |\n| `DATA_UPDATE` | a key under `card.dataFields` | Add 10 points on every visit |\n\n```json\n\"scanRules\": [\n  {\n    \"ruleId\": 1784703983001,\n    \"ruleName\": \"Upgrade to Gold after 20 visits\",\n    \"type\": \"TIER_UPDATE\",\n    \"conditions\": [\n      { \"field\": \"$.numOfScans\", \"operator\": \"GTE\", \"value\": 20 },\n      { \"field\": \"$.tier\", \"operator\": \"EQ\", \"value\": \"SILVER\" }\n    ],\n    \"action\": { \"operation\": \"SET\", \"field\": \"$.tier\", \"value\": \"GOLD\" }\n  },\n  {\n    \"ruleId\": 1784703983002,\n    \"ruleName\": \"Deactivate membership after 100 visits\",\n    \"type\": \"STATUS_UPDATE\",\n    \"conditions\": [\n      { \"field\": \"$.numOfScans\", \"operator\": \"GTE\", \"value\": 100 },\n      { \"field\": \"$.status\", \"operator\": \"EQ\", \"value\": \"ACTIVE\" }\n    ],\n    \"action\": { \"operation\": \"SET\", \"field\": \"$.status\", \"value\": \"INACTIVE\" }\n  },\n  {\n    \"ruleId\": 1784703983003,\n    \"ruleName\": \"Earn 10 points on each scan\",\n    \"type\": \"DATA_UPDATE\",\n    \"conditions\": [\n      { \"field\": \"$.numOfScans\", \"operator\": \"GTE\", \"value\": 0 },\n      { \"field\": \"$.status\", \"operator\": \"EQ\", \"value\": \"ACTIVE\" }\n    ],\n    \"action\": { \"operation\": \"INCREMENT\", \"field\": \"$.dataFields.points\", \"value\": 10 }\n  }\n]\n```\n\n**Every rule above carries a second condition, and that is the pattern to copy.** `conditions` is an AND: all of them must hold. The scan-count condition says *when* the rule becomes due, and the second one guards it. `$.tier EQ SILVER` stops the upgrade re-firing on a pass already at `GOLD`, and `$.status EQ ACTIVE` keeps a deactivated pass from earning anything.\n\nThat guard also keeps rules out of each other's way, which matters more than it looks. See **When two rules compete**, below.\n\nA `TIER_UPDATE` must name a tier that exists in `template.tiers`, and a `STATUS_UPDATE` a real pass status, or saving the template fails with `400 INVALID_TIER_IN_SCAN_RULE` or `400 INVALID_STATUS_IN_SCAN_RULE`.\n\n#### Business rules\n\nA business rule fires once the pass meets a business requirement you define. `REWARD` is the type to use for it: when a customer reaches the points threshold they become eligible for the reward, and claiming it deducts that reward's cost from their balance.\n\n```json\n{\n  \"ruleId\": 1784703999001,\n  \"ruleName\": \"Get 1 Free Drink after Buy 10\",\n  \"type\": \"REWARD\",\n  \"conditions\": [\n    { \"field\": \"$.dataFields.points\", \"operator\": \"GTE\", \"value\": 100 },\n    { \"field\": \"$.status\", \"operator\": \"EQ\", \"value\": \"ACTIVE\" }\n  ],\n  \"action\": { \"operation\": \"DECREMENT\", \"field\": \"$.dataFields.points\", \"value\": 100 }\n}\n```\n\n`ruleName` is required on a business rule, because it is the label your scanning app shows staff on the button they tap.\n\n**A business rule can also notify the customer afterwards.** Add a `postActions` entry and the push goes out with the pass update the rule applies, so the customer sees the new balance and the alert together:\n\n```json\n\"postActions\": [\n  {\n    \"type\": \"PUSH_NOTIFICATION\",\n    \"payload\": { \"title\": \"Enjoy 🧋\", \"message\": \"Your free drink is on us. Your card starts fresh today.\" }\n  }\n]\n```\n\n`PUSH_NOTIFICATION` is the only post-action type, and both `title` and `message` must be non-empty or the template is rejected with `400 INVALID_BUSINESS_RULE_POST_ACTION_PAYLOAD`. The push goes to that one pass and lands on the lock screen and on the back of the pass, the same as any [Push Notification](#tag/push-notifications). It fires **only on success**: a rejected or ineligible claim sends nothing. **One rule sends one push**: a rule that lists several `PUSH_NOTIFICATION` post-actions sends the first.\n\n#### Setting them up\n\nRules are part of the template document, not a separate resource. Send them as `scanRules` and `businessRules` on `POST /api/v1/template` when you create it, or on `PUT /api/v1/template` to change them later. See [Pass Templates CRUD](#tag/pass-templates-crud).\n\n```json\n{\n  \"templateId\": \"tmpl_koi_the\",\n  \"scanRules\": [ ... ],\n  \"businessRules\": [ ... ]\n}\n```\n\nBoth arrays are validated on save, so a malformed rule is refused at the template rather than failing silently at the counter. **Editing them takes effect immediately for every pass already issued**, because rules are read from the template at scan time rather than copied onto each pass. That is the opposite of how [template notifications](#tag/pass-template-notifications) behave.\n\nRules built in the dashboard are the same two arrays, so the two stay in step: a rule you post here appears there, and one built there is what this API applies.\n\n### How eligibility is decided\n\nEvery rule carries `conditions`, and **all of them must pass** for it to be eligible. A condition is a JSONPath into the pass, an operator, and a value:\n\n```json\n{ \"field\": \"$.dataFields.points\", \"operator\": \"GTE\", \"value\": 100 }\n```\n\n`$.numOfScans`, `$.status`, `$.tier` and any `$.dataFields.*` key are all addressable.\n\n**There are three operators, and they only point one way:** `GT`, `GTE` and `EQ`. There is no `LT`, `LTE` or \"not equal\", so a rule cannot be written to fire *below* a threshold. Express those as the absence of a rule, or as a tier you set on the way up.\n\nValues are compared numerically where they can be. A data field holding `\"90\"` matches `GTE 100` correctly once it reaches `\"100\"`, and the placeholder `\"-\"` counts as zero, which is what an untouched numeric field on a fresh pass usually holds.\n\n### When two rules compete\n\n**Rules that target the same field can suppress each other, and nothing warns you.**\n\nLitecard groups eligible rules by type, action field, and their non-scan conditions. Within a group **only one rule applies**: the one whose `$.numOfScans` threshold is *lowest*, with `EQ` beating `GTE` beating `GT` on a tie.\n\nLowest, not most recently passed. A `GTE` condition stays true forever once crossed, so an early rule keeps winning. Two milestones on `$.status` with nothing else to tell them apart:\n\n```\nSTATUS_UPDATE → INACTIVE  when $.numOfScans GTE 50\nSTATUS_UPDATE → DELETED   when $.numOfScans GTE 100\n```\n\nAt 100 scans both are eligible, they share a group, and the **lower threshold wins**. The pass goes `INACTIVE`, and it does so again at 150, and at 500. `DELETED` never fires at all.\n\n**The fix is the second condition.** Non-scan conditions are part of what defines a group, so a guard that differs puts each rule in a group of its own, and both survive:\n\n```\nSTATUS_UPDATE → INACTIVE  when $.numOfScans GTE 50  AND $.status EQ ACTIVE\nSTATUS_UPDATE → DELETED   when $.numOfScans GTE 100 AND $.status EQ INACTIVE\n```\n\nNow the two never contend. The pass goes `INACTIVE` on the scan that crosses 50, which makes the second rule's guard true, and `DELETED` follows on the next scan past 100. The guard doubles as a self-limit: neither rule can re-fire on a pass already in that state.\n\nThis is the same pattern as the examples above, and the reason to write every rule with two conditions rather than one.\n\n**The same grouping absorbs bonuses.** Add \"double points on the tenth visit\" as a second `DATA_UPDATE` on `$.dataFields.points` and, with no distinguishing guard, it joins the base earn rule's group. The base rule wins on its lower threshold and **the bonus never fires**.\n\nTo stack an outcome rather than replace one, separate the two. Give it a different `action.field`, accruing the bonus into its own data field, or a guard condition the other rule does not share."},{"name":"Voucher Codes","description":"**Upload a list, and let Litecard hand entries out one at a time.** Use it when the codes are minted by a system you do not control, such as a retailer's gift-card platform, or when only certain people may sign up at all.\n\nEverything here is CSV in, one entry per person out. There are **three upload types**, and they do different jobs.\n\n![The three voucher upload types and the CSV each one expects](/api/v1.0/assets/voucher-uploads.svg)\n\n### Pick the type, then match the header row\n\nThe `type` you send on the upload has to line up with how the template's field is mapped. Get the pairing wrong and the file uploads fine and then does nothing useful.\n\n| `type` | Template field mapping | CSV header | What it is for |\n| --- | --- | --- | --- |\n| `VOUCHER` | `VOUCHER_CODE` | `code` | A pool of codes, one handed to each pass |\n| `EMAIL` | `VALID_EMAIL` | `email` | A guest list: only these addresses may sign up |\n| `EMAIL_MEMBER` | `VALID_EMAIL_MAPPING_CODE` | `email,voucherCode` | A specific code reserved for a specific person |\n\n**The header row is the whole validation.** A `VOUCHER` file needs a `code` column, an `EMAIL` file needs `email`, and an `EMAIL_MEMBER` file needs **both** `email` and `voucherCode`. Miss one and the job is recorded as `INVALID_UPLOAD` with no rows written at all. Extra columns are kept and stored alongside the entry, so you can carry your own reference through.\n\n### What each type does at signup\n\n**`VOUCHER` behaves two different ways**, and which one you get is decided by the template, not by the upload. If the field's `formFieldSettings.usage` includes `EXTERNAL`, Litecard picks an unused code **at random** from the pool and writes it onto the pass. If it does not, the owner types a code themselves and it is checked against the pool. Run the pool dry on the random path and the call fails with `404 NO_VOUCHER_CODE`.\n\n**`EMAIL` issues no code at all.** The list is a gate. An address that is not on it is refused with `400 INVALID_EMAIL_FOR_TEMPLATE`, and one that has already signed up with `400 ALREADY_SIGNED_UP`. Addresses are lower-cased on upload, and anything that does not parse as an email is stored as `INVALID` rather than rejecting the file.\n\n**`EMAIL_MEMBER` removes the randomness.** The address is looked up and gets exactly the code you paired with it. Reach for it when the code has to be predictable: a membership number, a per-person entitlement, anything you have already told the customer.\n\n### Once means once\n\n**Every entry is usable once.** Taking one marks it `USED`, and a used entry is never handed out again.\n\n**Re-uploading does not reset anything.** Each row is written only if that code or address does not already exist for the template. If it does, the row is counted as a duplicate and discarded, whether the existing entry is used or not. There is no way through the API to put a used entry back into circulation, and uploading the same file twice is therefore safe rather than destructive.\n\n`GET /api/v1/voucher/codes/upload/groups/{templateId}` lists your uploads with their processing state, and `GET /api/v1/voucher/codes/counts/{templateId}` tells you how many are left. Check the upload landed before you start issuing passes against it: processing is asynchronous, so a job can still be `IN_PROGRESS` when the upload call has already returned.\n\n**One template can hold several pools.** When a template has more than one voucher field, `key` says which `dataField` the file belongs to, and it is **required**: leave it out and the upload is refused with `400 KEY_REQUIRED`. Name a field that does not exist and you get `400 INVALID_KEY`, listing the keys that do."},{"name":"Pass Uploads","description":"**Issue thousands of passes from one CSV.** What to reach for instead of looping [`POST /api/v1/card`](#tag/pass/POST/api/v1/card).\n\n### What a row is allowed to fill\n\n**Two groups, and nothing else.**\n\n**1. Template fields with `userCanEdit: true`.** A bulk upload can fill exactly the fields a customer could have filled in themselves on a signup form. Litecard builds that list by keeping the `template.dataFields` entries with `userCanEdit: true`.\n\nA field with `userCanEdit: false` belongs to the template rather than to the pass. Every pass shows the same value, so there is nothing for a row to say about it. It cannot be mapped, and a mapping that names one does nothing.\n\n**2. Three dedicated keys.** These are not template fields. They exist for uploads, and they are available whatever your template's `dataFields` look like.\n\n| Key | Fills | Notes |\n| --- | --- | --- |\n| `$externalId` | Your own id for the pass | Stored on the pass itself, not among its data fields |\n| `$thumbnailImage` | A per-pass image | **Membership templates only.** Rejected on any other type |\n| `$tier` | The pass's tier | **Multi-tier templates only**, and it is never mapped. See below |\n\n![What a CSV row can fill, and how headerMappings joins it to the template](/api/v1.0/assets/bulk-upload-mapping.svg)\n\n### Deriving headerMappings\n\n`headerMappings` is the join between the two sides. **The key is the template key, or one of the dedicated `$` keys. The value is the column header in your file.**\n\n```json\n{\n  \"templateId\": \"tmpl_coffee\",\n  \"fileName\": \"members.csv\",\n  \"headerMappings\": {\n    \"firstName\": \"First Name\",\n    \"email\": \"Email Address\",\n    \"points\": \"Balance\",\n    \"$externalId\": \"Ref\",\n    \"$thumbnailImage\": \"Photo URL\"\n  }\n}\n```\n\nTo build it, list your template's `userCanEdit: true` keys, add whichever `$` keys you need, then pair each with the column that holds it.\n\n- **Your columns keep their own names.** Nothing has to be renamed to match us.\n- **Column order does not matter**, and a column no mapping names is read past. It breaks nothing.\n- **A key you leave out is not filled**, silently. On a create, a required field missing that way fails the row; an optional one just stays empty.\n\n**`$tier` is the exception: it is never mapped.** Head the column exactly `$tier` and Litecard reads it by that name. The value has to match a tier in `template.tiers` or the row fails validation.\n\n### Create or update: `overrideExisting` decides\n\nLitecard looks for an existing owner **by email first, then by phone**, then for an active pass of that template belonging to them.\n\n| Row matches an existing pass | `overrideExisting` | Outcome |\n| --- | --- | --- |\n| no | either | A new pass is created |\n| yes | `true` | That pass is **updated** |\n| yes | `false` (the default) | The row is **skipped**, and says so in the results |\n\n**The default skips rather than duplicates**, which makes re-running a file safe. A run reporting mostly skipped rows means the audience already holds passes, not that anything failed.\n\n**Updates are more forgiving than creates.** Required fields are enforced only when creating. On an update a blank cell leaves the existing value alone rather than failing the row, so you can upload a file carrying only the columns you want to change. A create also needs an email or a phone, unless the template has `noPI` set.\n\n### The other flags\n\nAll optional, sent alongside `headerMappings`:\n\n| Flag | Default | What it does |\n| --- | --- | --- |\n| `overrideExisting` | `false` | Update a matched pass instead of skipping the row |\n| `emailInvitationEnabled` | `true` | Email each owner their pass link |\n| `smsInvitationEnabled` | `false` | Text each owner their pass link |\n| `fastCreate` | `false` | Trade lifecycle messaging for speed, see below |\n| `returnFile` | `false` | **Deprecated.** Left alone |\n\n**`fastCreate` has a real cost.** It skips scheduling the template's notifications, so a pass created this way gets no `AFTER_CREATION` and no `BEFORE_EXPIRY` message. If the template relies on a welcome push or an expiry reminder, leave it off.\n\n**`returnFile` is deprecated and does nothing on this path.** It used to push the results CSV to a business's SFTP server, and only the older upload pipeline still acts on it. Do not send it on new integrations.\n\nInvitation settings on the template still apply. These flags override them for this upload only.\n\n### How big a file, and what to expect while it runs\n\n**Up to 500,000 rows in one file, and the limit is enforced.** Go over it and the whole group stops at `RECORD_COUNT_EXCEEDED` with nothing processed, so a file that is one row too long costs you the entire run.\n\n**We recommend keeping it to 100,000.** Past that a single run takes long enough that a problem in the data is expensive to discover, and splitting gives you a checkpoint between batches.\n\n**Nothing is synchronous.** The `PUT` finishes when the file has landed, not when the passes exist, and validation happens afterwards. Rows are processed in batches of 10,000.\n\nA row that fails validation does not stop the others. Each is recorded with its own status and its own errors, which is the reason to use this over a loop: the row listing tells you exactly which customers to fix and re-send.\n\nCheck the group before you send anyone a link."},{"name":"CSV","description":"**Get your data out as a CSV.** Passes, scans and notification history, on demand.\n\n### Pick a table\n\n`tableName` decides what you get. It names the screen the data sits behind rather than a database table.\n\n| `tableName` | Exports |\n| --- | --- |\n| `cardsTable` | Passes, one row each |\n| `scansTable` | Scan history |\n| `notificationGroupsTable` | Notification sends, one row per group |\n| `notificationsTable` | Individual notifications |\n\n### Two shapes of answer, and the table decides which\n\n**This is the part worth knowing before you write the client.** Some exports come back with the file, and some hand you an id to collect later.\n\n| `tableName` | Response | What you get |\n| --- | --- | --- |\n| `cardsTable`, `notificationsTable` | `202` | An `exportId` to poll. These are the big ones |\n| `scansTable`, `notificationGroupsTable` | `200` | A `signedUrl` immediately |\n\nSo a client that only ever reads `signedUrl` will silently do nothing for a passes export, and one that only ever polls will wait forever for a scans export. **Branch on the status code.**\n\n### Filters\n\nAll optional, and all narrow the same export:\n\n- `templateId` limits it to one template's passes.\n- `statusList` filters on pass status: `ACTIVE`, `INACTIVE`, `DELETED`, `AVAILABLE`, `EXPIRED`.\n- `appleWalletStatus` and `googleWalletStatus` filter on where the pass actually is. `ACTIVE` means downloaded and still in the wallet, `INACTIVE` downloaded then removed, `NOT_DOWNLOADED` never added at all, and `ALL` does not filter.\n- `startDateTime` and `endDateTime` bound the range.\n\n**Dates come as a pair or not at all.** Send one without the other and the call fails with `400 BAD_REQUEST` naming the one you left out. Both must be ISO 8601, and the start must not be after the end.\n\n**`NOT_DOWNLOADED` is the one to reach for** when you want to know who never downloaded their pass. It is the audience an invitation reminder should go to.\n\n### Which columns you get\n\n**The same rule as an upload: the columns are the `userCanEdit` fields.** Litecard reads `template.dataFields`, keeps the entries with `userCanEdit: true`, and adds the standard pass columns. Anything with `userCanEdit: false` is the same on every pass, so there is nothing per-row to export.\n\nFields marked `isSecure` are left out entirely, on the template and on the form.\n\nThat symmetry is deliberate: an export of one template is close to a file you could feed back to [Pass Uploads](#tag/pass-uploads), and the two agree on what belongs to a pass."},{"name":"OAuth Connections","description":"**Authorise Litecard against a provider once, then point forms at it.** A connection is the stored permission, held for the whole business.\n\n> **Most people should never call these endpoints.** The dashboard runs the whole flow, and that is the route we recommend. Come back here only if you are building your own admin screen.\n\n### Do it in the dashboard instead\n\n**Start on View Pass Templates.** Each template's row has an **integrations icon** in the Actions column. That is the one to click.\n\n![The integrations icon in the Actions column of View Pass Templates](/api/v1.0/assets/dashboard-integrations-icon.svg)\n\n**That opens Third Party Integrations for that template.** Pick a provider on the left, and press **Authorize**.\n\n![The Third Party Integrations screen, with Klaviyo selected and the Authorize button beside a connection reading Not connected](/api/v1.0/assets/dashboard-authorize.svg)\n\nThe **Connection** panel tells you where you stand: *Not connected* before you start, and the provider's account once you are through. **Manage integrations** underneath lists every connection the business already holds, which is where you go to reuse one rather than authorising again.\n\nEverything the rest of this section describes happens behind that one button.\n\n### What OAuth is actually doing\n\n**You are not handing Litecard a password, and you are not copying an API key.** You send an administrator of your provider account to the provider itself. They approve a named list of permissions there, and the provider hands Litecard a token that stands for that approval. The token can be withdrawn at their end at any time, and it never reveals the password.\n\nKlaviyo is the clearest example, and it is exactly what the dashboard shows:\n\n1. You press **Authorize** on the Third Party Integrations page.\n2. Litecard sends you to Klaviyo's own consent screen.\n3. You sign in to Klaviyo if you are not already, and approve the permissions it lists.\n4. Klaviyo sends you back to Litecard, and the connection reads as connected.\n\nNothing is pasted anywhere. There is no key to rotate by hand and no shared secret sitting in someone's notes.\n\n### The same flow through the API\n\nTwo endpoints, and the split matters: **one is for your code, the other is for a browser.**\n\n1. `GET /api/v1/integrations/{provider}/oauth/start` returns `{ authorizeUrl }` as **JSON, not a redirect**. Your own code sends the browser there.\n2. The provider returns the person to `/api/v1/integrations/{provider}/oauth/callback`. **That one answers with a redirect and never JSON**, because a browser is what lands on it. Litecard exchanges the code, stores the connection, and sends the person back where they started with `connected=<provider>` on the URL, or `error=...` if it failed.\n\nPass `formId` on the start call to connect and attach in one go. Leave it out for an account-level connection you attach to forms afterwards.\n\n### Reconnecting does not duplicate\n\nStart the flow again for a business that already has an active connection and Litecard **re-authorises that connection** rather than minting a second one, so the id your forms point at survives. You only get a new one when the original has been disconnected.\n\n### Connection health is stored, not checked live\n\n`GET /api/v1/integrations/{provider}/connections` reports what Litecard last recorded. It does not call the provider to confirm anything.\n\n**So a grant revoked at the provider's end still reads `ACTIVE` here**, until a token refresh fails and notices. Treat the listing as a way to spot a connection we already know is broken, not as proof that one works. The first real symptom of a revoked grant is usually a form quietly failing to sync.\n\n### Attaching, and disconnecting\n\nA connection on its own syncs nothing. `POST /api/v1/integrations/{provider}/connections/{connectionId}/attach` points a form at one with **no second trip to the provider**, which is why a single Klaviyo authorisation can serve every form you run. What each form then does with it is configured in [Form Integrations](#tag/form-integrations).\n\nDisconnecting deletes the stored permission, and any form still attached stops syncing. Detach or re-point those first."},{"name":"Form Integrations","description":"**Decide what a signup form actually sends, and to whom.** [OAuth Connections](#tag/oauth-connections) is where you authorise a provider. This is where you say what flows.\n\n> **Start in the dashboard, not here.** This is the most intricate corner of the API: five providers, each with its own configuration shape, and a field-mapping step that only makes sense next to the provider's own field list. The dashboard renders all of that for you.\n\n### Getting to the page\n\nSame route as before. On **View Pass Templates**, click the **integrations icon** in the Actions column of the template you want.\n\n![The integrations icon in the Actions column of View Pass Templates](/api/v1.0/assets/dashboard-integrations-icon.svg)\n\n**Third Party Integrations** opens for that template. Choose a provider on the left and the right-hand pane becomes that provider's configuration.\n\n![The Third Party Integrations screen, with Klaviyo selected and its configuration alongside](/api/v1.0/assets/dashboard-authorize.svg)\n\nThe page names the direction under the provider you pick, \"Litecard ⇄ Klaviyo, data flows both ways\", so you can see at a glance whether a provider sends, receives, or both.\n\n**If the mapping is not obvious, ask us.** Some of these have provider-side setup that has to line up with ours, and getting it wrong is quiet rather than loud. The Litecard team would rather configure it with you once than have you debug a sync that silently does nothing.\n\n### What each provider is for\n\nSix are supported, and they do genuinely different jobs.\n\n| Provider | Direction | What it is for |\n| --- | --- | --- |\n| `KLAVIYO` | both ways | Marketing. Pass activity becomes profile properties, list membership and events; Klaviyo's flows can add people back |\n| `DOTDIGITAL` | outbound | Marketing email against a Dotdigital account |\n| `TALONONE` | both ways | Promotions and loyalty. Events reach it through **Talon.One's own event API**, not a plain webhook |\n| `LC_INBOUND_LISTENER` | inbound | **The general-purpose door in.** See below |\n| `SQUARE` | both ways | Square point of sale |\n| `BLOOMREACH` | outbound | Customer engagement. Pass events reach it through **Bloomreach's own add-event API**, keyed on the pass's `externalId` |\n\n**`LC_INBOUND_LISTENER` is the one to reach for when nothing else fits.** It lets any external system create or update a pass by posting a flat payload with an API key, without that system knowing anything about Litecard's own API.\n\nA payload names the pass, carries whatever fields you mapped, and can push a notification in the same call:\n\n| Reserved key | Does |\n| --- | --- |\n| `externalId` or `cardId` | Names the pass to create or update |\n| `$status` | Sets it `ACTIVE`, `INACTIVE` or `DELETED` |\n| `$tier` | Moves it between tiers |\n| `$cardExpiry` | Sets when the pass expires, overriding the template's rule |\n| `$pnTitle`, `$pnMessage` | Sends a push with the change, as the pass updates |\n| `$pnSendAt`, `$pnExpiresAt` | Holds that push until a time you pick, and expires it |\n\n**`$cardExpiry` takes an ISO 8601 date time** (`2026-12-31T23:59:59Z`) and fixes that pass's expiry to it, whatever rule the template carries. It holds until you send another one. A date already past expires the pass now, which is how you retire one; a date Litecard cannot read is ignored and the rest of the update still applies.\n\n**Without `$pnSendAt`, the push goes out with the update itself**, not on the next sweep. Give it a time and the push waits for it instead, so a time that has already passed is refused rather than sent late.\n\n**A bad notification does not lose the update.** If the push content is malformed, the card change still goes through and only the notification is dropped.\n\n### The parts you configure\n\nWhichever provider, an entry on a form carries the same few things:\n\n- `enabled` is the only off switch. A connection is either connected or removed; **this is what turns one form's sync off** without touching anything else.\n- `eventType` labels the direction as `RECEIVE`, `SEND` or `BOTH`. Nothing routes on it at runtime, but the API-key providers check that the record claims an inbound direction before they will accept anything.\n- `overridesLitecardInvitation` hands delivery of the pass invite to the provider. Set it and Litecard sends **neither** its invitation email nor its SMS, on the assumption the provider's own flow does it.\n- Merge fields line your pass fields up with theirs. Klaviyo uses a richer per-field contract instead, which is the part the dashboard is most worth using for.\n\n**Validate before you trust it.** `POST /api/v1/integrations/{provider}/validate` checks the configuration against the provider rather than against our schema, so it catches what a schema cannot: a list that has been deleted, a field that no longer exists, a credential that lapsed since you attached it.\n\nSome providers need onboarding beyond the credential, such as installing default flows. `POST /api/v1/integrations/{provider}/setup` runs it, and what it does is provider-specific."},{"name":"Outbound Webhooks","description":"**Litecard calls you when something happens to a pass.** Subscribe once and every event lands at your endpoint.\n\n### Why this matters more than it looks\n\n**Creating a pass tells you it exists. Nothing tells you anyone has downloaded it.**\n\n[`POST /api/v1/card`](#tag/pass/POST/api/v1/card) returns a `downloadId`, which addresses the page you send the owner, and `appleLink` and `googleLink`, the direct Apple and Google URLs behind it. All three exist the moment the pass is created, whether or not anybody opens them.\n\n**That is why your system can own the invitation.** You have the link at creation, so you can send it however you like, from your own CRM or messaging platform. What you do not have, without webhooks, is any idea what happened next:\n\n| Event | Tells you |\n| --- | --- |\n| `CARD_CREATE` | The pass exists and its links are ready to send |\n| `CARD_DOWNLOAD` | It is **actually in a wallet**. Stop chasing this person |\n| `CARD_REMOVE` | They deleted it from their wallet |\n| `CARD_UPDATE` | A field on the pass changed |\n\nSo the common shape is: create the pass, send the link yourself, then let `CARD_DOWNLOAD` tell you who has downloaded it. Without that you are guessing, and chasing people who added their pass days ago.\n\n### When the pass is created for you, webhooks are the only way back\n\nEverything above assumes you called [`POST /api/v1/card`](#tag/pass/POST/api/v1/card) yourself and read the response. **An external system posting to the inbound listener has no such response.**\n\n[`POST /api/v1/external/callback/{provider}/{businessIdPath}/{templateId}`](#tag/process-events-from-an-external-system/POST/api/v1/external/callback/{provider}/{businessIdPath}/{templateId}), and the older [`POST /api/v1/external/callback`](#tag/process-events-from-an-external-system/POST/api/v1/external/callback), do not create the pass while you wait. They validate the request, store it as `PENDING` and put it on a queue, then answer `200` immediately. The pass is written afterwards, so **the response carries no `cardId`**, and there is no id to look anything up with.\n\n**That is what `externalId` is for.** Send your own identifier with the request. Litecard keeps it on the pass, and every webhook then carries both identifiers, so your record and ours are tied together from the first event onwards.\n\n```\nyour system ──POST──▶ inbound listener       200 accepted, queued. No cardId.\n                            │\n                            ▼\n                      queue, then write the pass\n                            │\n            ◀──CARD_CREATE──┘                externalId + cardId + the data you sent\nyour system\n     │\n     └──GET /api/v1/card/{cardId}──▶         status, expiry, tier, barcode, links\n```\n\nSo the sequence is:\n\n1. **You post to the listener** with your `externalId`. You get `200` and nothing else.\n2. **`CARD_CREATE` arrives** once the pass exists. It carries your `externalId`, Litecard's `cardId`, and `data`, the `cardPayload` as you sent it. Store the `cardId` against your record here.\n3. **Anything more comes from [`GET /api/v1/card/{cardId}`](#tag/pass/GET/api/v1/card/{cardId}).** The event is deliberately small; the pass endpoint has the rest.\n4. **`CARD_UPDATE` follows the same shape** for later changes, carrying only the fields that changed.\n\n**Without a webhook subscription that pass is invisible to you.** It exists, it may already be in someone's wallet, and you have no id for it. This is the case where webhooks are not an improvement on polling but the only route back.\n\n### What arrives\n\n**The two kinds of event carry different data.** A create or update knows what you sent and which template it came from. A download or remove is reported by the wallet itself, so it knows only which pass moved and onto what.\n\n**`CARD_CREATE` and `CARD_UPDATE`:**\n\n```json\n{\n  \"event\": \"CARD_CREATE\",\n  \"cardId\": \"8f2c1a...\",\n  \"timestamp\": \"2026-09-04T09:12:44.031Z\",\n  \"data\": { \"firstName\": \"Ana\", \"email\": \"ana@acme.com\", \"points\": \"120\" },\n  \"templateId\": \"tmpl_coffee\",\n  \"templateName\": \"Coffee Club\",\n  \"externalId\": \"CRM-8841\"\n}\n```\n\n`data` is the `cardPayload` exactly as you sent it, so an update carries the fields that changed rather than the whole pass.\n\n**`CARD_DOWNLOAD` and `CARD_REMOVE`:**\n\n```json\n{\n  \"event\": \"CARD_DOWNLOAD\",\n  \"cardId\": \"8f2c1a...\",\n  \"timestamp\": \"2026-09-04T09:14:02.884Z\",\n  \"deviceType\": \"APPLE\",\n  \"status\": \"ACTIVE\",\n  \"externalId\": \"CRM-8841\"\n}\n```\n\nField by field:\n\n| Field | On | Notes |\n| --- | --- | --- |\n| `event`, `cardId`, `timestamp` | all four | |\n| `data` | create, update | The pass field values you sent |\n| `templateId` | create, update | |\n| `templateName` | **create only** | An update carries the id without the name |\n| `deviceType` | download, remove | `APPLE` or `GOOGLE` |\n| `status` | download, remove | `ACTIVE` on a download, `INACTIVE` on a remove |\n| `externalId` | all four, when the pass has one | |\n\n**`deviceType` is per wallet, so one pass can produce two downloads.** Someone who adds the same pass to Apple Wallet and Google Wallet triggers `CARD_DOWNLOAD` twice, once with each value. Treat \"has downloaded\" as a flag you set, not a count.\n\n### Authentication\n\n**This is how Litecard proves itself to you, not how you prove yourself to us.** You are the server here: Litecard is calling your endpoint, so the credential you give when you [register the webhook](#tag/outbound-webhooks/POST/api/v1/webhook/external) is the one Litecard presents on every delivery, and your endpoint checks it before acting on the body.\n\nGive an `API_KEY`, for example, and every request Litecard makes carries that key in the header you named. Your handler compares it and rejects anything that does not match, which is what stops somebody else posting invented events at your URL.\n\nThree mechanisms are supported, and each suits a different kind of endpoint.\n\n**`API_KEY` sends a header.** You name the header and give the key; Litecard stores the key out of reach and sets that header on every delivery.\n\n```json\n{\n  \"webhookUrl\": \"https://example.com/hooks/litecard\",\n  \"method\": \"POST\",\n  \"authType\": \"API_KEY\",\n  \"authConfig\": { \"securityHeader\": \"X-API-KEY\", \"apiKey\": \"sk_live_...\" },\n  \"events\": [\"REGISTER_WEBHOOK\", \"CARD_CREATE\", \"CARD_DOWNLOAD\", \"CARD_REMOVE\"]\n}\n```\n\n**`NONE` sends nothing, which is what you want when the credential is already in the URL.** There is no query-parameter mode, so put the key in `webhookUrl` yourself.\n\n```json\n{\n  \"webhookUrl\": \"https://example.com/hooks/litecard?key=abc123\",\n  \"method\": \"POST\",\n  \"authType\": \"NONE\",\n  \"events\": [\"REGISTER_WEBHOOK\", \"CARD_CREATE\", \"CARD_DOWNLOAD\", \"CARD_REMOVE\"]\n}\n```\n\n**`TOKEN` is the username and password option**, but it is not HTTP Basic. Litecard posts your credentials to `tokenEndpoint`, takes the token that comes back, and sends that on every delivery in the header you name, defaulting to `Authorization`.\n\n```json\n{\n  \"webhookUrl\": \"https://example.com/hooks/litecard\",\n  \"method\": \"POST\",\n  \"authType\": \"TOKEN\",\n  \"authConfig\": {\n    \"tokenEndpoint\": \"https://example.com/oauth/token\",\n    \"tokenUserName\": \"litecard\",\n    \"tokenPassword\": \"...\",\n    \"securityHeader\": \"Authorization\"\n  },\n  \"events\": [\"REGISTER_WEBHOOK\", \"CARD_CREATE\", \"CARD_DOWNLOAD\", \"CARD_REMOVE\"]\n}\n```\n\n### Scoping and retries\n\n**`templateIds` narrows the subscription.** Omit it and you receive events for every template in the business. The ids are checked at registration, because an unknown one would leave a webhook that registers cleanly and then never fires.\n\n**Deliveries retry, so your handler must be idempotent.** Expect the same event more than once, key on `cardId` and `event`, and make a repeat a no-op. Return quickly and do the work afterwards: a slow response becomes a retry, then a duplicate.\n\n### Webhooks a provider sets up for you\n\nSome integrations need a webhook that talks the provider's own API rather than posting our payload. **Dotdigital, Talon.One and Bloomreach are set up through the Third Party Integrations page**, not registered here, because they need credentials and field mapping this request body has no room for.\n\nSame route as any other integration. On **View Pass Templates**, click the **integrations icon** in the Actions column of the template:\n\n![The integrations icon in the Actions column of View Pass Templates](/api/v1.0/assets/dashboard-integrations-icon.svg)\n\nThen pick the provider and configure it there:\n\n![The Third Party Integrations screen for one template](/api/v1.0/assets/dashboard-authorize.svg)\n\nSee [Form Integrations](#tag/form-integrations) for what each provider does with the events.\n\n### REGISTER_WEBHOOK\n\n**List it in `events` with the card events**, as the examples above do. That is the convention.\n\n**It is a connection test, not an event you receive.** Litecard posts it once, during registration, to prove the URL and credentials work. Only the four card events are delivered afterwards.\n\nThe handshake is sent whether or not you list it, and nothing later ever carries it.\n\nThe body is placeholders, not a pass. Answer `200`, `201` or `202` and ignore it; anything else fails the registration, so **your endpoint has to be running before you register**."},{"name":"Subscribe to an External System","description":"**Have a provider tell Litecard when a customer changes, and issue or update a pass from it.** The opposite direction to [Outbound Webhooks](#tag/outbound-webhooks): there, Litecard calls you. Here, a provider calls Litecard, and a pass changes with no code of yours in between.\n\n**Shopify is the only provider today.** A customer created in your store becomes a pass, and a customer edited in your store updates it.\n\n### Install the app first\n\nThis builds on a Shopify app that is already installed. Registration reads the store's access token from the connection that installation created, and fails with `404 SHOPIFY_STORE_NOT_FOUND` if there is none. **Install the Litecard app on the store, then register.**\n\n### What registering does\n\nOne call creates the subscriptions **inside Shopify**, on your behalf, through their Admin API. Litecard builds the callback URL itself and hands it to Shopify, so there is no URL for you to supply or host:\n\n```\nPOST /api/v1/inbound-webhooks\n{ \"provider\": \"SHOPIFY\",\n  \"templateId\": \"tmpl_loyalty\",\n  \"topics\": [\"CUSTOMER_CREATE\", \"CUSTOMER_UPDATE\"],\n  \"config\": { \"storeName\": \"acme-store\" } }\n```\n\nThe three topics map onto Shopify's own:\n\n| Topic | Shopify topic | What Litecard does with it |\n| --- | --- | --- |\n| `CUSTOMER_CREATE` | `CUSTOMERS_CREATE` | Issues a pass from that template |\n| `CUSTOMER_UPDATE` | `CUSTOMERS_UPDATE` | Updates the matching pass |\n| `CUSTOMER_DELETE` | `CUSTOMERS_DELETE` | Marks the pass `DELETED` |\n\n**`templateId` is part of the callback URL**, so one subscription feeds one template. Registering for a second template creates a second set of Shopify subscriptions.\n\n`storeName` is the myshopify subdomain. Leave it out and Litecard uses the store already on the form's `shopifySettings`; with neither you get `400 MISSING_STORE_NAME`.\n\n### metafieldNamespaces, and why it cannot wait\n\n**A Shopify webhook carries no metafields unless the subscription asked for them at creation.** If any pass field comes from a metafield, name its namespace here:\n\n```json\n\"config\": { \"storeName\": \"acme-store\", \"metafieldNamespaces\": [\"loyalty\"] }\n```\n\n**It is fixed when the subscription is created.** Changing your mind means deleting the subscription and registering again. Litecard rejects a malformed value outright with `400 INVALID_METAFIELD_NAMESPACES` rather than accepting it, because a subscription created without namespaces looks healthy and delivers nothing.\n\n### Managing them\n\n`GET` lists what is registered, with the Shopify subscription id behind each. `DELETE` removes the subscription from Shopify as well as from Litecard, so deliveries stop at the source."},{"name":"Process Events from an External System","description":"**Send your events here and Litecard turns them into passes.** One endpoint, and the path says who is calling and what to build:\n\n```\nPOST /api/v1/external/callback/{provider}/{businessId}/{templateId}\n```\n\n**Every provider authenticates with an API key**, so forwarding events is a matter of pointing your system at the URL and setting a header. Nothing has to be installed on your side.\n\n### Start with the inbound listener\n\n`LC_INBOUND_LISTENER` is the simplest, and the one to reach for unless you are already using one of the platforms below. **It takes flat JSON of your own shape**, and you tell Litecard what each key means.\n\nThat mapping is configured in the dashboard, not in the payload. Open **View Pass Templates**, click the **integrations icon** in the Actions column of the template you want, and configure the listener there.\n\n![The inbound listener configuration: listener endpoint, API key, and each pass field paired with the event key it is read from](/api/v1.0/assets/dashboard-inbound-listener.svg)\n\nThe page pairs every Litecard pass field with **the key it should be read from in your event**, so your system keeps its own field names. It also shows the exact listener URL and a ready-made `curl`, which is usually all an integration needs.\n\n**A minimal event, once the mapping is set:**\n\n```json\n{ \"externalId\": \"CRM-8841\", \"email\": \"ana@acme.com\", \"firstName\": \"Ana\", \"segment1\": \"GOLD-2026\" }\n```\n\nThe first event for an `externalId` creates a pass; later ones update that pass, and only the fields you send. A handful of reserved keys do more than fill a field:\n\n| Key | Does |\n| --- | --- |\n| `externalId` or `cardId` | Names the pass. `externalId` is your own reference, `cardId` is ours |\n| `$status` | Sets it `ACTIVE`, `INACTIVE` or `DELETED` |\n| `$tier` | Moves it between tiers, on a multi-tier template |\n| `$cardExpiry` | Sets when the pass expires, overriding the template's rule |\n| `$pnTitle`, `$pnMessage` | Sends a push with the change, as the pass updates |\n| `$pnSendAt`, `$pnExpiresAt` | Holds that push until a time you pick, and expires it |\n\n**`$cardExpiry` takes an ISO 8601 date time** (`2026-12-31T23:59:59Z`) and fixes that pass's expiry to it, whatever rule the template carries. It holds until you send another one. A date already past expires the pass now, which is how you retire one; a date Litecard cannot read is ignored and the rest of the update still applies.\n\n**Without `$pnSendAt`, the push goes out with the update itself**, not on the next sweep. Give it a time and the push waits for it instead, so a time that has already passed is refused rather than sent late.\n\n**A bad push does not lose the update.** If the notification content is malformed, the pass change still goes through and only the push is dropped.\n\n### Providers with a shape of their own\n\nThe rest send their own payloads, in their own structure, which Litecard reads for you. **The work is extraction**: pulling out the customer, the identifier and whatever matters to the pass, such as points or a tier, and mapping it onto the template.\n\n`SHOPIFY`, `TALONONE`, `KLAVIYO`, `SQUARE`, `YOTPO`, `RIVO`, `GROWAVE`, `OKENDO`, `FYND` and `FARE_HARBOR` each have a handler that knows their format. You point them at the URL; there is nothing to reshape first.\n\nSome go further than a field copy. Shopify subscriptions are created for you and its deliveries are signature-verified, covered in [Subscribe to an External System](#tag/subscribe-to-an-external-system).\n\n### What holds for every provider\n\n**Events are deduplicated on `templateId#externalId`**, so a provider retrying a delivery does not issue a second pass to the same person. Retries are normal: most platforms resend on anything that is not a fast success.\n\n**The status code depends on the provider**, because some use it to decide whether to retry. The operation below sets out which get real codes and which are answered in-body.\n\n**Invitation email follows the template.** A pass issued this way sends Litecard's invitation unless the template disables it."},{"name":"Business","description":"**Your account, and the businesses under it.**\n\nFor figures across a business rather than its structure, see [Business Analytics](#tag/business-analytics).\n\n![A master business and the sub-businesses under it](/api/v1.0/assets/master-sub-business.svg)\n\n### Master and sub-businesses\n\n**A master business owns others.** Each sub-business keeps its own passes, templates, users and analytics, and nothing crosses between them. It is how an agency runs several clients, or a franchise group keeps each brand apart, under one contract.\n\n`POST /api/v1/subBusiness` creates one. Only a master account can call it, and only with the `Admin` role.\n\n**A sub-business id is built from its parent's**: the master id, a `#`, then its own part.\n\n```\nR9xh12dga8i          the master\nR9xh12dga8i#a7Kq2    a sub-business of it\n```\n\nThat `#` is why a business id in a URL has to be percent-encoded as `%23`. It is also how Litecard checks ownership: an id that does not begin with your master id, and is not in your list of sub-businesses, is refused.\n\n### Acting as one of them\n\n**A master account's token names the master, not a sub-business.** To work inside one, send its id in a header:\n\n```\nx-active-business-id: R9xh12dga8i#a7Kq2\n```\n\nEverything then applies to that business: the passes you list, the templates you create, the analytics you read.\n\n**A sub-business user needs none of this.** Their own token is already scoped to their business, and the header is ignored.\n\nWithout the header, a master token is refused on anything that acts on a single business's data, with `401 UNAUTHORIZED`. The same error covers an id that is not yours. See [Master and sub-business accounts](#description/master-and-sub-business-accounts)."},{"name":"Business Analytics","description":"**How the whole business is performing**, rather than one template. Two endpoints over the same records, and the difference is the shape you get back.\n\n| Endpoint | Returns |\n| --- | --- |\n| `/pass/downloads/count` | **Totals**, one row per business |\n| `/pass/downloads/trend` | **One row per day**, oldest first |\n\n### A master account gets the portfolio, in one call\n\n**The count endpoint answers differently depending on who asks.** A master account gets one row per sub-business, each with its name and logo. Any other business gets exactly one row, its own.\n\nThat is what makes a single call enough to build a portfolio view. **The trend endpoint does not do this**: it always returns one series, for whichever business the request resolves to, so charting several means calling it once each.\n\n**`x-active-business-id` narrows either of them** to one sub-business, and the count endpoint then answers with a single row, exactly as that business would see it.\n\n### Reading the numbers\n\n**`ALL` is the cheap one.** It reads counters Litecard keeps as it goes. Any other duration aggregates the daily records over that window, so prefer `ALL` when you only want lifetime totals.\n\n### reference: what a day's row is counting\n\n**`CREATION` and `DOWNLOADS` do not measure the same thing on the same day.** One follows the passes created that day; the other counts the downloads that happened that day.\n\n![CREATION and DOWNLOADS count the same day differently](/api/v1.0/assets/trend-reference.svg)\n\nTake 12 June: **20 passes created**, 12 of them downloaded. Separately, **10 passes created on earlier days** were downloaded on the 12th too.\n\n| `reference` | The row for 12 June says | Because it follows |\n| --- | --- | --- |\n| `CREATION` | 20 created, 12 of those downloaded | the **cohort** created that day |\n| `DOWNLOADS` | 22 downloads | the **downloads** that happened that day |\n\n**`CREATION` sticks with its cohort.** If the remaining 8 are downloaded next week, they still belong to 12 June's row, which is what makes it the right view for judging a send: of the passes we issued on Monday, how many landed?\n\n**`DOWNLOADS` ignores where a pass came from.** 12 from that day's own passes plus 10 from earlier ones, and it answers a different question: how much wallet activity was there today?\n\nThey agree only when nobody downloads a pass on a day other than the one it was created. In practice they never quite do, and the two lines together show how far downloads lag issuing.\n\nFor one template instead of the business, the same pair lives under [Pass Templates Analytics](#tag/pass-templates-analytics)."},{"name":"User Groups","description":"**A named set of roles, and the unit permissions are managed in.** \"Store Managers\", \"Read only\", \"Head office\".\n\n![One group per user, and where the permissions live](/api/v1.0/assets/user-groups.svg)\n\n### One group per user, and why\n\n**A user holds a single `userGroupId`.** Not a list of roles, not a group plus exceptions. One.\n\nThat makes a permission question answerable in one step, in both directions. Reading a user tells you what they can do. Reading a group tells you everyone who can do it, which is the question that matters when someone leaves or an audit asks.\n\n**Two people who need different access belong in two groups.** Adding an exception to one person is the thing this model deliberately does not allow, because it is the thing that makes permissions impossible to reason about later.\n\n### Changing access\n\n**Editing a group changes every member at once**, which is the point of the model and the thing to be careful with. Moving one person is `PUT /api/v1/users/{userId}`; changing what a job can do is `PUT` on the group.\n\nGroups belong to a business. A master account manages each sub-business's groups by acting as that business, with `x-active-business-id`."},{"name":"User Management","description":"**People who log in to Litecard**, not the people who hold passes. For those, see [Pass Owner](#tag/pass-owner).\n\nA user is an email address, a business, and **exactly one user group**. The group is where the permissions live, so creating a user is choosing which group they join. See [User Groups](#tag/user-groups) for why it works that way.\n\n```json\n{ \"email\": \"ana@acme.com\", \"userGroupId\": \"grp_7Kq2\", \"sendInvitation\": true }\n```\n\n**Up to 20 users per business**, counted per business rather than per account, so each sub-business gets its own allowance.\n\n**Changing what someone can do means moving them.** `PUT /api/v1/users/{userId}` puts a user in a different group. There is nothing else on a user to edit for permissions.\n\nA user with no group has not finished being set up. It is not a user with restricted access, and Litecard treats the two differently."},{"name":"Design","description":"Brand the pages your pass owners see, not the pass itself.\n\nSignup forms, per-template download pages, and a business-wide default all take a design payload here. `GET /api/v1/uiconfig/{downloadId}` reads back what a given download page will render, which is the branding the hosted page applies for that pass.\n\nPass artwork lives on the [template](#tag/pass-templates-crud) instead."},{"name":"Order Templates","description":"Blueprint for Apple Wallet **order** passes: the package-tracking passes that appear in Apple Wallet with a delivery status.\n\nA different product from a loyalty or ticket pass, so it has its own template type. Pair with [Orders](#tag/orders)."},{"name":"Orders","description":"Live Apple Wallet order passes, one per customer purchase.\n\n`PATCH /api/v1/orders/{orderId}` updates fulfilment state and pushes it to the owner's wallet, which is how the delivery status changes on their lock screen as a parcel moves."}],"x-tagGroups":[{"name":"Get started","tags":["Authentication","Health Check","Documentation"]},{"name":"Pass Templates","tags":["Pass Templates CRUD","Pass Templates Analytics"]},{"name":"Issue and manage passes","tags":["Pass","Pass Owner","Access Passes by Download ID"]},{"name":"Engage pass owners","tags":["Push Notifications","Location Based Notifications","Notification Groups","Pass Template Notifications"]},{"name":"Scan and redeem","tags":["Scan","Voucher Codes"]},{"name":"Bulk uploads","tags":["Pass Uploads"]},{"name":"Exporting data","tags":["CSV"]},{"name":"OAuth integrations","tags":["OAuth Connections"]},{"name":"Integrations","tags":["Form Integrations"]},{"name":"Outbound webhooks","tags":["Outbound Webhooks"]},{"name":"Inbound webhooks","tags":["Subscribe to an External System","Process Events from an External System"]},{"name":"Business & Analytics","tags":["Business","Business Analytics"]},{"name":"Users","tags":["User Groups","User Management"]},{"name":"Branding","tags":["Design"]},{"name":"Apple Wallet orders","tags":["Order Templates","Orders"]}]}