MezaTapMezaTapDevelopers
QuickstartReferenceOpenAPIDashboard

MezaTap Connect · API 2026-10-15

Stamp loyalty cards from any till.

The cashier scans or types the customer's code, your POS sends one call, and MezaTap adds the right stamps by the shop's own rules. The wallet card on the customer's phone updates in seconds.

Start in 3 steps OpenAPI 3.1
  1. 1Pair onceTill shows a code, manager approves it in MezaTap. No secrets typed.
  2. 2Press the buttonCashier scans the wallet QR or types the short code, phone or email.
  3. 3Stamps landMezaTap applies the rule, blocks duplicates and fraud, updates the wallet.

Start

OverviewPick your pathQuickstartPOS checklist

Connect

Pair a tillSend the productsStay in syncPermissionsFind the customer

Sell

Add stampsEarning rulesRetries & duplicatesRefundsRedeem rewards

No-code

Cashier screen

Events

Webhooks

Reference

EndpointsLimitsErrorsVersioningFor POS companies

Start

Overview

MezaTap Connect is a small REST API on https://mezatap.com/api/connect/v1. Requests and answers are JSON. Every till gets its own token, every shop decides what its tills may do, and every answer carries a message written so you can show it to the cashier as it is.

Secure by defaultTokens are stored hashed, scoped per till, and can be paused or revoked by the shop in one click.
Safe to retryIdempotency keys and receipt numbers mean a flaky network never stamps a sale twice.
Rules live in MezaTapPer visit, per amount spent or per product. The shop changes it, your code stays the same.

Start

Pick your path

Three ways to connect. Most POS companies start with the first and move to the second when they want it inside their own screens.

Least workCashier screenAdd a “Loyalty” button that opens a MezaTap link. The cashier scans the code on our screen and we hand control back to the till.POST /sessionsMost controlNative buttonYour till asks for the code and sends it with the sale. You show the answer in your own interface.POST /transactionsBack officeWebhooksGet a signed message whenever stamps are added, a card fills up, a reward is redeemed or someone joins.stamps.earned

Start

Quickstart

  1. Get a till token. Either pair the till (recommended for products you ship to many shops), or ask the shop owner to open Dashboard → POS & API → Connect with a token and paste the mtc_live_… token into your settings.
  2. Send the sale. When the cashier presses your loyalty button, send what they typed or scanned as code, plus your receipt number as orderRef and the total.
  3. Show the answer. On success show explanation and customer.balance.progressText. On an error show error.message. If customer.suggestedAction is offer_reward, the customer has a full card.

Treat the token like a password. Keep it on your server or in the till's secure storage, never in a web page.

Step 2 · the till button
curl -X POST https://mezatap.com/api/connect/v1/transactions \
  -H "Authorization: Bearer $MEZATAP_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: sale-R-1042" \
  -d '{
    "code": "4f2a9c1e",
    "orderRef": "R-1042",
    "totalCents": 1850,
    "currency": "EUR",
    "items": [{ "sku": "116", "quantity": 1 }, { "sku": "201", "quantity": 2 }],
    "cashier": { "ref": "emp-7", "name": "Sara" }
  }'

Start

POS checklist

Everything a till has to do so the shop's loyalty button just works, including after the manager changes a rule.

  1. Pair. Show the code from POST /pairings, poll for the token, store it. The token answer lists nextSteps.
  2. Send every product. PUT /catalog with the whole menu straight after pairing, then again whenever a product or category is added, renamed, re-priced or removed. Shops pick the categories and products that earn stamps from this list. Sales are never refused for a missing list: MezaTap also learns products from the lines you sell, and the manager can import the POS's product export. Sending the list is still the best way.
  3. Load the setup on every page load. GET /me when the till screen opens and every refreshAfterSeconds (60) with If-None-Match. A new pairing or a rule change shows up after a page refresh, no restart and no new token.
  4. Draw the button from the answer. Show the loyalty button when cards is not empty and cashier.canStamp is true. Print cards[].today.text (or summary) on it, in the shop's language. Hide controls the manager switched off in cashier.
  5. Send what was sold. Every sale carries items[] with the same sku you used in the catalog, plus totalCents. MezaTap fills in names and categories from the catalog and applies today's rule in the shop's timezone. Lines with only a name work too; send category with them so category rules match. Without items, a product rule answers ITEMS_REQUIRED.
  6. Show the answer. explanation on success, error.message otherwise. Both are written for the cashier.

Connect

Pair a till

Pairing works like signing in to a TV app: the till shows a short code, the shop manager approves it from their MezaTap account, and the till receives its token. Nobody copies secrets, and the manager decides the branch and permissions.

  1. POST /pairings with a deviceName. Show userCode big on the till, or render qrSvg so the manager can scan it.
  2. The manager opens https://mezatap.com/connect (or scans the QR, which opens verificationUrlComplete), checks the till and taps Approve.
  3. Meanwhile, poll POST /pairings/token every interval seconds. You get 202 while waiting and 200 with the token once. A second poll after that returns PAIRING_CLAIMED.

Codes expire after 10 minutes. Polling faster than interval returns SLOW_DOWN. If the manager declines, you get PAIRING_DENIED.

{
  "object": "pairing",
  "userCode": "B6DJ-MC29",
  "deviceCode": "mtc_dev_VaQacAkHcrt-yV-Es4_EtqZRY28…",
  "verificationUrl": "https://mezatap.com/connect",
  "verificationUrlComplete": "https://mezatap.com/connect?code=B6DJ-MC29",
  "qrSvg": "<svg …>",
  "expiresIn": 600,
  "interval": 3,
  "partner": { "name": "Your POS", "verified": true }
}

Tokens at a glance

PrefixWhat it isLifetime
mtc_live_Till token. Send as Authorization: Bearer ….Until the shop disconnects the till
mtc_dev_Device code, only used to poll for the till token.10 minutes
mtc_ses_Inside a cashier screen link. Opens one sale.15 minutes, one sale
whsec_Webhook signing secret.Until the shop rotates it
curl -X POST https://mezatap.com/api/connect/v1/pairings \
  -H "Content-Type: application/json" \
  -d '{"deviceName": "Till 1", "posName": "Your POS", "clientId": "your-client-id"}'

Connect

Send the products

The till is the source of truth for the menu. Send all of it with PUT /catalog: one line per product with your own sku (product id), its name, and its category plus categoryId when you have one. The manager then ticks, for example, the category Main dishes in MezaTap and only sales with a main dish earn stamps.

  • PUT replaces this till's list. Products it sent before and leaves out now are switched off, not deleted, so old receipts still read well.
  • PATCH sends only what changed. "active": false removes one product.
  • Up to 5,000 products and 2 MB per PUT. SKUs compare case-insensitively; the same SKU from two tills of one shop is one product.
  • Prefer categoryId: rules point at cat:<categoryId>, so renaming a category in the till keeps the rule working.
  • GET /catalog shows what MezaTap has, grouped by category, how many came from each source (bySource: till, sales, import) and when this till last synced.
  • Optional, never blocking. Products sold before the first sync are learned from the sale's items and show up for the manager; once the till sends the same product with its sku, the till's line wins.
  • Every sale, stamped or not, appears in the shop's till log with the reason (for example no qualifying product or PROGRAM_REQUIRED), so the manager can see why a customer got no stamp.
PUT /catalog
curl -X PUT https://mezatap.com/api/connect/v1/catalog \
  -H "Authorization: Bearer $MEZATAP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "sku": "116", "name": "Sahawiq burger", "category": "Main dishes", "categoryId": "12", "priceCents": 1250 },
      { "sku": "117", "name": "Shawarma plate", "category": "Main dishes", "categoryId": "12", "priceCents": 1450 },
      { "sku": "201", "name": "Cola", "category": "Drinks", "categoryId": "3", "priceCents": 300 }
    ]
  }'

Connect

Stay in sync

GET /me is the till's whole loyalty setup in one answer: the cards, today's rule already worded in the shop's language, which products count, what the cashier may do, and whether setup is finished. It carries an ETag; send it back as If-None-Match and an unchanged setup is an empty 304.

  • Call it on every page load and every refreshAfterSeconds. When the manager changes a rule, a day, a product list or a permission, the next call returns the new setup.
  • setup.nextSteps lists what is still recommended, such as sync_catalog (required: false). setup.complete is true as soon as the till is paired: nothing blocks stamps.
  • cards[].today has the weekday (0 is Monday, in shop.timeZone), today's stamps and a ready line such as “Today (Monday): 2 stamps per visit”. closed: true means no stamps today.
  • cashier is what the manager allows on this till: canStamp, canRedeem, canChooseStamps, canPickItems and canUndo (within undoWindowSeconds).
  • A 401 means the shop disconnected or paused the till: hide the loyalty button and offer to pair again.
The till's loyalty loop
let config = null;
let etag = null;

// On page load, then every refreshAfterSeconds (60). A 304 costs nothing.
export async function refreshLoyalty() {
  const res = await fetch("https://mezatap.com/api/connect/v1/me", {
    headers: { Authorization: `Bearer ${token}`, ...(etag ? { "If-None-Match": etag } : {}) },
  });
  if (res.status === 304) return config;
  if (res.status === 401) return (config = null); // disconnected: hide the button
  config = await res.json();
  etag = res.headers.get("ETag");
  if (config.setup.nextSteps.some((s) => s.id === "sync_catalog")) await syncMenu(loadProducts());
  renderLoyaltyButton(config); // config.cards[0].today.text, .summary, config.cashier
  return config;
}

setInterval(refreshLoyalty, 60_000);
document.addEventListener("visibilitychange", () => document.visibilityState === "visible" && refreshLoyalty());

Connect

Permissions

A till asks for permissions when it pairs. The manager can switch them off before approving, or later from the dashboard. Missing one returns INSUFFICIENT_SCOPE.

ScopeAllows
customers:readLook customers up and read their balance. Always included when one of the others is granted.
stamps:writeAdd stamps, void them on refunds and open the cashier screen.
rewards:redeemRedeem full cards for the reward.

Call GET /me when the till starts. It tells you the shop, its branches, its cards and how each card earns, so you can show “1 stamp per €10” on your button before anyone presses it.

GET /me
curl https://mezatap.com/api/connect/v1/me -H "Authorization: Bearer $MEZATAP_TOKEN"

Connect

Find the customer

Send exactly what the cashier entered as code. MezaTap recognises the wallet QR content, the card number, the short code printed under the QR, a phone number in any format, or an email address. You never need to parse it.

  • POST /identify is optional. Use it if you want to show the customer's name and balance before the sale is final.
  • If a phone or email matches several cards, writes return AMBIGUOUS_CUSTOMER with a matches list. Let the cashier pick one and send its customerId instead of code.
  • Shops with more than one card may need a programId. You get PROGRAM_REQUIRED with the options when that is the case.
  • We only return the first name. Phone numbers and emails never leave MezaTap.
curl -X POST https://mezatap.com/api/connect/v1/identify \
  -H "Authorization: Bearer $MEZATAP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"code": "+31 6 52265006"}'

Sell

Add stamps

POST /transactions is the whole integration for most tills. Send the customer, your receipt number and what was sold. MezaTap decides how many stamps that is worth.

FieldRequiredMeaning
code / customerIdOne of themWhat was scanned or typed, or the id from a previous match.
orderRefStrongly advisedYour receipt id. The same orderRef never earns twice, and refunds can find it.
totalCents, currencyFor spend rulesSale total in minor units: 1850 is 18.50.
items[]Always, when you have themsku and quantity are enough once the catalog is synced. Before that, send name and category too: MezaTap learns the products from them. categoryId and unitPriceCents are optional.
occurredAtFor offline queuesISO time of the sale, with offset. Day rules use it when it is within the last 24 hours.
cashierNo{ ref, name } so the shop sees who stamped.
stampsNoIgnored unless the shop allows the till to choose.
locationIdNoOnly for tills not tied to one branch.

A sale that earns nothing (for example below the minimum spend) is not an error: you get 200 with status: "skipped" and an explanation to show. Use POST /transactions/preview with the same body to show what a sale would earn without writing anything.

201 Created
{
  "object": "transaction",
  "id": "cmv8y9jcz001chr02usunl7tv",
  "status": "completed",
  "kind": "earn",
  "orderRef": "R-1042",
  "stampsAdded": 2,
  "explanation": "2 stamps for this visit (Monday rule).",
  "rule": {
    "mode": "visit",
    "reason": "visit",
    "capped": false,
    "dailyCapped": false,
    "weekday": 0,
    "perUnit": 2,
    "dayAdjusted": true,
    "qualifyingItems": [0]
  },
  "customer": {
    "matchedBy": "card",
    "customer": { "id": "cmv8wjwxb…", "firstName": "Lina" },
    "card": {
      "programId": "cmv8vhmn7…",
      "name": "Aden Stempelkaart",
      "stampsRequired": 10,
      "rewardText": "1 free shawarma"
    },
    "balance": {
      "stamps": 14,
      "rewardsAvailable": 1,
      "stampsToNextReward": 6,
      "progressText": "4/10"
    },
    "suggestedAction": "offer_reward"
  },
  "rewardUnlocked": false,
  "flaggedForReview": false,
  "createdAt": "2026-10-15T02:59:03.876Z"
}

Sell

Earning rules

Each card has one rule the shop sets in its dashboard. Your request stays the same whichever rule is active; just send what you know about the sale.

Per visitA fixed number of stamps per sale. Nothing else needed.{ "mode": "visit", "stampsPerVisit": 1 }
Per amount spentOne stamp per step, for example per €10. Needs totalCents.{ "mode": "spend", "spendStepCents": 1000 }
Per productStamps per matching item, by SKU, category or name. Needs items.{ "mode": "items", "itemMatch": ["cat:12", "sku:116"] }

On top of the mode, a shop can add:

  • Days. days is seven numbers, Monday to Sunday, replacing the per-visit, per-step or per-product number on that day. [2, 2, 2, 2, 1, 1, 1] is “Mon–Thu 2 stamps, Fri–Sun 1”. 0 means no stamps that day (reason: closed_day). The day is read in the shop's timezone.
  • Required products. With requireItems: true, a visit or amount card only stamps when the sale has at least one product from itemMatch, for example a main dish (reason: missing_required_item otherwise).
  • Matching. cat:12 matches a category id or name, sku:116 one product, and plain text matches SKU, name or category. Case-insensitive.

Every rule also has a minimum spend, a maximum per sale, and on top of that MezaTap's fraud limits: one stamp event per customer per minute and at most 10 stamps per customer per day. The rule object in the answer says which part applied: reason, weekday, perUnit, dayAdjusted and qualifyingItems (indexes into your items). You never need to copy these rules into your till: read /me to show them and let MezaTap apply them.

Sell

Retries & duplicates

Networks drop. Two independent safety nets make retrying always safe:

  • Idempotency-Key header. Any unique string of 8 to 120 characters. Send the same key again and you get the first answer back, with no second write. The same key with a different body returns IDEMPOTENCY_CONFLICT.
  • orderRef. A receipt that already earned is refused with ORDER_ALREADY_PROCESSED, and the original transaction is attached so you can show it.

Rule of thumb: build the key from your sale id (sale-R-1042) and retry on timeouts, 429 and 5xx with the same key.

409 Conflict
{
  "error": {
    "code": "ORDER_ALREADY_PROCESSED",
    "message": "This orderRef was already processed. The original transaction is attached.",
    "docs": "https://mezatap.com/developers#error-order_already_processed",
    "transaction": { "object": "transaction", "id": "cmv8y9jcz…", "status": "completed", "stamps": 1 }
  },
  "requestId": "req_2H0aLiqMwTgW"
}

Sell

Refunds

When a sale is refunded, void it by orderRef or transactionId. The stamps come off the card. If those stamps had already been spent on a reward, MezaTap takes back what it can and tells you in reversedStamps. Voiding a redemption puts the stamps back (restoredStamps).

Transactions can be voided for 30 days. The shop can also undo a till transaction from its dashboard; you'll get a transaction.voided webhook.

curl -X POST https://mezatap.com/api/connect/v1/transactions/void \
  -H "Authorization: Bearer $MEZATAP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"orderRef": "R-1042", "reason": "Refund"}'

Sell

Redeem rewards

When a match says suggestedAction: "offer_reward", offer the reward. If the customer accepts, call POST /redemptions. It takes one full card worth of stamps per count. Asking for more than the customer has returns NOT_ENOUGH_STAMPS.

Use an Idempotency-Key here too, so a double tap never redeems twice.

curl
curl -X POST https://mezatap.com/api/connect/v1/redemptions \
  -H "Authorization: Bearer $MEZATAP_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: reward-R-1043" \
  -d '{"customerId": "cmv8wjwxb000clfnj0zwn9te7", "count": 1, "orderRef": "R-1043"}'

No-code

Cashier screen

Don't want to build any screens? Create a session for the sale and open its url in a popup, iframe or web view. The cashier scans or types the customer's code there, sees the balance, confirms, and can redeem a reward. MezaTap follows the shop's language and colours automatically.

  • The link works once, for 15 minutes, and needs no login. Create it from your server: it carries the till's rights for that one sale.
  • The screen shows today's rule and the order lines you sent in items. If you sent none and the rule depends on products, the cashier ticks the qualifying products from the catalog, when the manager allows it for this till.
  • Right after stamping, the cashier can undo for 10 minutes (if allowed). The stamps come off, the sale reopens and a transaction.voided webhook is sent.
  • When the cashier is done, the screen sends a postMessage to the window that opened it and to the React Native bridge, then follows returnUrl if you gave one.
  • returnUrl gets mezatap_session, mezatap_status (completed or canceled) and mezatap_transaction added. App deep links such as yourpos://done work.
  • Always confirm on your server with GET /sessions/{id} or the stamps.earned webhook before you trust the result.
Hear when it's done
const session = await createSession(sale); // POST /sessions from your backend
const screen = window.open(session.url, "mezatap", "width=480,height=760");

window.addEventListener("message", (event) => {
  if (event.origin !== "https://mezatap.com" || event.data?.source !== "mezatap") return;
  if (event.data.type === "session.completed") {
    // { sessionId, transactionId, stampsAdded, rewardUnlocked }
    markSaleStamped(event.data.transactionId);
  }
  if (event.data.type === "session.canceled") showOnTill("No stamps for this sale");
});
curl -X POST https://mezatap.com/api/connect/v1/sessions \
  -H "Authorization: Bearer $MEZATAP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "orderRef": "R-1043",
    "totalCents": 1250,
    "currency": "EUR",
    "returnUrl": "https://pos.example.com/loyalty-done"
  }'

Events

Webhooks

The shop owner adds your HTTPS endpoint under Dashboard → POS & API → Webhooks, picks the events and gets a whsec_… secret once. Each delivery is a JSON POST signed with that secret.

EventWhen
stamps.earnedStamps were added, by a till or by staff with the MezaTap scanner.
reward.unlockedA card just became full. Good moment to tell the customer.
reward.redeemedA reward was handed out.
transaction.voidedA till transaction was undone (refund, mistake).
customer.joinedSomeone added the shop's card to their wallet.
card.updatedPoints, balance, tier, membership, pack or coupon changed at the counter.
pingSent when the shop presses “Send test”.

Verify the signature

MezaTap-Signature: t=<unix seconds>,v1=<hex> where v1 is HMAC-SHA256 of `${t}.${rawBody}` with your secret. Reject timestamps older than 5 minutes to stop replays, and compare in constant time.

import crypto from "node:crypto";

// rawBody must be the exact bytes you received, before any JSON parsing.
export function verifyMezaTap(rawBody, header, secret, toleranceSeconds = 300) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.trim().split("=")));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
  const given = Buffer.from(parts.v1 ?? "", "hex");
  return given.length === expected.length && crypto.timingSafeEqual(given, expected);
}

// Express
app.post("/hooks/mezatap", express.raw({ type: "application/json" }), (req, res) => {
  if (!verifyMezaTap(req.body.toString("utf8"), req.get("MezaTap-Signature") ?? "", process.env.MEZATAP_WEBHOOK_SECRET)) {
    return res.sendStatus(400);
  }
  const event = JSON.parse(req.body);
  res.sendStatus(200); // answer fast, do the work after
  handle(event);
});

Delivery

  • Answer with any 2xx within 8 seconds. Do slow work after you respond.
  • Failed deliveries are retried after 1 min, 5 min, 30 min, 2 h, 6 h, 24 h. After 30 failures in a row the endpoint is switched off and the shop is told in its dashboard.
  • Events can arrive twice or out of order. Use MezaTap-Event-Id to skip ones you already handled.
POST to your URL
POST /hooks/mezatap HTTP/1.1
Content-Type: application/json
User-Agent: MezaTap-Webhooks/1.0
MezaTap-Event: stamps.earned
MezaTap-Event-Id: evt_3kTq9yZx0aV1bN2c
MezaTap-Delivery: cmv8z1abc…
MezaTap-Signature: t=1792033143,v1=5f2b0c8e9d…

{
  "id": "evt_3kTq9yZx0aV1bN2c",
  "type": "stamps.earned",
  "apiVersion": "2026-10-15",
  "createdAt": "2026-10-15T02:59:03.912Z",
  "merchantId": "cmv8vhmn7…",
  "data": {
    "source": "pos",
    "transactionId": "cmv8y9jcz…",
    "orderRef": "R-1042",
    "stamps": 1,
    "connectionId": "cmv8x0kmx…",
    "locationId": "cmv8vhmn7…",
    "customer": { "id": "cmv8wjwxb…", "firstName": "Lina", "programId": "cmv8vhmn7…", "balance": { "stamps": 13, "rewardsAvailable": 1 } }
  }
}

Reference

Endpoints

Base URL https://mezatap.com/api/connect/v1. The machine-readable spec is at /api/connect/v1/openapi.json and imports straight into Postman, Insomnia or any OpenAPI code generator.

EndpointScopeDoes
POST /pairingsNo tokenStart pairing. Show the code on the till.
POST /pairings/tokenNo tokenPoll until the manager approves. Returns the till token once.
GET /meAnyThis till, its shop, cards, today's rule and what the cashier may do. Poll with If-None-Match.
PUT /catalogstamps:writeSend the till's whole product list. Right after pairing and whenever the menu changes.
PATCH /catalogstamps:writeAdd, change or remove (active: false) a few products.
GET /catalogcustomers:readHow many products MezaTap has, by category, and when this till last synced.
POST /identifycustomers:readFind a customer from whatever the cashier typed or scanned.
POST /transactions/previewcustomers:readWhat a sale would earn, without writing anything.
POST /transactionsstamps:writeAdd stamps for a sale. The till button.
GET /transactions?orderRef=customers:readFind the transaction for one of your receipts.
GET /transactions/{id}customers:readGet one transaction.
POST /transactions/voidstamps:writeTake a sale's stamps back after a refund.
POST /redemptionsrewards:redeemHand out a reward and take its stamps off the card.
POST /sessionsstamps:writeOpen MezaTap's hosted cashier screen for one sale.
GET /sessions/{id}AnyHow a cashier screen session ended.

Reference

Limits

Requests per till300 per minute. Over that you get 429 RATE_LIMITED with Retry-After.
Starting pairings10 per minute per IP address.
Same customerOne stamp event per minute (COOLDOWN) and 10 stamps per day (DAILY_LIMIT). Protects shops from mistakes and abuse.
Per saleThe shop's maximum, never more than 10 stamps.
Request body64 KB, up to 200 items.
Product catalog2 MB and 5,000 products per PUT, 1,000 per PATCH.

Reference

Errors

Errors always look the same: error.code to branch on, error.message to show, error.docs linking here, and a requestId to quote to support. Codes never change meaning within an API version.

CodeHTTPMeaning
UNAUTHORIZED401Missing or unknown token. Send Authorization: Bearer mtc_live_…
TOKEN_REVOKED401This till was disconnected by the shop. Pair it again.
CONNECTION_PAUSED403The shop paused this till. Ask the manager to resume it in MezaTap.
PARTNER_DISABLED403This POS integration is disabled. Contact MezaTap support.
INSUFFICIENT_SCOPE403This till is not allowed to do that. The manager can change its permissions.
MERCHANT_SUSPENDED403This shop's MezaTap account is suspended.
SUBSCRIPTION_BLOCKED403This shop's subscription needs attention before stamps can be added.
PLAN_REQUIRED403Till connections need Tap Elite or higher.
RATE_LIMITED429Too many requests. Slow down and retry after the Retry-After header.Safe to retry
INVALID_INPUT400The request body is not valid. See details.
INVALID_CODE400That doesn't look like a card code, phone number or email.
CUSTOMER_NOT_FOUND404No card found for that code in this shop.
CARD_INACTIVE410That card was removed from the wallet or closed.
AMBIGUOUS_CUSTOMER409More than one card matches. Pick one from matches and send its customerId.
PROGRAM_NOT_FOUND404No active card with that programId in this shop.
PROGRAM_REQUIRED409This shop has several cards. Send programId.
LOCATION_NOT_FOUND404Unknown locationId for this shop.
TOTAL_REQUIRED422This card earns stamps by amount spent. Send totalCents.
ITEMS_REQUIRED422This card's rule depends on the products sold. Send items: sku, or name with category.
COOLDOWN429This customer was stamped less than a minute ago.Safe to retry
DAILY_LIMIT429This customer reached today's stamp limit.
NOT_ENOUGH_STAMPS409The customer doesn't have a full card to redeem yet.
CARD_TYPE_UNSUPPORTED409This card keeps points, money, a membership, a pack or a coupon instead of stamps. Use the MezaTap scanner for it.
ORDER_ALREADY_PROCESSED409This orderRef was already processed. The original transaction is attached.
IDEMPOTENCY_CONFLICT409This Idempotency-Key was used with a different request.
TRANSACTION_NOT_FOUND404No transaction with that id or orderRef for this till's shop.
ALREADY_VOIDED409This transaction was already voided.
VOID_WINDOW_PASSED409Transactions can only be voided within 30 days.
PAIRING_DENIED403The shop manager declined this till.
PAIRING_EXPIRED410The pairing code expired. Start a new pairing.
PAIRING_CLAIMED410This pairing was already completed. Its token was handed out once.
SLOW_DOWN429Polling too fast. Wait `interval` seconds between polls.Safe to retry
UNKNOWN_CLIENT400Unknown clientId. Leave it out or ask MezaTap to register your POS.
SESSION_NOT_FOUND404This cashier link is unknown or expired.
SESSION_CLOSED410This cashier link was already used or canceled.
NOT_FOUND404Not found.
MAINTENANCE503MezaTap is in maintenance. Retry in a few minutes.Safe to retry
SERVER_ERROR500Something went wrong on our side. It is safe to retry with the same Idempotency-Key.Safe to retry

Reference

Versioning

Every answer carries MezaTap-Api-Version: 2026-10-15 and an X-Request-Id. We only add fields and error codes within a version, so ignore fields you don't know. Anything that would break a till ships as a new version path, announced well ahead.

Reference

For POS companies

Building this into a product other shops use? Ask us for a clientId and send it with POST /pairings. Managers then see your name with a verified badge when they approve a till, which makes them trust the connection. Unregistered tills still work, they just show as “not verified”.

Partner with MezaTapGet a client ID, a test shop and a direct line to our team.
Contact us
MezaTap Connect API 2026-10-15 · OpenAPI · Privacy