Create a card template
Creates a new card template.
The type field is immutable
Once created, you cannot switch a card from stamp to reward. Design your card family carefully - create a new template if you need different mechanics.
Required fields
type- one ofstamp,reward,discount,coupon,custom.gift_cardandmembershipcards also exist, but they are dashboard-managed: create them there or through a connected POS, not through this endpoint.name- display name (string or{sk, cs, en, de}per-language map)description- customer-facing descriptiondefault_language+languages_supported[]- the localization setdesign- bg/text colors, logo, iconmechanics- type-specific (see request examples for each variant)issuer- company name + contact info shown on wallet pass
Design mode
design.design_mode: "standard"- pick from our 10,000+ icon librarydesign.design_mode: "individual"- upload custom images per stamp/tier
Image generation (standard design)
Cards created with design.design_mode: "standard" get their wallet pass images rendered automatically - the per-stamp-state strip images for stamp cards, or the single pattern strip for other types. The card is returned immediately; images render in the background, typically within seconds. Track progress via image_generation.status on the card (processing -> completed). The pattern image text comes from the card description: pass a per-language object and one image is generated per supported language, each customer receiving the one matching their signup language (fallback sk -> en). A plain-string description produces a single image. Individual-design cards skip generation: you supply the imagery.
Wallet pass
The response includes the auto-generated pass URLs (add_to_wallet_url, google_wallet_url) so you can immediately test the design.
Idempotency
Send Idempotency-Key to make retries safe. See Idempotency.
See the 11 request examples below for every card-type variant.
Authorizations
Your secret API key as a Bearer token: Authorization: Bearer vxn_sk_.... Keys are created and scoped in the dashboard - see Authentication.
Headers
Body
Body for POST /v1/cards. Creates a card template. type, name, description, default_language, design, form_builder and issuer are required - everything else has sensible defaults or can be added via PATCH. The mechanics block must match the chosen type. Once created, type is immutable; all other fields can be patched.
REQUIRED. Card mechanics type. Immutable after creation. Determines which mechanics block applies, which initial_state overrides are valid on customer enrollment, and the wallet pass layout. Notifications reach every card type, including custom.
membership and gift_card cards exist too, but they are dashboard-managed: create them there or through a connected POS, not through this endpoint. Once created, they are fully readable through this API.
stamp, reward, discount, coupon, custom REQUIRED. Brief customer-facing description of the card/loyalty program. Required by Apple PKPass (description field, used for accessibility/voice-over) and Google LoyaltyClass (programName description for review approval). Can be a string OR per-language object for multi-language passes. Min 1, max 500 chars per language.
1 - 500Visual design of the wallet pass. Required: design_mode, bg_color, text_color, icon_url (logo_url is optional; the pass falls back to text when omitted).
VISUAL LAYOUT by mode + type:
- design_mode=standard + stamp: mechanics.stamp_icon_url + (optional custom_background_image_url)
- design_mode=individual + stamp: mechanics.stamp_count_images (N+1 per-position images; image_url accepts a plain URL or an OPTIONAL per-language map - every position must share the same language set; each customer sees the variant in their language, else sk, else en, else the first one)
- design_mode=standard + non-stamp: pattern_type + pattern_icon_urls + (optional custom_background_image_url) Server AUTO-GENERATES the final pattern image per supported language: pass a per-language object as card description and each language gets its own image; single-language cards (or a plain-string description) get one image. Each customer is served the image matching the language they picked at signup (fallback sk, then en, then the first one). No image URLs are needed for the standard pattern.
- design_mode=individual + non-stamp: individual_background_image_url REQUIRED (accepts a plain URL or an OPTIONAL per-language map; each customer sees the variant in their language, else sk, else en, else the first one)
TIER OVERRIDES: tiered reward/discount cards use mechanics.tiers[i].background_image_urls (per-language) which overrides any card-level background for customers at that tier.
REQUIRED. Signup form rendered at the public claim URL (/signup/{card_id}). ENFORCEMENT: email (system_key='email') and first_name (system_key='first_name') are ALWAYS required and CANNOT be deleted from fields[]. Server returns 422 'form_builder_missing_email_field' or 'form_builder_missing_first_name_field' if either is missing. Both fields' system_key cannot be unset, but their translations can be overridden (e.g., display 'Tel. kontakt' instead of 'Email' in Slovak). Partners can add additional system fields (last_name, phone, date_of_birth, address, consent) or custom_field references with custom labels.
Optional partner reference for syncing with external systems. Persisted on the card template and returned on reads (null when unset). Not unique: two templates may share a reference, so GET /v1/cards?external_id= filters and can return more than one row. Send null to clear it.
255Scanner UX flag. When true, staff MUST enter a purchase amount before they can record a transaction against this card. When false, the amount field is optional (staff can record visit-only / quick-redeem transactions). Affects scanner behavior across all card types.
false
The wallet pass layout - three front slots + back rows, identical model on Apple and Google (the same shape the platform's partner integrations have used in production):
The PRIMARY front area (the big number - stamps, points, balance) is owned by the card's mechanics and never configurable here. Typed values ({{custom.<key>}} of type date/number/currency/boolean) render locale-formatted on both platforms automatically.
PATCH replaces this object as a WHOLE, it does not merge per slot. Any slot you omit is REMOVED from the card. Sending {"pass_fields": {"header": {...}}} on a card that also had front_left, front_right, back or barcode_alt_text clears those. To change one slot, send the full pass_fields object including every slot you want to keep, or read the card first and send it back with your edit applied. This differs from metadata and custom on other resources, which ARE JSON Merge Patch (a per-key merge where null deletes one key).
Type-specific mechanics block. REQUIRED but must match Card.type. Pick the matching tab. Custom = pick 'Custom (null)'. An unknown key inside mechanics (also inside a reward, a level or employee) answers 422 parameter_unknown with the full name, for example mechanics.rewards[0].colour. Before, an unknown key was ignored.
- Option 1
- Option 2
- Option 3
- Option 4
- Option 5
- Option 6
Optional terms and conditions shown on the wallet pass back side. text supports per-language (string OR object {sk, cs, en, de}) - the pass renders the customer's own language variant when present, falling back to the card's default-language text otherwise. full_url is a single URL only (no per-language variant).
Response
Card created.
A card template defines the loyalty program a merchant offers. The template controls visual design, mechanics (stamps/points/discount/etc.), the customer signup form, wallet pass content, and behavioral defaults. Customers are enrolled in exactly one card via POST /v1/customers. Cards are immutable on type after creation.
Card mechanics type. Immutable after creation. Drives which mechanics block applies, which initial_state overrides are valid on POST /v1/customers, and which wallet pass layout is used. 'custom' is partner-driven and has no built-in mechanics. Notifications reach every card type, including custom.
stamp, reward, discount, coupon, membership, gift_card, custom "stamp"
REQUIRED. Brief customer-facing description of the card/loyalty program. Required by Apple PKPass (description field, used for accessibility/voice-over) and Google LoyaltyClass (programName description for review approval). Can be a string OR per-language object for multi-language passes. Min 1, max 500 chars per language.
1 - 500Visual design of the wallet pass. Required: design_mode, bg_color, text_color, icon_url (logo_url is optional; the pass falls back to text when omitted).
VISUAL LAYOUT by mode + type:
- design_mode=standard + stamp: mechanics.stamp_icon_url + (optional custom_background_image_url)
- design_mode=individual + stamp: mechanics.stamp_count_images (N+1 per-position images; image_url accepts a plain URL or an OPTIONAL per-language map - every position must share the same language set; each customer sees the variant in their language, else sk, else en, else the first one)
- design_mode=standard + non-stamp: pattern_type + pattern_icon_urls + (optional custom_background_image_url) Server AUTO-GENERATES the final pattern image per supported language: pass a per-language object as card description and each language gets its own image; single-language cards (or a plain-string description) get one image. Each customer is served the image matching the language they picked at signup (fallback sk, then en, then the first one). No image URLs are needed for the standard pattern.
- design_mode=individual + non-stamp: individual_background_image_url REQUIRED (accepts a plain URL or an OPTIONAL per-language map; each customer sees the variant in their language, else sk, else en, else the first one)
TIER OVERRIDES: tiered reward/discount cards use mechanics.tiers[i].background_image_urls (per-language) which overrides any card-level background for customers at that tier.
Your stable reference for this card template (e.g., your internal product SKU). Useful for syncing with external systems. Persisted on the card template and returned on reads (null when unset). Not unique: two templates may share a reference, so GET /v1/cards?external_id= filters and can return more than one row. Send null to clear it.
255"loyalty-coffee-stamp-2026"
Barcode format rendered on the wallet pass to identify customers at POS. QR/Aztec/PDF417/Code128 work on both Apple and Google Wallet; others are Google Wallet only.
QR, Aztec, PDF417, Code128, Code39, EAN8, EAN13, ITF14, UPCA, DataMatrix, Codabar "QR"
Scanner UX flag. When true, staff MUST enter a purchase amount before they can record a transaction against this card. When false, the amount field is optional (staff can record visit-only / quick-redeem transactions). Affects scanner behavior across all card types.
false
The wallet pass layout - three front slots + back rows, identical model on Apple and Google (the same shape the platform's partner integrations have used in production):
The PRIMARY front area (the big number - stamps, points, balance) is owned by the card's mechanics and never configurable here. Typed values ({{custom.<key>}} of type date/number/currency/boolean) render locale-formatted on both platforms automatically.
PATCH replaces this object as a WHOLE, it does not merge per slot. Any slot you omit is REMOVED from the card. Sending {"pass_fields": {"header": {...}}} on a card that also had front_left, front_right, back or barcode_alt_text clears those. To change one slot, send the full pass_fields object including every slot you want to keep, or read the card first and send it back with your edit applied. This differs from metadata and custom on other resources, which ARE JSON Merge Patch (a per-key merge where null deletes one key).
Optional terms and conditions shown on the wallet pass back side. text supports per-language (string OR object {sk, cs, en, de}) - the pass renders the customer's own language variant when present, falling back to the card's default-language text otherwise. full_url is a single URL only (no per-language variant).
Public signup URL where customers self-enroll. Pattern: https://vexioncards.one/signup/?id={card_id}. Share via QR code, flyer, or email. Append ?loc={location_id} for per-location attribution (signup_location_id on the resulting customer). For per-location URLs of every location with this card enabled, call GET /v1/locations and check enabled_card_ids for this card's id (or read each location's cards[] (GET /v1/locations) - every entry carries its location-scoped signup_url). We previously had registration_urls_per_location on this response, but it duplicated data that lives on Location and forced an extra refresh whenever a location toggled the card on/off.
"https://vexioncards.one/signup/?id=crd_sophcrprtvpmmnwxah2lomnkee"
Free-form key-value map for partner-internal IDs and integration markers. NOT rendered on the wallet pass. Shape IS enforced (422 validation_failed): max 50 keys, key must match ^[a-zA-Z0-9_]{1,40}$, values must be strings of at most 500 chars. The platform never interprets the contents.
Persisted on the card template and returned on reads. Omitted entirely when empty rather than sent as null. On PATCH the map is merged per key, so sending one key never clears the others. (Distinct from Customer.metadata, which is built and live.)
Template lifecycle. draft (created but never launched - not issuable, its public signup page turns customers away), active (issuable), paused (temporarily closed to NEW signups; existing passes keep working and keep earning), archived (retired - existing customer passes keep working, no new signups). A card created through this API is born active. DELETE archives, never destroys, and historical references stay resolvable forever.
draft, active, paused, archived "active"
Server-side pass-image generation state. Standard-design cards get their wallet pass images (the per-stamp-state strip images, or the single pattern strip) rendered automatically after create and after design edits. The card is returned immediately and images render in the background, typically within seconds. processing: rendering in progress - the pass already works and images appear as soon as they are ready. completed: all images are live. failed: rendering hit an error and is retried automatically; the card itself is unaffected. not_applicable: individual-design cards (you supply the images) and cards with no generated imagery.
The business currency of this card. A business has one currency, so it is the same on every card. Set from the country of the first location and locked after the first transaction, customer balance or gift card purchase. See Money and currency.
EUR, CZK, GBP, HUF, PLN, RON, CHF, USD