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
Stable opaque string up to 255 chars. Same key + same body within 24h replays cached response.
8 - 255^[A-Za-z0-9_-]{8,255}$Preferred language for error messages and localized fields. One of sk, cs, en, de. Defaults to card's default_language.
sk, cs, en, de 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. Display name (string OR per-language object). At minimum the default_language must have a value.
1 - 100REQUIRED. Default language for the card. Determines which language is used when the customer's preference is not supported.
sk, cs, en, de 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.
REQUIRED. Issuer details. company_name is REQUIRED because it maps to Apple PKPass.organizationName (shown on lock screen) and Google LoyaltyClass.issuerName (shown in pass list). Without it, Apple shows 'Unknown Issuer' and Google rejects the pass during review.
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.
255Languages this card supports. Defaults to [default_language] when omitted.
sk, cs, en, de Barcode format for the wallet pass. Default 'QR' (works on both Apple and Google Wallet).
QR, Aztec, PDF417, Code128, Code39, EAN8, EAN13, ITF14, UPCA, DataMatrix, Codabar 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).
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
Maximum customer enrollments. Null = unlimited. Capped at 100,000,000 to prevent overflow abuse. 0 and null both mean unlimited; both read back as null.
0 <= x <= 100000000Referral program configuration.
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).
Free-form partner key-value map.
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.
Card-attached links shown on the wallet pass back side - business links (website/social) and a review-prompt link. Max 10 items.
10Response
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 identifier - an opaque, server-issued token prefixed crd_. Immutable.
^crd_[A-Za-z0-9]+$"crd_sophcrprtvpmmnwxah2lomnkee"
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"
Display name of the card template. Accepts a plain string (used in all languages) OR a per-language object {sk: '...', en: '...'} for localized names.
1 - 100REQUIRED. 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 - 500Languages this card template supports. Customer.language must be one of these. Pass content and notification templates must provide values for each.
1sk, cs, en, de Default language used when the customer's preferred language is not in languages_supported.
sk, cs, en, de "sk"
Visual 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.
Type-specific mechanics block. REQUIRED but must match Card.type. Pick the matching tab. Custom = pick 'Custom (null)'.
- Option 1
- Option 2
- Option 3
- Option 4
- Option 5
- Option 6
REQUIRED. Issuer details. company_name is REQUIRED because it maps to Apple PKPass.organizationName (shown on lock screen) and Google LoyaltyClass.issuerName (shown in pass list). Without it, Apple shows 'Unknown Issuer' and Google rejects the pass during review.
Resource type discriminator. Always 'card' for this resource. Useful for client libraries that handle polymorphic responses (e.g., webhooks delivering mixed event types). Server always sets it.
"card""card"
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).
Configuration for the public customer signup form at /signup/{card_id}. Lists which fields to collect, in what order, and which are required.
Maximum number of customers that can be enrolled in this card. Null = unlimited. When reached, POST /v1/customers returns 403 card_limit_reached. 0 and null both mean unlimited; both read back as null.
x >= 01000
Current number of customers enrolled in this card (read-only). Updated on every successful POST /v1/customers.
x >= 0234
Referral program configuration. When enabled, customers can refer friends via a unique referral URL; both parties earn a bonus when the referee enrolls and completes a qualifying action.
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.
Unix timestamp when the card was created.
1782345600
Unix timestamp when any field on the card was last modified.
1782604800
Unix seconds when the card FIRST went live. Stamped on the first transition to active and never overwritten, so it survives pause/reactivate and un-archive cycles. Null for a card that has never been activated (a draft).
Unix seconds of the most recent transition to paused. Restamped on every pause. Null for a card that has never been paused. Not cleared when the card reactivates, so it records the last pause, not the current state - read status for that.
Unix timestamp when the card was archived (status='archived'). Null if active.
null
Card-attached links shown on the wallet pass back side - business links (website/social) and a review-prompt link. Max 10 items.
10The 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