API reference

Partner API and widget

The partner API is for your servers: report orders, read reports and shoppers, and manage codes and webhooks with a secret key. The widget's JavaScript API is for your pages.

Basics

Base URL
https://vouchernaut.com/api/partner/v1
Authentication
Authorization: Bearer vn_sk_…, a secret key from Developers in the console. Test keys (vn_sk_test_…) never touch live data. Use them from servers only; cookies never authenticate.
Errors
Every error has the same shape, and every response carries X-Request-Id and RateLimit-* headers.
{
  "error": { "code": "insufficient_scope", "message": "This key can't read orders." },
  "requestId": "req_…"
}
Version
Vouchernaut for Brands API, version 1 (from our last published copy).

Scopes

A key can do only what its scopes allow. New keys get every scope except the ones marked opt-in.

  • campaigns:read

    Read your campaigns and their settings.

  • campaigns:write

    Create, change, pause and publish campaigns.

  • codes:write

    Upload and manage discount codes in your code pools.

  • verifications:read

    Read verifications and whether they passed.

  • orders:write

    Report orders that used a verified discount.

  • orders:read

    Read the orders you reported, how each matched a reward and its commission basis.

  • reports:read

    Read reports on views, verifications and redemptions.

  • webhooks:write

    Add, change and remove webhook endpoints, and rotate their secrets.

  • webhooks:read

    List webhook endpoints and read recent events, including details shoppers shared.

  • captures:read

    Read the details shoppers shared after verifying, with their consent.

  • shoppers:writeopt-in

    Export or erase a shopper's data with your brand when they ask you to.

  • locations:read

    List your shops: store codes, names and addresses.

  • in_store:redeem

    Look up in-store codes shoppers show at the till and mark them redeemed.

Your key

GET/api/partner/v1/me

The key's brand

Auth Secret key: Authorization: Bearer vn_sk_…

Responses

  • 200

    The brand and key a secret key belongs to.

    • brandobjectrequired
      • namestringrequired
      • slugstringrequired
    • keyobjectrequired
      • idstringrequired
      • mode"live" | "test"required
      • scopes"widget" | "campaigns:read" | "campaigns:write" | "codes:write" | "verifications:read" | "orders:write" | "orders:read" | "reports:read" | "webhooks:write" | "webhooks:read" | "captures:read" | "shoppers:write" | "locations:read" | "in_store:redeem"[]required
  • 401

    No key, or one that doesn't work (missing_key, invalid_key, key_revoked, key_expired, verification_invalid).

    PartnerError

  • 403

    The key may not do this (wrong_key_type, secret_key_in_browser, origin_not_allowed, insufficient_scope, brand_not_verified, terms_required, brand_suspended, market_unavailable, location_required, wrong_pin).

    PartnerError

  • 429

    Over a rate limit; see Retry-After (rate_limited, too_many_requests, locked, pin_locked).

    PartnerError

  • 500

    Something failed on our side (internal_error).

    PartnerError

Example

curl -X GET "https://vouchernaut.com/api/partner/v1/me" \
  -H "Authorization: Bearer vn_sk_test_…"

Usage

GET/api/partner/v1/usage

This month's verifications and allowance

Auth Secret key: Authorization: Bearer vn_sk_…

The brand's plan and its billable verifications this London calendar month (live mode only: test mode is never billed): one per shopper, audience and month, whether they were verified here or recognised from another brand. left is how many more may verify this month, counting what the overage cap pays for on Growth and Scale; null when there's no limit. Any secret key may read it.

Responses

  • 200

    This month's usage.

    Usage

  • 401

    No key, or one that doesn't work (missing_key, invalid_key, key_revoked, key_expired, verification_invalid).

    PartnerError

  • 403

    The key may not do this (wrong_key_type, secret_key_in_browser, origin_not_allowed, insufficient_scope, brand_not_verified, terms_required, brand_suspended, market_unavailable, location_required, wrong_pin).

    PartnerError

  • 429

    Over a rate limit; see Retry-After (rate_limited, too_many_requests, locked, pin_locked).

    PartnerError

  • 500

    Something failed on our side (internal_error).

    PartnerError

Example

curl -X GET "https://vouchernaut.com/api/partner/v1/usage" \
  -H "Authorization: Bearer vn_sk_test_…"

Campaigns

GET/api/partner/v1/campaigns

The brand's campaigns

Auth Secret key: Authorization: Bearer vn_sk_…

Newest first, a page at a time. Pass the response's nextCursor back as cursor for the next page; it is null on the last. Needs the campaigns:read scope.

Parameters

  • limitquery · integer

    Campaigns per page, 1 to 100.

  • cursorquery · string

    An opaque cursor: the previous page's nextCursor.

Responses

  • 200

    A page of the brand's campaigns.

  • 400

    The request isn't valid (invalid_request, audience_not_offered, method_unavailable, email_not_eligible, not_on_staff_list, disposable_email, wrong_code).

    PartnerError

  • 401

    No key, or one that doesn't work (missing_key, invalid_key, key_revoked, key_expired, verification_invalid).

    PartnerError

  • 403

    The key may not do this (wrong_key_type, secret_key_in_browser, origin_not_allowed, insufficient_scope, brand_not_verified, terms_required, brand_suspended, market_unavailable, location_required, wrong_pin).

    PartnerError

  • 429

    Over a rate limit; see Retry-After (rate_limited, too_many_requests, locked, pin_locked).

    PartnerError

  • 500

    Something failed on our side (internal_error).

    PartnerError

Example

curl -X GET "https://vouchernaut.com/api/partner/v1/campaigns" \
  -H "Authorization: Bearer vn_sk_test_…"
GET/api/partner/v1/campaigns/{id}

One campaign

Auth Secret key: Authorization: Bearer vn_sk_…

Needs the campaigns:read scope.

Parameters

  • idpath · stringrequired

    The campaign's id.

Responses

  • 200

    The campaign.

  • 400

    The request isn't valid (invalid_request, audience_not_offered, method_unavailable, email_not_eligible, not_on_staff_list, disposable_email, wrong_code).

    PartnerError

  • 401

    No key, or one that doesn't work (missing_key, invalid_key, key_revoked, key_expired, verification_invalid).

    PartnerError

  • 403

    The key may not do this (wrong_key_type, secret_key_in_browser, origin_not_allowed, insufficient_scope, brand_not_verified, terms_required, brand_suspended, market_unavailable, location_required, wrong_pin).

    PartnerError

  • 404

    No such thing, or it isn't the key's brand's (not_found).

    PartnerError

  • 429

    Over a rate limit; see Retry-After (rate_limited, too_many_requests, locked, pin_locked).

    PartnerError

  • 500

    Something failed on our side (internal_error).

    PartnerError

Example

curl -X GET "https://vouchernaut.com/api/partner/v1/campaigns/{id}" \
  -H "Authorization: Bearer vn_sk_test_…"
PATCH/api/partner/v1/campaigns/{id}

Move a campaign to another code pool

Auth Secret key: Authorization: Bearer vn_sk_…

Live secret keys only. Takes codePoolId and nothing else: another of the brand's code pools for a unique-code campaign (as the Shopify app does with the pool it makes). A live campaign may only move to a pool with codes left (409 pool_empty otherwise); a draft or paused one may take an empty pool. An archived campaign or one whose reward isn't a unique code is refused (400, error.details.reason campaign_archived or not_unique_code). Another brand's campaign or pool is 404. Needs the campaigns:write scope.

Parameters

  • idpath · stringrequired

    The campaign's id.

Request body · CampaignPoolPatch

  • codePoolIdstring (uuid)required

Responses

  • 200

    The campaign.

  • 400

    The request isn't valid (invalid_request, audience_not_offered, method_unavailable, email_not_eligible, not_on_staff_list, disposable_email, wrong_code).

    PartnerError

  • 401

    No key, or one that doesn't work (missing_key, invalid_key, key_revoked, key_expired, verification_invalid).

    PartnerError

  • 403

    The key may not do this (wrong_key_type, secret_key_in_browser, origin_not_allowed, insufficient_scope, brand_not_verified, terms_required, brand_suspended, market_unavailable, location_required, wrong_pin). A test key gets wrong_key_type.

    PartnerError

  • 404

    No such thing, or it isn't the key's brand's (not_found).

    PartnerError

  • 409

    It can't go any further (rejected, pool_empty, offer_unavailable, claim_limit, claim_redeemed, claim_void, pin_not_set).

    PartnerError

  • 429

    Over a rate limit; see Retry-After (rate_limited, too_many_requests, locked, pin_locked).

    PartnerError

  • 500

    Something failed on our side (internal_error).

    PartnerError

Example

curl -X PATCH "https://vouchernaut.com/api/partner/v1/campaigns/{id}" \
  -H "Authorization: Bearer vn_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{
  "codePoolId": "3f0e8c3a-2b1d-4c5e-9a7f-1d2c3b4a5e6f"
}'
GET/api/partner/v1/campaigns/{id}/products

A campaign's product list

Auth Secret key: Authorization: Bearer vn_sk_…

The SKUs and collections the campaign is limited to (its product list, uploaded in the console), sorted; both empty when it applies to everything. The Shopify app builds a discount's products from it. Needs the campaigns:read scope.

Parameters

  • idpath · stringrequired

    The campaign's id.

Responses

  • 200

    The campaign's product list.

    CampaignProducts

  • 400

    The request isn't valid (invalid_request, audience_not_offered, method_unavailable, email_not_eligible, not_on_staff_list, disposable_email, wrong_code).

    PartnerError

  • 401

    No key, or one that doesn't work (missing_key, invalid_key, key_revoked, key_expired, verification_invalid).

    PartnerError

  • 403

    The key may not do this (wrong_key_type, secret_key_in_browser, origin_not_allowed, insufficient_scope, brand_not_verified, terms_required, brand_suspended, market_unavailable, location_required, wrong_pin).

    PartnerError

  • 404

    No such thing, or it isn't the key's brand's (not_found).

    PartnerError

  • 429

    Over a rate limit; see Retry-After (rate_limited, too_many_requests, locked, pin_locked).

    PartnerError

  • 500

    Something failed on our side (internal_error).

    PartnerError

Example

curl -X GET "https://vouchernaut.com/api/partner/v1/campaigns/{id}/products" \
  -H "Authorization: Bearer vn_sk_test_…"

Code pools

POST/api/partner/v1/code-pools

Make a code pool

Auth Secret key: Authorization: Bearer vn_sk_…

Live secret keys only. A shopify pool names its discount's DiscountCodeNode gid as externalRef (required for it, refused on a csv pool); asking again for the same discount answers the pool already made (200) rather than a second. lowStockThreshold defaults to 100. Add codes with POST /api/partner/v1/code-pools/{id}/codes. Needs the codes:write scope.

Request body · CodePoolCreateRequest

  • namestringrequired

    1 to 80 characters

  • source"csv" | "shopify"

    default "csv"

  • externalRefstring

    pattern ^gid:\/\/shopify\/DiscountCodeNode\/\d{1,20}$

  • lowStockThresholdinteger

    0 to 1000000

Responses

  • 200

    The pool already made for this discount.

  • 201

    The new pool.

  • 400

    The request isn't valid (invalid_request, audience_not_offered, method_unavailable, email_not_eligible, not_on_staff_list, disposable_email, wrong_code).

    PartnerError

  • 401

    No key, or one that doesn't work (missing_key, invalid_key, key_revoked, key_expired, verification_invalid).

    PartnerError

  • 403

    The key may not do this (wrong_key_type, secret_key_in_browser, origin_not_allowed, insufficient_scope, brand_not_verified, terms_required, brand_suspended, market_unavailable, location_required, wrong_pin). A test key gets wrong_key_type: test mode never draws on code pools.

    PartnerError

  • 429

    Over a rate limit; see Retry-After (rate_limited, too_many_requests, locked, pin_locked).

    PartnerError

  • 500

    Something failed on our side (internal_error).

    PartnerError

Example

curl -X POST "https://vouchernaut.com/api/partner/v1/code-pools" \
  -H "Authorization: Bearer vn_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "string"
}'
POST/api/partner/v1/code-pools/{id}/codes

Add codes to a pool

Auth Secret key: Authorization: Bearer vn_sk_…

Live secret keys only. Adds the file's valid codes that aren't already in the pool (compared case-insensitively). A code has at most 64 characters and no whitespace or control characters; invalid rows are reported, not imported. Nothing is imported when the file is malformed, over 200,000 rows or would take the pool over 1,000,000 codes (400, with error.details.reason). One upload per brand runs at a time: another sent meanwhile gets 429 rate_limited with error.details.reason import_in_progress and Retry-After. Needs the codes:write scope.

Parameters

  • idpath · stringrequired

    The code pool's id.

Responses

  • 200

    What the import did.

    • totalintegerrequired

      Non-blank rows read.

    • addedintegerrequired
    • duplicatesintegerrequired

      Valid codes already in the pool or the file.

    • invalidobject[]required

      The first 50 invalid rows.

      • lineintegerrequired
      • reasonstringrequired
    • invalidCountintegerrequired
  • 400

    The request isn't valid (invalid_request, audience_not_offered, method_unavailable, email_not_eligible, not_on_staff_list, disposable_email, wrong_code).

    PartnerError

  • 401

    No key, or one that doesn't work (missing_key, invalid_key, key_revoked, key_expired, verification_invalid).

    PartnerError

  • 403

    The key may not do this (wrong_key_type, secret_key_in_browser, origin_not_allowed, insufficient_scope, brand_not_verified, terms_required, brand_suspended, market_unavailable, location_required, wrong_pin). A test key (vn_sk_test_) gets wrong_key_type: test mode never draws on code pools, so codes are added with a live key.

    PartnerError

  • 404

    No such thing, or it isn't the key's brand's (not_found).

    PartnerError

  • 413

    The body is over 8 MB (invalid_request).

    PartnerError

  • 429

    Over a rate limit; see Retry-After (rate_limited, too_many_requests, locked, pin_locked).

    PartnerError

  • 500

    Something failed on our side (internal_error).

    PartnerError

Example

curl -X POST "https://vouchernaut.com/api/partner/v1/code-pools/{id}/codes" \
  -H "Authorization: Bearer vn_sk_test_…"

Captures

GET/api/partner/v1/captures

Captured shoppers

Auth Secret key: Authorization: Bearer vn_sk_…

What verified shoppers shared with the brand after verifying, in the key's mode, with the exact consent wording they agreed to. Oldest first, a page at a time: pass the response's nextCursor back as cursor for the next page; it is null on the last. To pick up later, pass the newest record's createdAt as since and skip ids you already have. sub is the per-brand pseudonymous shopper id (a gated link's sub). email is the address the shopper proved, shared because they ticked the capture consent; null when they proved a phone number. Needs the captures:read scope.

Parameters

  • campaignquery · string

    Only this campaign's captures. Another brand's campaign is not_found.

  • sincequery · string (date-time)

    Only captures made at or after this ISO 8601 time.

  • limitquery · integer

    Captures per page, 1 to 100.

  • cursorquery · string

    An opaque cursor: the previous page's nextCursor.

Responses

  • 200

    A page of the brand's captured shoppers.

    CapturesPage

  • 400

    The request isn't valid (invalid_request, audience_not_offered, method_unavailable, email_not_eligible, not_on_staff_list, disposable_email, wrong_code).

    PartnerError

  • 401

    No key, or one that doesn't work (missing_key, invalid_key, key_revoked, key_expired, verification_invalid).

    PartnerError

  • 403

    The key may not do this (wrong_key_type, secret_key_in_browser, origin_not_allowed, insufficient_scope, brand_not_verified, terms_required, brand_suspended, market_unavailable, location_required, wrong_pin).

    PartnerError

  • 404

    No such thing, or it isn't the key's brand's (not_found).

    PartnerError

  • 429

    Over a rate limit; see Retry-After (rate_limited, too_many_requests, locked, pin_locked).

    PartnerError

  • 500

    Something failed on our side (internal_error).

    PartnerError

Example

curl -X GET "https://vouchernaut.com/api/partner/v1/captures" \
  -H "Authorization: Bearer vn_sk_test_…"

Shoppers

POST/api/partner/v1/shoppers/export

Export a shopper's data

Auth Secret key: Authorization: Bearer vn_sk_…

For a shopper's access request: everything the brand holds on the shopper with this email, or this phone number for one who verified by SMS, in the key's mode (a live key, live data; a test key, test data), as JSON. Only the brand's own records: its verifications, the uses of proofs on it, the rewards it issued (without their codes), what the shopper shared with it and the orders it reported for them; a proof the shopper made with another brand is named by the brand's own verification that recognised it. An address the brand holds nothing for answers the same shape, empty. Audited without the address or number. Needs the shoppers:write scope.

Request body · ShopperRequest

  • emailstring

    The address the shopper proved; compared trimmed and lower-cased.

    0 to 254 characters · pattern ^[^\s@]+@[^\s@]+\.[^\s@]+$

  • phonestring

    The mobile number the shopper proved by SMS: E.164, or a UK number starting 0.

    0 to 32 characters · pattern ^\+?[\d\s().-]{7,31}$

Responses

  • 200

    The brand's records of the shopper.

    ShopperExport

  • 400

    The request isn't valid (invalid_request, audience_not_offered, method_unavailable, email_not_eligible, not_on_staff_list, disposable_email, wrong_code).

    PartnerError

  • 401

    No key, or one that doesn't work (missing_key, invalid_key, key_revoked, key_expired, verification_invalid).

    PartnerError

  • 403

    The key may not do this (wrong_key_type, secret_key_in_browser, origin_not_allowed, insufficient_scope, brand_not_verified, terms_required, brand_suspended, market_unavailable, location_required, wrong_pin).

    PartnerError

  • 429

    Over a rate limit; see Retry-After (rate_limited, too_many_requests, locked, pin_locked).

    PartnerError

  • 500

    Something failed on our side (internal_error).

    PartnerError

Example

curl -X POST "https://vouchernaut.com/api/partner/v1/shoppers/export" \
  -H "Authorization: Bearer vn_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "sam@leeds.ac.uk",
  "phone": "+447700900123"
}'
POST/api/partner/v1/shoppers/erase

Erase a shopper's data

Auth Secret key: Authorization: Bearer vn_sk_…

For a shopper's erasure request (by email, or phone for one who verified by SMS), in the key's mode: deletes what they shared with the brand and the uses of their proofs on it, and unlinks them from the brand's rewards, funnel events and orders (the rows stay, anonymous, for the brand's counts). Their proofs and other brands' records are theirs and untouched; a shopper erases everything at vouchernaut.com/privacy/verification. Answers the same, with zeros, for an address the brand holds nothing for. Audited without the address or number. Needs the shoppers:write scope.

Request body · ShopperRequest

  • emailstring

    The address the shopper proved; compared trimmed and lower-cased.

    0 to 254 characters · pattern ^[^\s@]+@[^\s@]+\.[^\s@]+$

  • phonestring

    The mobile number the shopper proved by SMS: E.164, or a UK number starting 0.

    0 to 32 characters · pattern ^\+?[\d\s().-]{7,31}$

Responses

  • 200

    What was erased.

    ShopperErasure

  • 400

    The request isn't valid (invalid_request, audience_not_offered, method_unavailable, email_not_eligible, not_on_staff_list, disposable_email, wrong_code).

    PartnerError

  • 401

    No key, or one that doesn't work (missing_key, invalid_key, key_revoked, key_expired, verification_invalid).

    PartnerError

  • 403

    The key may not do this (wrong_key_type, secret_key_in_browser, origin_not_allowed, insufficient_scope, brand_not_verified, terms_required, brand_suspended, market_unavailable, location_required, wrong_pin).

    PartnerError

  • 429

    Over a rate limit; see Retry-After (rate_limited, too_many_requests, locked, pin_locked).

    PartnerError

  • 500

    Something failed on our side (internal_error).

    PartnerError

Example

curl -X POST "https://vouchernaut.com/api/partner/v1/shoppers/erase" \
  -H "Authorization: Bearer vn_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "sam@leeds.ac.uk",
  "phone": "+447700900123"
}'

Orders

POST/api/partner/v1/orders

Report an order

Auth Secret key: Authorization: Bearer vn_sk_…

Creates the order, or updates the one with the same externalId. Amounts are whole minor units (pence, cents). The key decides the mode; test: true on a live key stores the order in test mode (Shopify test orders). An order is matched to the reward that led to it: an issued unique code (code_unique), the campaign's shared code (code_shared), the widget session (sessionId, the vn:sid the widget keeps) or, failing those, the verified shopper's email (email). The email is only ever stored as a keyed hash. A report for an order the pixel sent takes over its totals, codes and items, and keeps its session when none is sent. sourceName: "pos" or channel: "in_store" stores channel: in_store; an in-store order matched to an in-store claim redeems the claim only when the key also has in_store:redeem. A repeat that changes nothing is answered 200 and records nothing. Needs the orders:write scope.

Request body · OrderRequest

  • externalIdstringrequired

    1 to 100 characters

  • orderNumberstring

    1 to 100 characters

  • placedAtstring (date-time)
  • currencystringrequired
  • totalintegerrequired

    0 to 1000000000000

  • subtotalinteger

    0 to 1000000000000

  • taxinteger

    0 to 1000000000000

  • discountTotalinteger

    0 to 1000000000000

  • discountCodesstring[]

    up to 20 items

  • lineItemsobject[]

    up to 200 items

    • skustring | null

      1 to 200 characters

    • quantityintegerrequired

      1 to 100000

    • priceintegerrequired

      0 to 1000000000000

    • productIdstring | integer
    • variantIdstring | integer
    • collectionsstring[]

      up to 50 items

  • sessionIdstring

    pattern ^[A-Za-z0-9_-]{8,64}$

  • customerEmailstring

    0 to 254 characters · pattern ^[^\s@]+@[^\s@]+\.[^\s@]+$

  • newCustomerboolean
  • countrystring

    pattern ^[A-Za-z]{2}$

  • landingSitestring

    0 to 2048 characters

  • sourceNamestring

    1 to 100 characters

  • channel"online" | "in_store"
  • testboolean

Responses

  • 200

    An existing order, updated.

  • 201

    A new order.

  • 400

    The request isn't valid (invalid_request, audience_not_offered, method_unavailable, email_not_eligible, not_on_staff_list, disposable_email, wrong_code).

    PartnerError

  • 401

    No key, or one that doesn't work (missing_key, invalid_key, key_revoked, key_expired, verification_invalid).

    PartnerError

  • 403

    The key may not do this (wrong_key_type, secret_key_in_browser, origin_not_allowed, insufficient_scope, brand_not_verified, terms_required, brand_suspended, market_unavailable, location_required, wrong_pin).

    PartnerError

  • 422

    It would leave the order inconsistent (refund_exceeds_total). The new total is below what has already been refunded.

    PartnerError

  • 429

    Over a rate limit; see Retry-After (rate_limited, too_many_requests, locked, pin_locked).

    PartnerError

  • 500

    Something failed on our side (internal_error).

    PartnerError

Example

curl -X POST "https://vouchernaut.com/api/partner/v1/orders" \
  -H "Authorization: Bearer vn_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{
  "externalId": "string",
  "currency": "string",
  "total": 1000
}'
GET/api/partner/v1/orders

The brand's orders

Auth Secret key: Authorization: Bearer vn_sk_…

Newest first by when they were placed, in the key's mode, a page at a time: pass the response's nextCursor back as cursor for the next page; it is null on the last. since keeps orders placed at or after it; test=true on a live key lists test orders. Needs the orders:read scope.

Parameters

  • sincequery · string (date-time)

    Only orders placed at or after this ISO 8601 time.

  • limitquery · integer

    Orders per page, 1 to 100.

  • cursorquery · string

    An opaque cursor: the previous page's nextCursor.

  • testquery · "true" | "false"

    A live key's test orders.

Responses

  • 200

    A page of orders.

    OrdersPage

  • 400

    The request isn't valid (invalid_request, audience_not_offered, method_unavailable, email_not_eligible, not_on_staff_list, disposable_email, wrong_code).

    PartnerError

  • 401

    No key, or one that doesn't work (missing_key, invalid_key, key_revoked, key_expired, verification_invalid).

    PartnerError

  • 403

    The key may not do this (wrong_key_type, secret_key_in_browser, origin_not_allowed, insufficient_scope, brand_not_verified, terms_required, brand_suspended, market_unavailable, location_required, wrong_pin).

    PartnerError

  • 429

    Over a rate limit; see Retry-After (rate_limited, too_many_requests, locked, pin_locked).

    PartnerError

  • 500

    Something failed on our side (internal_error).

    PartnerError

Example

curl -X GET "https://vouchernaut.com/api/partner/v1/orders" \
  -H "Authorization: Bearer vn_sk_test_…"
GET/api/partner/v1/orders/{externalId}

One order

Auth Secret key: Authorization: Bearer vn_sk_…

The stored order, its match and commission basis. Another brand's order is not_found. A live key also reaches the brand's test orders (sent with test: true) when no live order has the id. Needs the orders:read scope.

Parameters

  • externalIdpath · stringrequired

    The brand's order id, as sent in externalId (URL-encoded: shopify%3Agid%3A%2F%2Fshopify%2FOrder%2F1).

Responses

  • 200

    The order as stored, with its match and commission basis.

  • 400

    The request isn't valid (invalid_request, audience_not_offered, method_unavailable, email_not_eligible, not_on_staff_list, disposable_email, wrong_code).

    PartnerError

  • 401

    No key, or one that doesn't work (missing_key, invalid_key, key_revoked, key_expired, verification_invalid).

    PartnerError

  • 403

    The key may not do this (wrong_key_type, secret_key_in_browser, origin_not_allowed, insufficient_scope, brand_not_verified, terms_required, brand_suspended, market_unavailable, location_required, wrong_pin).

    PartnerError

  • 404

    No such thing, or it isn't the key's brand's (not_found).

    PartnerError

  • 429

    Over a rate limit; see Retry-After (rate_limited, too_many_requests, locked, pin_locked).

    PartnerError

  • 500

    Something failed on our side (internal_error).

    PartnerError

Example

curl -X GET "https://vouchernaut.com/api/partner/v1/orders/{externalId}" \
  -H "Authorization: Bearer vn_sk_test_…"
PATCH/api/partner/v1/orders/{externalId}

Set the refunded total, or cancel

Auth Secret key: Authorization: Bearer vn_sk_…

{ refundedTotal } sets the refunded total (minor units, at most the order's total; the difference is recorded as a refund), or { status: "cancelled" } cancels the order. Needs an Idempotency-Key header (400 without one): a repeat with the same key answers the order as it stands and changes nothing. A live key also reaches the brand's test orders (sent with test: true) when no live order has the id. Needs the orders:write scope.

Parameters

  • externalIdpath · stringrequired

    The brand's order id, as sent in externalId (URL-encoded: shopify%3Agid%3A%2F%2Fshopify%2FOrder%2F1).

  • idempotency-keyheader · string

    Required: one per change you mean to make.

Request body · OrderPatch

object | object

Responses

  • 200

    The order as stored, with its match and commission basis.

  • 400

    The request isn't valid (invalid_request, audience_not_offered, method_unavailable, email_not_eligible, not_on_staff_list, disposable_email, wrong_code).

    PartnerError

  • 401

    No key, or one that doesn't work (missing_key, invalid_key, key_revoked, key_expired, verification_invalid).

    PartnerError

  • 403

    The key may not do this (wrong_key_type, secret_key_in_browser, origin_not_allowed, insufficient_scope, brand_not_verified, terms_required, brand_suspended, market_unavailable, location_required, wrong_pin).

    PartnerError

  • 404

    No such thing, or it isn't the key's brand's (not_found).

    PartnerError

  • 422

    It would leave the order inconsistent (refund_exceeds_total).

    PartnerError

  • 429

    Over a rate limit; see Retry-After (rate_limited, too_many_requests, locked, pin_locked).

    PartnerError

  • 500

    Something failed on our side (internal_error).

    PartnerError

Example

curl -X PATCH "https://vouchernaut.com/api/partner/v1/orders/{externalId}" \
  -H "Authorization: Bearer vn_sk_test_…" \
  -H "idempotency-key: <idempotency-key>" \
  -H "Content-Type: application/json" \
  -d '{
  "refundedTotal": 1000
}'
POST/api/partner/v1/orders/{externalId}/refunds

Refund part or all of an order

Auth Secret key: Authorization: Bearer vn_sk_…

Adds amount (minor units) to the refunded total; the order becomes partially_refunded, or refunded once the refunds reach its total, and its commission basis shrinks with it. Idempotent by refundId: a repeat answers the order as it stands and changes nothing. A refund that would take the refunded total above the order's total is refused with 422 refund_exceeds_total and changes nothing. A live key also reaches the brand's test orders (sent with test: true) when no live order has the id. Needs the orders:write scope.

Parameters

  • externalIdpath · stringrequired

    The brand's order id, as sent in externalId (URL-encoded: shopify%3Agid%3A%2F%2Fshopify%2FOrder%2F1).

Request body · RefundRequest

  • refundIdstringrequired

    1 to 100 characters

  • amountintegerrequired

    1 to 1000000000000

  • reasonstring

    0 to 500 characters

  • lineItemsobject[]

    up to 200 items

    • skustring | null

      1 to 200 characters

    • quantityintegerrequired

      1 to 100000

    • amountinteger

      0 to 1000000000000

Responses

  • 200

    The order as stored, with its match and commission basis.

  • 400

    The request isn't valid (invalid_request, audience_not_offered, method_unavailable, email_not_eligible, not_on_staff_list, disposable_email, wrong_code).

    PartnerError

  • 401

    No key, or one that doesn't work (missing_key, invalid_key, key_revoked, key_expired, verification_invalid).

    PartnerError

  • 403

    The key may not do this (wrong_key_type, secret_key_in_browser, origin_not_allowed, insufficient_scope, brand_not_verified, terms_required, brand_suspended, market_unavailable, location_required, wrong_pin).

    PartnerError

  • 404

    No such thing, or it isn't the key's brand's (not_found).

    PartnerError

  • 422

    It would leave the order inconsistent (refund_exceeds_total).

    PartnerError

  • 429

    Over a rate limit; see Retry-After (rate_limited, too_many_requests, locked, pin_locked).

    PartnerError

  • 500

    Something failed on our side (internal_error).

    PartnerError

Example

curl -X POST "https://vouchernaut.com/api/partner/v1/orders/{externalId}/refunds" \
  -H "Authorization: Bearer vn_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{
  "refundId": "string",
  "amount": 1
}'

Reports

GET/api/partner/v1/reports/funnel

The funnel report

Auth Secret key: Authorization: Bearer vn_sk_…

Placement impressions to redeemed orders for the brand, from daily rollups refreshed every 10 minutes, as the console shows it. Counts of shoppers are distinct per day, so a range adds up shopper-days. Money is per currency. from and to are London days (up to 400, the last 30 by default). A test key reads test mode only; a live key reads live unless mode=test. Needs the reports:read scope.

Parameters

  • fromquery · string

    YYYY-MM-DD, London.

  • toquery · string

    YYYY-MM-DD, London.

  • marketquery · string

    An ISO-2 country, or UK, EU, US or ROW.

  • campaignquery · string | string[]

    A campaign id; repeat for several. Another brand's campaign is not_found.

  • modequery · "live" | "test"

    live or test; the key's mode when not given.

  • groupquery · "day" | "audience" | "campaign" | "country" | "device" | "shopper" | "placement"

    How rows break down; day when not given.

Responses

  • 200

    The report.

    FunnelReport

  • 400

    The request isn't valid (invalid_request, audience_not_offered, method_unavailable, email_not_eligible, not_on_staff_list, disposable_email, wrong_code).

    PartnerError

  • 401

    No key, or one that doesn't work (missing_key, invalid_key, key_revoked, key_expired, verification_invalid).

    PartnerError

  • 403

    The key may not do this (wrong_key_type, secret_key_in_browser, origin_not_allowed, insufficient_scope, brand_not_verified, terms_required, brand_suspended, market_unavailable, location_required, wrong_pin).

    PartnerError

  • 404

    No such thing, or it isn't the key's brand's (not_found).

    PartnerError

  • 429

    Over a rate limit; see Retry-After (rate_limited, too_many_requests, locked, pin_locked).

    PartnerError

  • 500

    Something failed on our side (internal_error).

    PartnerError

Example

curl -X GET "https://vouchernaut.com/api/partner/v1/reports/funnel" \
  -H "Authorization: Bearer vn_sk_test_…"

Webhook endpoints

GET/api/partner/v1/webhook-endpoints

The brand's webhook endpoints

Auth Secret key: Authorization: Bearer vn_sk_…

Every endpoint in the key's mode, newest first, with the last four characters of its secret, never the secret. Needs the webhooks:read scope.

Responses

  • 200

    The endpoints.

  • 400

    The request isn't valid (invalid_request, audience_not_offered, method_unavailable, email_not_eligible, not_on_staff_list, disposable_email, wrong_code).

    PartnerError

  • 401

    No key, or one that doesn't work (missing_key, invalid_key, key_revoked, key_expired, verification_invalid).

    PartnerError

  • 403

    The key may not do this (wrong_key_type, secret_key_in_browser, origin_not_allowed, insufficient_scope, brand_not_verified, terms_required, brand_suspended, market_unavailable, location_required, wrong_pin).

    PartnerError

  • 429

    Over a rate limit; see Retry-After (rate_limited, too_many_requests, locked, pin_locked).

    PartnerError

  • 500

    Something failed on our side (internal_error).

    PartnerError

Example

curl -X GET "https://vouchernaut.com/api/partner/v1/webhook-endpoints" \
  -H "Authorization: Bearer vn_sk_test_…"
POST/api/partner/v1/webhook-endpoints

Subscribe a URL to events (REST hook)

Auth Secret key: Authorization: Bearer vn_sk_…

Makes an endpoint in the key's mode that receives the events named (event for one, as Zapier's REST hooks send it, or events). The URL must be https on a public host (no user name or password, up to 2,000 characters); its host is resolved and refused when it points at a private, loopback, link-local or metadata address. Up to 10 endpoints per mode. Answers the endpoint's id and its signing secret, which is never shown again: keep it to check signatures. Deliveries are signed: Vouchernaut-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>"> under the endpoint's secret. Needs the webhooks:write scope, and captures:read as well to subscribe to capture.submitted.

Request body · WebhookSubscribeRequest

  • urlstringrequired

    0 to 4000 characters

  • event"verification.completed" | "reward.issued" | "capture.submitted" | "order.attributed" | "order.refunded" | "code_pool.low"
  • events"verification.completed" | "reward.issued" | "capture.submitted" | "order.attributed" | "order.refunded" | "code_pool.low"[]

    up to 6 items

  • descriptionstring

    0 to 200 characters

Responses

  • 201

    The new endpoint and its secret.

    • idstring (uuid)required
    • secretstringrequired

      The endpoint's signing secret (vn_whsec_…), shown in this answer only.

    • endpointWebhookEndpointrequired
  • 400

    The request isn't valid (invalid_request, audience_not_offered, method_unavailable, email_not_eligible, not_on_staff_list, disposable_email, wrong_code).

    PartnerError

  • 401

    No key, or one that doesn't work (missing_key, invalid_key, key_revoked, key_expired, verification_invalid).

    PartnerError

  • 402

    The brand's plan doesn't allow it; error.details names the feature and the cheapest plan with it (plan_limit, plan_required).

    PartnerError

  • 403

    The key may not do this (wrong_key_type, secret_key_in_browser, origin_not_allowed, insufficient_scope, brand_not_verified, terms_required, brand_suspended, market_unavailable, location_required, wrong_pin).

    PartnerError

  • 429

    Over a rate limit; see Retry-After (rate_limited, too_many_requests, locked, pin_locked).

    PartnerError

  • 500

    Something failed on our side (internal_error).

    PartnerError

Example

curl -X POST "https://vouchernaut.com/api/partner/v1/webhook-endpoints" \
  -H "Authorization: Bearer vn_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "string"
}'
DELETE/api/partner/v1/webhook-endpoints/{id}

Unsubscribe (remove an endpoint)

Auth Secret key: Authorization: Bearer vn_sk_…

Removes the endpoint; nothing more is sent to it. Another brand's or mode's endpoint is not_found. Needs the webhooks:write scope.

Parameters

  • idpath · string (uuid)required

    The endpoint's id, as the subscribe call answered it.

Responses

  • 200

    Removed.

    • idstring (uuid)required
    • deletedtruerequired
  • 400

    The request isn't valid (invalid_request, audience_not_offered, method_unavailable, email_not_eligible, not_on_staff_list, disposable_email, wrong_code).

    PartnerError

  • 401

    No key, or one that doesn't work (missing_key, invalid_key, key_revoked, key_expired, verification_invalid).

    PartnerError

  • 403

    The key may not do this (wrong_key_type, secret_key_in_browser, origin_not_allowed, insufficient_scope, brand_not_verified, terms_required, brand_suspended, market_unavailable, location_required, wrong_pin).

    PartnerError

  • 404

    No such thing, or it isn't the key's brand's (not_found).

    PartnerError

  • 429

    Over a rate limit; see Retry-After (rate_limited, too_many_requests, locked, pin_locked).

    PartnerError

  • 500

    Something failed on our side (internal_error).

    PartnerError

Example

curl -X DELETE "https://vouchernaut.com/api/partner/v1/webhook-endpoints/{id}" \
  -H "Authorization: Bearer vn_sk_test_…"
POST/api/partner/v1/webhook-endpoints/{id}/rotate

Rotate an endpoint's secret

Auth Secret key: Authorization: Bearer vn_sk_…

Makes a new signing secret, answered here only. For the next 24 hours deliveries carry a second v1 signature made with the old secret, so the receiver can switch over. Needs the webhooks:write scope.

Parameters

  • idpath · string (uuid)required

    The endpoint's id, as the subscribe call answered it.

Responses

  • 200

    The endpoint and its new secret.

    • idstring (uuid)required
    • secretstringrequired

      The endpoint's signing secret (vn_whsec_…), shown in this answer only.

    • endpointWebhookEndpointrequired
  • 400

    The request isn't valid (invalid_request, audience_not_offered, method_unavailable, email_not_eligible, not_on_staff_list, disposable_email, wrong_code).

    PartnerError

  • 401

    No key, or one that doesn't work (missing_key, invalid_key, key_revoked, key_expired, verification_invalid).

    PartnerError

  • 402

    The brand's plan doesn't allow it; error.details names the feature and the cheapest plan with it (plan_limit, plan_required).

    PartnerError

  • 403

    The key may not do this (wrong_key_type, secret_key_in_browser, origin_not_allowed, insufficient_scope, brand_not_verified, terms_required, brand_suspended, market_unavailable, location_required, wrong_pin).

    PartnerError

  • 404

    No such thing, or it isn't the key's brand's (not_found).

    PartnerError

  • 429

    Over a rate limit; see Retry-After (rate_limited, too_many_requests, locked, pin_locked).

    PartnerError

  • 500

    Something failed on our side (internal_error).

    PartnerError

Example

curl -X POST "https://vouchernaut.com/api/partner/v1/webhook-endpoints/{id}/rotate" \
  -H "Authorization: Bearer vn_sk_test_…"

Events

GET/api/partner/v1/events

Recent events (polling trigger)

Auth Secret key: Authorization: Bearer vn_sk_…

The brand's latest events in the key's mode, newest first, each exactly as a webhook delivers it, id first (a polling trigger deduplicates on it). Events are kept for 30 days. Needs the webhooks:read scope; capture.submitted (what shoppers shared) is listed only for a key with captures:read as well.

Parameters

  • typequery · "verification.completed" | "reward.issued" | "capture.submitted" | "order.attributed" | "order.refunded" | "code_pool.low"

    Only events of this type.

  • limitquery · integer

    Events per page, 1 to 100.

Responses

  • 200

    The events, newest first.

  • 400

    The request isn't valid (invalid_request, audience_not_offered, method_unavailable, email_not_eligible, not_on_staff_list, disposable_email, wrong_code).

    PartnerError

  • 401

    No key, or one that doesn't work (missing_key, invalid_key, key_revoked, key_expired, verification_invalid).

    PartnerError

  • 402

    The brand's plan doesn't allow it; error.details names the feature and the cheapest plan with it (plan_limit, plan_required).

    PartnerError

  • 403

    The key may not do this (wrong_key_type, secret_key_in_browser, origin_not_allowed, insufficient_scope, brand_not_verified, terms_required, brand_suspended, market_unavailable, location_required, wrong_pin).

    PartnerError

  • 429

    Over a rate limit; see Retry-After (rate_limited, too_many_requests, locked, pin_locked).

    PartnerError

  • 500

    Something failed on our side (internal_error).

    PartnerError

Example

curl -X GET "https://vouchernaut.com/api/partner/v1/events" \
  -H "Authorization: Bearer vn_sk_test_…"

Locations

GET/api/partner/v1/locations

The brand's shops

Auth Secret key: Authorization: Bearer vn_sk_…

The brand's in-store locations by id, a page at a time (nextCursor). Needs the locations:read scope.

Parameters

  • statusquery · "active" | "archived" | "all"
  • limitquery · integer

    Shops per page, 1 to 100.

  • cursorquery · string

    The previous page's nextCursor.

Responses

  • 200

    A page of shops.

    • dataLocation[]required
    • nextCursorstring | nullrequired
  • 400

    The request isn't valid (invalid_request, audience_not_offered, method_unavailable, email_not_eligible, not_on_staff_list, disposable_email, wrong_code).

    PartnerError

  • 401

    No key, or one that doesn't work (missing_key, invalid_key, key_revoked, key_expired, verification_invalid).

    PartnerError

  • 403

    The key may not do this (wrong_key_type, secret_key_in_browser, origin_not_allowed, insufficient_scope, brand_not_verified, terms_required, brand_suspended, market_unavailable, location_required, wrong_pin).

    PartnerError

  • 429

    Over a rate limit; see Retry-After (rate_limited, too_many_requests, locked, pin_locked).

    PartnerError

  • 500

    Something failed on our side (internal_error).

    PartnerError

Example

curl -X GET "https://vouchernaut.com/api/partner/v1/locations" \
  -H "Authorization: Bearer vn_sk_test_…"

In store

GET/api/partner/v1/in-store/claims/{code}

Look up an in-store claim

Auth Secret key: Authorization: Bearer vn_sk_…

The claim a code names, in the key's mode: the issued, unexpired one if any (a shared code names many), else the newest. storeCode narrows it to one shop. Needs the in_store:redeem scope.

Parameters

  • codepath · stringrequired

    The code the shopper shows, any case.

  • storeCodequery · string

Responses

  • 200

    The claim.

  • 400

    The request isn't valid (invalid_request, audience_not_offered, method_unavailable, email_not_eligible, not_on_staff_list, disposable_email, wrong_code).

    PartnerError

  • 401

    No key, or one that doesn't work (missing_key, invalid_key, key_revoked, key_expired, verification_invalid).

    PartnerError

  • 403

    The key may not do this (wrong_key_type, secret_key_in_browser, origin_not_allowed, insufficient_scope, brand_not_verified, terms_required, brand_suspended, market_unavailable, location_required, wrong_pin).

    PartnerError

  • 404

    No such thing, or it isn't the key's brand's (not_found).

    PartnerError

  • 429

    Over a rate limit; see Retry-After (rate_limited, too_many_requests, locked, pin_locked).

    PartnerError

  • 500

    Something failed on our side (internal_error).

    PartnerError

Example

curl -X GET "https://vouchernaut.com/api/partner/v1/in-store/claims/{code}" \
  -H "Authorization: Bearer vn_sk_test_…"
POST/api/partner/v1/in-store/claims/{code}/redeem

Redeem an in-store claim

Auth Secret key: Authorization: Bearer vn_sk_…

Marks the claim a code names redeemed by the till (redeemedVia: partner_api), recording orderRef. Once only: a claim already redeemed (by staff, the till or a matched order) is 409 claim_redeemed with error.details.redeemedAt and redeemedVia; an expired one 410. Needs the in_store:redeem scope.

Parameters

  • codepath · stringrequired

    The code the shopper shows, any case.

Request body

  • storeCodestring

    Only the claim made at this shop.

    0 to 64 characters

  • orderRefstring

    The till's own reference, kept on the claim.

    0 to 200 characters

Responses

  • 200

    The claim, redeemed.

  • 400

    The request isn't valid (invalid_request, audience_not_offered, method_unavailable, email_not_eligible, not_on_staff_list, disposable_email, wrong_code).

    PartnerError

  • 401

    No key, or one that doesn't work (missing_key, invalid_key, key_revoked, key_expired, verification_invalid).

    PartnerError

  • 403

    The key may not do this (wrong_key_type, secret_key_in_browser, origin_not_allowed, insufficient_scope, brand_not_verified, terms_required, brand_suspended, market_unavailable, location_required, wrong_pin).

    PartnerError

  • 404

    No such thing, or it isn't the key's brand's (not_found).

    PartnerError

  • 409

    It can't go any further (rejected, pool_empty, offer_unavailable, claim_limit, claim_redeemed, claim_void, pin_not_set).

    PartnerError

  • 410

    It has expired (code_expired, scan_expired, claim_expired).

    PartnerError

  • 429

    Over a rate limit; see Retry-After (rate_limited, too_many_requests, locked, pin_locked).

    PartnerError

  • 500

    Something failed on our side (internal_error).

    PartnerError

Example

curl -X POST "https://vouchernaut.com/api/partner/v1/in-store/claims/{code}/redeem" \
  -H "Authorization: Bearer vn_sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{
  "storeCode": "string",
  "orderRef": "string"
}'

Schemas

PartnerError

Every error from both APIs. requestId matches the response's X-Request-Id.

  • errorobjectrequired
    • code"missing_key" | "invalid_key" | "key_revoked" | "key_expired" | "wrong_key_type" | "secret_key_in_browser" | "origin_not_allowed" | "insufficient_scope" | "brand_not_verified" | "terms_required" | "brand_suspended" | "plan_limit" | "plan_required" | "rate_limited" | "invalid_request" | "not_found" | "internal_error" | "market_unavailable" | "audience_not_offered" | "method_unavailable" | "email_not_eligible" | "not_on_staff_list" | "disposable_email" | "wrong_code" | "code_expired" | "too_many_requests" | "locked" | "rejected" | "verification_invalid" | "pool_empty" | "offer_unavailable" | "refund_exceeds_total" | "launch_unavailable" | "scan_expired" | "location_required" | "claim_limit" | "claim_redeemed" | "claim_expired" | "claim_void" | "wrong_pin" | "pin_locked" | "pin_not_set"required
    • messagestringrequired

      1 to any characters

    • detailsany
  • requestIdstringrequired

    pattern ^[A-Za-z0-9_-]{8,64}$

Usage

  • plan"free" | "growth" | "scale" | "enterprise"required
  • monthstringrequired

    YYYY-MM, London time.

  • resetsAtstringrequired

    When the next month's count starts, at zero.

  • verificationsintegerrequired
  • includedinteger | nullrequired
  • overageintegerrequired

    Verifications over the allowance this month.

  • leftinteger | nullrequired
  • overLimitbooleanrequired

    New verifications are refused (plan_limit) until next month.

  • overageCapPenceinteger | nullrequired

PartnerCampaign

  • idstring (uuid)required
  • namestringrequired
  • status"draft" | "active" | "paused" | "archived"required

    As stored: what the brand last chose.

  • state"draft" | "paused" | "archived" | "scheduled" | "ended" | "out_of_codes" | "live"required

    status, with active resolved against the dates, the total cap and the pool (in the key's mode): scheduled, live, ended or out_of_codes.

  • rewardType"shared_code" | "unique_code" | "codeless_link" | "gated_link"required
  • valueTextstring | nullrequired
  • discountKind"percent" | "fixed" | "free_shipping" | "other"required
  • discountValueinteger | nullrequired

    Basis points for percent, minor units for fixed.

  • discountCurrencystring | nullrequired
  • minSpendMinorinteger | nullrequired
  • combinesWithobjectrequired
    • orderbooleanrequired
    • productbooleanrequired
    • shippingbooleanrequired
  • affiliateRoutebooleanrequired
  • termsstring | nullrequired
  • sharedCodestring | nullrequired
  • codePoolIdstring (uuid) | nullrequired
  • linkUrlstring | nullrequired
  • startsAtstring (date-time) | nullrequired
  • endsAtstring (date-time) | nullrequired
  • marketsstring[]required
  • allowedCountriesstring[] | nullrequired
  • totalCapinteger | nullrequired
  • dailyCapinteger | nullrequired
  • perShopperLimitintegerrequired
  • perShopperWindowDaysinteger | nullrequired
  • ageMininteger | nullrequired
  • ageMaxinteger | nullrequired
  • audiences"student" | "graduate" | "apprentice" | "teacher" | "youth" | "under_26" | "over_60" | "custom_age" | "nhs" | "healthcare" | "social_worker" | "carer" | "police" | "fire" | "first_responder" | "emergency_services" | "military" | "veteran" | "key_worker" | "delivery_transport" | "supermarket" | "charity_worker" | "government" | "employee" | "disability" | "maternity" | "social_follow" | "strava" | "donor"[]required
  • merchantIdinteger | nullrequired
  • captureFieldsobject[]required

    What the shopper is asked for after verifying, in order.

    • field"firstName" | "dateOfBirth" | "occupation"required
    • requiredbooleanrequired
  • captureConsentTextstring | nullrequired
  • marketingConsentTextstring | nullrequired

    Null hides the marketing tick box.

  • codelessMode"shopify_discount" | "parameter" | nullrequired
  • codelessParamstring | nullrequired

    name=value added to the landing page in parameter mode.

  • gatedTtlMinutesintegerrequired

    How long a gated link's token lasts, 5 to 1440 minutes.

  • channels"online" | "in_store"[]required

    Where it runs: online (your site and our hosted pages), in_store (the in-store launcher) or both.

  • inStoreobjectrequired

    In store: how the phone shows the code (barcode, qr or text), for how many minutes (2 to 30), and whether staff confirm it with the shop's PIN.

    • display"barcode" | "qr" | "text"required
    • ttlMinutesintegerrequired
    • staffConfirmbooleanrequired
  • createdAtstring (date-time)required
  • updatedAtstring (date-time)required
  • issuedTodayintegerrequired

    Rewards issued today (London) in the key's mode.

  • issuedTotalintegerrequired

    Rewards issued in the key's mode.

CampaignProducts

  • skusstring[]required
  • collectionsstring[]required

PartnerCodePool

  • idstring (uuid)required
  • namestringrequired
  • source"csv" | "shopify"required

    csv for imported codes; shopify for codes the Shopify app makes in the discount externalRef names.

  • externalRefstring | nullrequired

    The Shopify DiscountCodeNode gid, for a shopify pool.

  • lowStockThresholdintegerrequired

    code_pool.low fires when available codes fall below it.

  • lowStockAlertedAtstring (date-time) | nullrequired
  • availableintegerrequired
  • issuedintegerrequired
  • voidintegerrequired
  • createdAtstring (date-time)required

CodePoolCreateRequest

  • namestringrequired

    1 to 80 characters

  • source"csv" | "shopify"

    default "csv"

  • externalRefstring

    pattern ^gid:\/\/shopify\/DiscountCodeNode\/\d{1,20}$

  • lowStockThresholdinteger

    0 to 1000000

CampaignPoolPatch

  • codePoolIdstring (uuid)required

CapturesPage

  • dataobject[]required
    • idstring (uuid)required
    • campaignIdstring (uuid)required
    • rewardIdstring (uuid) | nullrequired
    • substringrequired

      1 to any characters

    • audience"student" | "graduate" | "apprentice" | "teacher" | "youth" | "under_26" | "over_60" | "custom_age" | "nhs" | "healthcare" | "social_worker" | "carer" | "police" | "fire" | "first_responder" | "emergency_services" | "military" | "veteran" | "key_worker" | "delivery_transport" | "supermarket" | "charity_worker" | "government" | "employee" | "disability" | "maternity" | "social_follow" | "strava" | "donor" | nullrequired
    • verifiedUntilstring (date-time) | nullrequired
    • emailstring (email) | nullrequired
    • fieldsobjectrequired
      • firstNamestring
      • dateOfBirthstring
      • occupationstring
    • consentTextstringrequired
    • consentVersionstringrequired
    • marketingConsentbooleanrequired
    • createdAtstring (date-time)required
  • nextCursorstring | nullrequired

ShopperExport

  • emailstring

    The address asked about, when it was one.

  • phonestring

    The number asked about (E.164), when it was one.

  • mode"live" | "test"required
  • identitiesobject[]required

    The shopper as the brand knows them.

    • substringrequired

      The per-brand shopper id.

    • emailstring | nullrequired
    • phonestring | nullrequired
  • verificationsobject[]required

    Verifications started on the brand.

    • idstringrequired
    • substringrequired

      The per-brand shopper id.

    • audiencestringrequired
    • methodstringrequired
    • statusstringrequired
    • campaignIdstring | nullrequired
    • verifiedAtstring (date-time) | nullrequired
    • expiresAtstring (date-time) | nullrequired
    • createdAtstring (date-time)required
  • usesobject[]required

    Each time a proof was used on the brand.

    • idstringrequired
    • verificationIdstring | nullrequired

      The brand's own verification it stood on (one of verifications), also when it recognised a proof the shopper made elsewhere; null when the brand has none.

    • campaignIdstring | nullrequired
    • reusedbooleanrequired
    • createdAtstring (date-time)required
  • rewardsobject[]required

    Rewards the brand issued them (without codes).

    • idstringrequired
    • substringrequired

      The per-brand shopper id.

    • campaignIdstringrequired
    • verificationIdstring | nullrequired

      The brand's own verification it stood on (one of verifications), also when it recognised a proof the shopper made elsewhere; null when the brand has none.

    • rewardTypestringrequired
    • countrystring | nullrequired
    • surfacestring | nullrequired
    • issuedAtstring (date-time)required
    • expiresAtstring (date-time) | nullrequired
  • capturesobject[]required

    What they shared with the brand after verifying.

    • idstringrequired
    • substringrequired

      The per-brand shopper id.

    • campaignIdstringrequired
    • rewardIdstring | nullrequired
    • fieldsobjectrequired
      • firstNamestring
      • dateOfBirthstring
      • occupationstring
    • marketingConsentbooleanrequired
    • consentTextstringrequired
    • marketingConsentTextstring | nullrequired
    • consentVersionstringrequired
    • createdAtstring (date-time)required
  • ordersobject[]required

    Orders the brand reported that name the shopper or carry their email's hash.

    • idstringrequired
    • externalIdstringrequired

      The brand's order id.

    • substring | nullrequired

      The per-brand shopper id.

    • matchstringrequired
    • campaignIdstring | nullrequired
    • rewardIdstring | nullrequired
    • statusstringrequired
    • currencystringrequired
    • totalintegerrequired

      Minor units.

    • refundedTotalintegerrequired

      Minor units.

    • placedAtstring (date-time)required

ShopperRequest

The shopper's email, or the phone they proved by SMS (staff lists).

  • emailstring

    The address the shopper proved; compared trimmed and lower-cased.

    0 to 254 characters · pattern ^[^\s@]+@[^\s@]+\.[^\s@]+$

  • phonestring

    The mobile number the shopper proved by SMS: E.164, or a UK number starting 0.

    0 to 32 characters · pattern ^\+?[\d\s().-]{7,31}$

ShopperErasure

  • erasedobjectrequired
    • capturesintegerrequired

      Captures deleted.

    • usesintegerrequired

      Verification uses deleted.

    • rewardsintegerrequired

      Rewards no longer linked to them.

    • eventsintegerrequired

      Funnel events no longer linked to them.

    • ordersintegerrequired

      Orders no longer linked to them (identity and email hash removed).

Order

  • idstring (uuid)required
  • externalIdstringrequired
  • orderNumberstring | nullrequired
  • mode"live" | "test"required
  • source"pixel" | "api" | "shopify"required
  • sourceNamestring | nullrequired
  • channel"online" | "in_store"required
  • status"placed" | "partially_refunded" | "refunded" | "cancelled"required
  • currencystringrequired
  • totalintegerrequired
  • taxinteger | nullrequired
  • discountTotalintegerrequired
  • refundedTotalintegerrequired
  • codesstring[]required
  • lineItemsobject[]required
    • skustring | nullrequired
    • quantityintegerrequired
    • priceintegerrequired
    • productIdstring
    • variantIdstring
    • collectionsstring[]
  • sessionIdstring | nullrequired
  • newCustomerboolean | nullrequired
  • countrystring | nullrequired
  • device"mobile" | "tablet" | "desktop" | nullrequired
  • matchobjectrequired
    • type"code_unique" | "code_shared" | "session" | "email" | "none"required
    • campaignIdstring (uuid) | nullrequired
    • rewardIdstring (uuid) | nullrequired
    • verificationIdstring (uuid) | nullrequired
    • audience"student" | "graduate" | "apprentice" | "teacher" | "youth" | "under_26" | "over_60" | "custom_age" | "nhs" | "healthcare" | "social_worker" | "carer" | "police" | "fire" | "first_responder" | "emergency_services" | "military" | "veteran" | "key_worker" | "delivery_transport" | "supermarket" | "charity_worker" | "government" | "employee" | "disability" | "maternity" | "social_follow" | "strava" | "donor" | nullrequired
    • substring | nullrequired
  • awinTransactionIdinteger | nullrequired
  • commissionBasisobjectrequired
    • channel"awin" | "direct"required
    • basisMinorintegerrequired

      0 to any

    • currencystringrequired
    • computedAtstring (date-time)required
  • placedAtstring (date-time)required
  • createdAtstring (date-time)required
  • updatedAtstring (date-time)required

OrderRequest

  • externalIdstringrequired

    1 to 100 characters

  • orderNumberstring

    1 to 100 characters

  • placedAtstring (date-time)
  • currencystringrequired
  • totalintegerrequired

    0 to 1000000000000

  • subtotalinteger

    0 to 1000000000000

  • taxinteger

    0 to 1000000000000

  • discountTotalinteger

    0 to 1000000000000

  • discountCodesstring[]

    up to 20 items

  • lineItemsobject[]

    up to 200 items

    • skustring | null

      1 to 200 characters

    • quantityintegerrequired

      1 to 100000

    • priceintegerrequired

      0 to 1000000000000

    • productIdstring | integer
    • variantIdstring | integer
    • collectionsstring[]

      up to 50 items

  • sessionIdstring

    pattern ^[A-Za-z0-9_-]{8,64}$

  • customerEmailstring

    0 to 254 characters · pattern ^[^\s@]+@[^\s@]+\.[^\s@]+$

  • newCustomerboolean
  • countrystring

    pattern ^[A-Za-z]{2}$

  • landingSitestring

    0 to 2048 characters

  • sourceNamestring

    1 to 100 characters

  • channel"online" | "in_store"
  • testboolean

OrdersPage

  • dataobject[]required
    • idstring (uuid)required
    • externalIdstringrequired
    • orderNumberstring | nullrequired
    • mode"live" | "test"required
    • source"pixel" | "api" | "shopify"required
    • sourceNamestring | nullrequired
    • channel"online" | "in_store"required
    • status"placed" | "partially_refunded" | "refunded" | "cancelled"required
    • currencystringrequired
    • totalintegerrequired
    • taxinteger | nullrequired
    • discountTotalintegerrequired
    • refundedTotalintegerrequired
    • codesstring[]required
    • lineItemsobject[]required
      • skustring | nullrequired
      • quantityintegerrequired
      • priceintegerrequired
      • productIdstring
      • variantIdstring
      • collectionsstring[]
    • sessionIdstring | nullrequired
    • newCustomerboolean | nullrequired
    • countrystring | nullrequired
    • device"mobile" | "tablet" | "desktop" | nullrequired
    • matchobjectrequired
      • type"code_unique" | "code_shared" | "session" | "email" | "none"required
      • campaignIdstring (uuid) | nullrequired
      • rewardIdstring (uuid) | nullrequired
      • verificationIdstring (uuid) | nullrequired
      • audience"student" | "graduate" | "apprentice" | "teacher" | "youth" | "under_26" | "over_60" | "custom_age" | "nhs" | "healthcare" | "social_worker" | "carer" | "police" | "fire" | "first_responder" | "emergency_services" | "military" | "veteran" | "key_worker" | "delivery_transport" | "supermarket" | "charity_worker" | "government" | "employee" | "disability" | "maternity" | "social_follow" | "strava" | "donor" | nullrequired
      • substring | nullrequired
    • awinTransactionIdinteger | nullrequired
    • commissionBasisobjectrequired
      • channel"awin" | "direct"required
      • basisMinorintegerrequired

        0 to any

      • currencystringrequired
      • computedAtstring (date-time)required
    • placedAtstring (date-time)required
    • createdAtstring (date-time)required
    • updatedAtstring (date-time)required
  • nextCursorstring | nullrequired

RefundRequest

  • refundIdstringrequired

    1 to 100 characters

  • amountintegerrequired

    1 to 1000000000000

  • reasonstring

    0 to 500 characters

  • lineItemsobject[]

    up to 200 items

    • skustring | null

      1 to 200 characters

    • quantityintegerrequired

      1 to 100000

    • amountinteger

      0 to 1000000000000

OrderPatch

object | object

FunnelReport

  • rangeobjectrequired
    • fromstring (date)required
    • tostring (date)required
    • daysintegerrequired
  • filtersobjectrequired
    • mode"live" | "test"required
    • marketstring | nullrequired
    • campaignsstring[]required
    • group"day" | "audience" | "campaign" | "country" | "device" | "shopper" | "placement"required
  • totalsobjectrequired
    • impressionsintegerrequired
    • opensintegerrequired
    • verificationsStartedintegerrequired
    • verifiedintegerrequired
    • repeatVerificationsintegerrequired
    • rewardsIssuedintegerrequired
    • ordersintegerrequired
    • returnedOrdersintegerrequired
    • redeemedintegerrequired
    • conversionnumber | nullrequired

      Verified / opens; null with no opens.

    • redemptionRatenumber | nullrequired

      Redeemed / rewards issued; null with no rewards.

    • returnRatenumber | nullrequired

      Returned orders / orders; null with no orders.

    • byCurrencyobject[]required

      Money per currency, never added across them.

      • currencystringrequired

        ISO 4217.

      • ordersintegerrequired
      • returnedOrdersintegerrequired
      • redeemedintegerrequired
      • revenueMinorintegerrequired

        Order totals less refunds, in minor units.

      • refundedMinorintegerrequired
      • aovMinorinteger | nullrequired
      • returnRatenumber | nullrequired
      • refundRatenumber | nullrequired

        Refunded / (revenue + refunded); null with none.

  • funnelobject[]required
    • step"impressions" | "opens" | "verificationsStarted" | "verified" | "rewardsIssued" | "ordered" | "redeemed"required
    • valueintegerrequired
  • rowsobject[]required
    • keystringrequired

      The group's value: a day, audience, campaign id (empty for none), ISO-2 country, device, new/returning/unknown, or placement type. Empty when unknown.

    • daystring (date)
    • labelstring | null

      Group campaign: the campaign's name.

    • impressionsintegerrequired
    • opensintegerrequired
    • verificationsStartedintegerrequired
    • verifiedintegerrequired
    • repeatVerificationsintegerrequired
    • rewardsIssuedintegerrequired
    • ordersintegerrequired
    • returnedOrdersintegerrequired
    • redeemedintegerrequired
    • conversionnumber | nullrequired

      Verified / opens; null with no opens.

    • redemptionRatenumber | nullrequired

      Redeemed / rewards issued; null with no rewards.

    • returnRatenumber | nullrequired

      Returned orders / orders; null with no orders.

    • byCurrencyobject[]required

      Money per currency, never added across them.

      • currencystringrequired

        ISO 4217.

      • ordersintegerrequired
      • returnedOrdersintegerrequired
      • redeemedintegerrequired
      • revenueMinorintegerrequired

        Order totals less refunds, in minor units.

      • refundedMinorintegerrequired
      • aovMinorinteger | nullrequired
      • returnRatenumber | nullrequired
      • refundRatenumber | nullrequired

        Refunded / (revenue + refunded); null with none.

  • refreshedAtstring (date-time) | nullrequired

    When the brand's reports were last brought up to date.

WebhookEndpoint

  • idstring (uuid)required
  • mode"live" | "test"required
  • urlstringrequired
  • descriptionstringrequired
  • events"verification.completed" | "reward.issued" | "capture.submitted" | "order.attributed" | "order.refunded" | "code_pool.low"[]required
  • status"active" | "disabled"required
  • disabledReasonstring | nullrequired

    gone (it answered 410), failing (72 hours of failures) or manual.

  • failingSincestring | nullrequired
  • secretHintstringrequired

    The last four characters of the signing secret.

  • previousSecretExpiresAtstring | nullrequired

    Until when the secret before the last rotation still signs alongside, if it does.

  • createdAtstringrequired
  • updatedAtstringrequired

WebhookSubscribeRequest

  • urlstringrequired

    0 to 4000 characters

  • event"verification.completed" | "reward.issued" | "capture.submitted" | "order.attributed" | "order.refunded" | "code_pool.low"
  • events"verification.completed" | "reward.issued" | "capture.submitted" | "order.attributed" | "order.refunded" | "code_pool.low"[]

    up to 6 items

  • descriptionstring

    0 to 200 characters

WebhookEvent

  • idstringrequired

    evt_ and 26 characters.

  • type"verification.completed" | "reward.issued" | "capture.submitted" | "order.attributed" | "order.refunded" | "code_pool.low"required
  • createdstringrequired

    ISO 8601, UTC, whole seconds.

  • mode"live" | "test"required
  • brandstringrequired

    The brand's slug.

  • testtrue
  • dataobjectrequired

Location

  • idstringrequired
  • storeCodestringrequired

    The brand's own id for the shop.

  • namestringrequired
  • addressLine1string | nullrequired
  • addressLine2string | nullrequired
  • citystring | nullrequired
  • postcodestring | nullrequired
  • countrystringrequired
  • timezonestringrequired
  • status"active" | "archived"required
  • createdAtstring (date-time)required
  • updatedAtstring (date-time)required

InStoreClaimForTill

  • idstringrequired
  • codestringrequired
  • status"issued" | "redeemed" | "expired" | "void"required
  • mode"live" | "test"required
  • storeCodestringrequired
  • locationIdstringrequired
  • campaignobjectrequired
    • idstring (uuid)required
    • namestringrequired
  • valueTextstringrequired
  • issuedAtstring (date-time)required
  • expiresAtstring (date-time)required
  • redeemedAtstring (date-time) | nullrequired
  • redeemedVia"staff_pin" | "partner_api" | "order_match" | nullrequired

Widget JavaScript API

Add the script before </body>. It defines window.Vouchernaut and the custom elements below, and never throws into your page.

<script async src="https://vouchernaut.com/embed/v1/widget.js" data-key="vn_pk_test_…"></script>

Script attributes

  • data-keyrequired

    Your publishable key: vn_pk_test_… while you test, vn_pk_live_… when you go live. It only works on your allowed origins.

  • data-consent

    Set to "denied" and the widget writes nothing to localStorage. It never sets cookies either way.

  • data-debug

    Logs what the widget does to the browser console.

  • data-api

    Another API origin, for local work. Only honoured with a test key.

Methods

  • Vouchernaut.open({ campaign?, audience? })

    Opens the verify flow, for a campaign and audience if you name them.

  • Vouchernaut.close()

    Closes it.

  • Vouchernaut.on(event, handler) → off()

    Calls handler(detail) on an event (below) and returns a function that stops listening.

  • Vouchernaut.track(name, props?)

    Records a named event with flat properties (strings, numbers, booleans).

  • Vouchernaut.track('order', order)

    The order pixel: call it on your order confirmation page. Amounts are major units (12.50). Each order id is sent once per page.

  • Vouchernaut.setCart(cart | null)

    The basket, for placement rules on basket value and products. null clears it.

  • Vouchernaut.setProduct(product | null)

    The product in view, for product messages and SKU targeting. null clears it.

  • Vouchernaut.setCustomer(customer | null)

    Whether the shopper is signed in and on your mailing list, and their wishlist, for placement rules on them. Leave out what you don't know. null forgets it.

  • Vouchernaut.config()

    Your live campaigns, placements and theme as the widget loaded them, or null before it's ready.

  • Vouchernaut.version

    The widget's version.

Code that runs before the script has loaded can queue itself:

(window.VouchernautReady = window.VouchernautReady || []).push((Vouchernaut) => {
  Vouchernaut.on("reward:issued", (detail) => console.log(detail.campaign));
});

setCart(cart)

Vouchernaut.setCart({
  value: 64.99,
  currency: "GBP",
  items: [{ sku: "TEE-BLK-M", qty: 2, price: 32.5 }]
});
  • valuenumberrequired

    0 to 10000000

  • currencystringrequired

    pattern ^[A-Za-z]{3}$

  • itemsobject[]required

    up to 200 items

    • skustringrequired

      1 to 200 characters

    • qtyintegerrequired

      1 to 100000

    • pricenumberrequired

      0 to 10000000

    • productIdstring | integer
    • variantIdstring | integer
    • collectionsstring[]

      up to 50 items

setProduct(product)

  • skustringrequired

    1 to 200 characters

  • pricenumberrequired

    0 to 10000000

  • currencystringrequired

    pattern ^[A-Za-z]{3}$

  • productIdstring | integer
  • variantIdstring | integer
  • collectionsstring[]

    up to 50 items

setCustomer(customer)

Vouchernaut.setCustomer({
  loggedIn: true,
  subscribed: false,
  wishlist: ["TEE-BLK-M", "HOODIE-GRY-L"]
});
  • loggedInboolean
  • subscribedboolean
  • wishliststring | integer[]

    up to 200 items

track("order", order)

Vouchernaut.track("order", {
  id: "100234",
  total: 42.5,
  currency: "GBP",
  codes: ["STUDENT10"],
  items: [{ sku: "TEE-BLK-M", qty: 1, price: 42.5 }]
});
  • idstring | integerrequired
  • totalnumber | stringrequired
  • currencystringrequired
  • codesstring[]

    up to 20 items

  • itemsobject[]

    up to 200 items

    • skustringrequired

      1 to 200 characters

    • qtyintegerrequired

      1 to 100000

    • pricenumber | stringrequired
    • collectionsstring[]

      up to 50 items

Events

Listen with Vouchernaut.on(name, handler), or for vouchernaut:<name> on document. An event's detail holds ids such as campaign and audience, never an email, phone number, code or token.

  • ready

    The widget has loaded your configuration.

  • unavailable

    A button or offer can't be shown: the campaign isn't live, or isn't available right now.

  • open

    The verify flow opened.

  • close

    It closed.

  • verify:start

    A shopper started verifying.

  • verify:success

    They verified.

  • verify:fail

    Verification didn't go through.

  • reward:issued

    They were given the reward.

  • reward:copied

    They copied the code.

  • audience:select

    They chose which group they belong to.

  • placement:impression

    A placement was shown.

  • placement:click

    A placement was clicked.

  • error

    Something failed, with a code and a requestId.

Elements

  • <vouchernaut-button>campaign, audience, label

    A button that opens the verify flow. Its label defaults to "Verify for exclusive discounts".

  • <vouchernaut-verify>campaign, audience

    The verify flow inline on your page, rather than in a popup.

  • <vouchernaut-slot>type, sku, price, currency, product-id, variant-id

    Where a product message renders; with a sku it also sets the product in view.

  • <vouchernaut-hub>

    Your offer hub, rendered in place. For search engines, include the server-rendered HTML instead.