Integrating Meimo Cards as a payment method
Your Meimo Business account has a Live API key. This is the complete path from that key to a real customer holding a Meimo Card and spending it down in your product — issuing your own cards, listing what customers can buy in-app, and charging a card at checkout. Four endpoints, no OAuth dance, no separate developer account.
api.meimopay.comAuth: Bearer API keySDK @meimopay/sdk-node1. What a Meimo Card is
A Meimo Card is a bearer stored-value instrument — a 16-digit card number and a 4-digit PIN are the entire access credential. Anyone holding both can check its balance and spend it down at checkout, no session or account required, exactly like a gift card.
Private card
Issued by your API key — spendable only through your own business, never at anyone else's.
Global card
Issued by Meimo itself — spendable at any business that opts into accepting global cards.
There's no separate template, brand, or entitlement model to configure — a card just has a tier (Meimo Card, Silver, Gold, Platinum, Black) and a starting balance you set at issuance.
2. Your Live API key
Every Meimo Business account has exactly one sign-in and can hold any number of API keys, in two environments.
- 1Sign in to your Business account at app.meimopay.com
The same phone/KYB-verified sign-in you already use — no separate developer account. - 2Open API Keys
Generate a key and choose LIVE or SANDBOX. The raw key is shown exactly once — store it in your secrets manager immediately. - 3Use it as a Bearer token
Every Partner API call isAuthorization: Bearer <your key>. No client id/secret or OAuth exchange for server-to-server calls.
401immediately — revoking is the right move the moment a key leaks, not a softer “disable” state.3. Authenticating requests
Which environment a call touches is decided entirely by which key you send — not a URL flag or request field.
curl https://api.meimopay.com/v1/listings \
-H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx"A LIVE key only ever creates and sees real cards; a SANDBOX key's cards are tagged isSandbox: true and fully isolated from your live view. Build and test your whole integration on a sandbox key first — the request/response shapes are identical.
4. Issuing a card
Mint a fresh private card, locked to your own business. The raw card number and PIN are only ever returned here, once — hand them to whoever will use the card (a gift, a physical card, an SMS).
import { MeimoPayClient } from "@meimopay/sdk-node";
const meimo = new MeimoPayClient({ apiKey: process.env.MEIMO_LIVE_KEY! });
const card = await meimo.issueCard({ tier: "MEIMO_CARD", balanceCents: 2000 });
// card.cardNumber and card.pin are shown here, once — save or hand them off nowcurl -X POST https://api.meimopay.com/v1/cards \
-H "Authorization: Bearer sk_live_xxxx" \
-H "Content-Type: application/json" \
-d '{
"tier": "MEIMO_CARD",
"balanceCents": 2000
}'A card issued through the API is the exact same row your Business Portal and the Meimo admin panel see — there's no separate “API card” bucket. It shows up immediately under Cards → Cards you've issued.
5. What you can sell in-app
A listing is a price sheet, not inventory — buying one mints a brand-new card on the spot with that listing's tier and balance. This call returns your own listings, plus every active global listing if your business has opted into accepting global cards.
| Call | Returns |
|---|---|
GET/v1/listings | Your own active listings, plus global listings if you accept global cards |
const listings = await meimo.listListings();
// each listing: { id, title, tier, balanceCents, priceCents, isGlobal, ... }Listings themselves are managed from the Business Portal's Cards page (create, price, activate/deactivate) — the Partner API only reads them, since a listing is a storefront concern, not something you'd script per-request.
6. Charging a card at checkout
Your customer hands you a card number + PIN at checkout — from a gift, a purchase, or a card they already hold. Debit it directly; the merchant charging is always resolved from your own API key, never from the request body.
const result = await meimo.pay({
cardNumber: "6602000000000017",
pin: "4821",
amountCents: 1840,
idempotencyKey: "order_9931-pay", // safe to retry with the same key
});
// result.remainingBalanceCents is the balance AFTER this chargecurl -X POST https://api.meimopay.com/v1/cards/pay \
-H "Authorization: Bearer sk_live_xxxx" \
-H "Content-Type: application/json" \
-d '{
"cardNumber": "6602000000000017",
"pin": "4821",
"amountCents": 1840,
"idempotencyKey": "order_9931-pay"
}'Rejects with CARD_NOT_ACCEPTED if the card is neither yours nor a global card you accept, INSUFFICIENT_FUNDS if the balance is too low, and CARD_EXPIRED past its validity window. Always pass a stable idempotencyKey — a repeated call with the same key returns the original result instead of double-charging.
7. Checking a balance
A pure bearer lookup — card number + PIN is the entire auth, no API key required. Handy for showing a balance before a customer commits to checkout.
| Call | Notes |
|---|---|
POST/cards/balance | Public — no Authorization header at all, just the card number + PIN in the body |
const balance = await meimo.checkBalance("6602000000000017", "4821");
// { tier, balanceCents, currency, status, isGlobal, businessId, expiresAt }8. Sandbox vs Live
One account, one API, two key types — not two separate systems to keep in sync.
Sandbox key
Every card issued is tagged isSandbox: true and invisible in your live dashboards.
Live key
Real cards, real customers, real balances. Nothing about the request shape changes — only which key you send.
Recommended order: build the whole flow (issue → pay → balance check) against a sandbox key, confirm it behaves the way you expect, then swap in the live key with zero code changes.
9. Errors
Every non-2xx response is JSON with a message field; the SDK throws MeimoPayApiError with the status and body attached.
| Status | Meaning |
|---|---|
401 | Missing, invalid, or revoked API key — or an incorrect PIN on a balance/pay lookup |
402 | INSUFFICIENT_FUNDS — the card's balance is too low for the requested amount |
403 | CARD_NOT_ACCEPTED — the card is neither yours nor a global card you accept |
404 | Unknown card number |
410 | CARD_EXPIRED — past its validity window |
409 | Card is not ACTIVE (already blocked, depleted, or closed) |
10. Go-live checklist
The section to hand your engineer directly.
- Generate a SANDBOX key and build the full flow against it first
- Always pass a stable idempotencyKey on pay calls — safe to retry on timeout
- Show a balance via checkBalance before a customer commits at checkout
- Handle CARD_NOT_ACCEPTED, INSUFFICIENT_FUNDS, and CARD_EXPIRED explicitly in your checkout UI
- Generate a LIVE key, store it in your secrets manager, and cut over with no other code changes
- Confirm the first real card shows up in Business Portal → Cards
Full API reference
Every endpoint, request/response shape, and error code — generated straight from the live API, always current.
Open the API docs →Questions building something? Get in touch or back to Developers.
