Start here — the ReGenerate Retailer API
From the invitation email to your first codes, on the sandbox, in this order. Everything here runs as printed once you set two lines; when it works on the sandbox, production is the same calls with a different address and key.
Two environments
| Sandbox | Production | |
|---|---|---|
| Portal | https://sandbox.regenerate.co.il | https://app.regenerate.co.il |
| API base URL | https://api-sandbox.regenerate.co.il/v1 | https://api.regenerate.co.il/v1 |
| Keys start with | rg_test_ | rg_live_ |
| Money | None: the Admin tops up a test wallet | Your real wallet |
| Codes | From the suppliers’ test systems, or marked as test codes | Real cards |
The sandbox has the same catalogue as production: the same public codes (RG-…), names, values and your prices. A key belongs to the environment that issued it, and each environment has its own portal, account and address list.
1. Sign in to the portal
Your account manager opens the account and you receive an invitation from no-reply@regenerate.co.il. It is written in Hebrew — the subject is “הזמנה לפלטפורמת ReGenerate” — and the link in it is valid for 72 hours and once.
The link asks for a password: at least 8 characters, with an upper-case letter, a lower-case letter, a digit and a symbol. Then it pairs an authenticator app (Google Authenticator, Microsoft Authenticator, 1Password or any TOTP app): scan the picture, or on the phone that holds the app press “Add to authenticator app”, or copy the key by hand. Enter the six digits it shows.
From then on every sign-in asks for a six-digit code, and so does every sensitive action in the portal: issuing, rotating or revoking a key, changing the address list or the Active switch, showing or downloading codes. A code works once; wait for the next one if you need two.
A link that expired can be sent again from the page it opens. Signing in, the portal opens on Overview.
2. The address list
Overview → API credentials → “Where your integration may call from”. List the addresses your integration calls from: single addresses, or blocks no wider than /24 (IPv4) or /64 (IPv6).
On the sandbox the list is optional: without one, any address may call. On production it is required: until a list is saved, every token request is refused with forbidden and a message naming this page; a saved production list can be replaced but not emptied.
List the address of the server that calls, not of the browser you are reading this in. From that server, curl -s https://checkip.amazonaws.com prints it. The portal shows the address your browser comes from, which is only the same when the browser and the integration share an office connection.
The Active switch beside the list stops every call with this environment’s keys at once, until it is switched on again.
3. Get a key
Overview → API credentials → “Issue a key” (or “Rotate key” when one exists), and a fresh code. The key is shown once, in that dialog: copy it into your secret store before closing. We keep a hash, not the key, so it cannot be shown again.
Rotating issues a new key and keeps the old one working for 24 hours, so a fleet of tills can move without an outage. Revoke, beside each key, stops that key at once — the key in the wrong hands case — and the tokens it issued stop with it.
4. The calls, in one shell
Run the first block — it sets BASE and asks for your key — then run the blocks in order in the same shell (bash or zsh); each uses what the ones before it set. You need curl and jq. Amounts are integers in minor units: agorot for shekels, cents for dollars.
Where to call, and your key. Paste it at the prompt rather than into the command line, where it would stay in your shell history.
# Once per shell. Sandbox first; for production change these two lines only.
BASE=https://api-sandbox.regenerate.co.il/v1
# Paste the rg_test_ key when asked; read -s keeps it off the screen and out of your history.
printf 'API key: '; read -rs KEY; echo
Trade the key for a token. It lasts an hour; when a call answers 401 unauthenticated, get a new one with the key and repeat the call.
# A token, valid for one hour. The key goes to this endpoint and no other.
TOKEN=$(curl -s -X POST "$BASE/auth/token" \
-H 'content-type: application/json' \
-d "{\"apiKey\":\"$KEY\"}" | jq -r .accessToken)
echo "token: ${TOKEN:0:12}..."
Your wallet.
# The wallet, in agorot: 200000 is ₪2,000.00.
curl -s "$BASE/balance" -H "authorization: Bearer $TOKEN"
The catalogue, priced for you. Each SKU is a public code: RG- and six characters, digits and capitals without 0, O, 1, I or L (RG-7KQ4MX). It is permanent, the same on the sandbox and on production, and means nothing — no supplier, product or value is encoded, so never parse it. Several SKUs can share a productName: a brand with many values is one product with a SKU per value, and you always order by SKU. A free-amount card has pricingMode: "range": you send requestedAmount inside minAmount…maxAmount, as a multiple of stepAmount, in the card’s own currency (faceCurrency). Some free-amount cards are also offered at ready values — those are ordinary fixed SKUs, ordered like any other. price is always in agorot; faceValue is in the card’s currency, and faceValueInIls gives it in agorot at the same rate. cardNote is the product’s note, the same on every SKU of it; valueNote is a note for that SKU only — show both where your customer chooses a card. Prices move with the exchange rate: read the catalogue every few minutes, not once a day.
# The catalogue at your price. One entry per SKU.
curl -s "$BASE/catalog" -H "authorization: Bearer $TOKEN" | jq '.items[0]'
# The cheapest fixed-value card you can buy now:
SKU=$(curl -s "$BASE/catalog" -H "authorization: Bearer $TOKEN" \
| jq -r '[.items[] | select(.available and .pricingMode == "fixed")] | sort_by(.price) | .[0].sku')
echo "sku: $SKU"
Before a large order: whether the SKU sells now, and maxQuantityPerOrder. A card worth more than 200 USD is sold one per order unless your account manager raised that for you; this is where the number shows.
# Whether it sells now, and how many you may order at once.
curl -s "$BASE/catalog/$SKU/availability" -H "authorization: Bearer $TOKEN"
Place the order. reference is yours: 8 to 128 letters, digits and . _ : -, unique per order. The answer is immediate and the order is created, before the supplier has delivered. Order any quantity your account allows — supplier batch sizes are ours to handle.
# Your reference is the idempotency key: unique per order, the same on every retry.
REF="TILL-0001-$(date -u +%Y%m%d%H%M%S)"
curl -s -X POST "$BASE/orders" \
-H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
-d "{\"sku\":\"$SKU\",\"quantity\":1,\"reference\":\"$REF\"}"
Send the same reference again — after a timeout, say — and you get the first order back with 200, never a second charge. The reference decides: a second request with the same reference and a different body still returns the first order, so compare sku and quantity in the answer if your code can send different orders under one reference.
# The same request again is the same order, not a second one: 200 instead of 201.
curl -s -o /dev/null -w '%{http_code}\n' -X POST "$BASE/orders" \
-H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
-d "{\"sku\":\"$SKU\",\"quantity\":1,\"reference\":\"$REF\"}"
Wait while the status is created or processing. delivered means the codes are ready. failed and refunded are final, and the wallet has already been credited. A webhook (POST /webhooks, in the reference) tells you instead of being asked.
# Poll until the order is final (or register a webhook and be told).
until curl -s "$BASE/orders/$REF" -H "authorization: Bearer $TOKEN" \
| jq -e '.status == "delivered" or .status == "failed" or .status == "refunded"' > /dev/null
do sleep 3; done
curl -s "$BASE/orders/$REF" -H "authorization: Bearer $TOKEN" | jq '{status, codesAvailable}'
Read the codes of a delivered order: card number, PIN where the card has one, a redemption link where the supplier gives one, expiry. Every retrieval is counted and recorded.
# The codes. Store them: this is a retrieval endpoint, not a vault.
curl -s "$BASE/orders/$REF/codes" -H "authorization: Bearer $TOKEN" | jq '.codes'
The orders of a day, newest first, 50 a page at most; total counts all matching orders.
# A day of orders, for reconciliation (dates are calendar days in Israel time).
curl -s "$BASE/orders?from=$(date -u +%Y-%m-%d)&to=$(date -u +%Y-%m-%d)&pageSize=50" \
-H "authorization: Bearer $TOKEN" | jq '{total, first: .items[0].reference}'
5. The same orders in the portal
Every order your integration places is in the portal under Order reports, with your reference beside it; search by reference or order id, and filter by dates and product. “Export to Excel” gives the list of orders — no codes in it, and no code needed.
The codes of an order are there too, behind a fresh code. An order of one card offers “Show code”: the code is on screen with a Copy button for 60 seconds, then hidden. An order of several cards offers “Download Excel”: the codes go into a file, one row per code, and never onto the screen.
6. Refunds and failures
An order moves created → processing → delivered. If the supplier refuses it, it goes to failed and then refunded, and the price is back in the wallet — check GET /balance. A short supplier outage is retried for up to fifteen minutes while the order stays processing; only then does it fail and refund.
A delivered order can still become refunded later, when the Admin refunds it on your request.
After failed or refunded, place a new order with a new reference if you still want the card. Never retry a refused or refunded order under its old reference: that returns the old order.
7. Every error, and what to do
Every refusal has the same body: {"error":{"code","message","requestId"}}. Branch on code; the message is for people and may change. Retry only what this table says is safe, and always with the same reference.
| Code | HTTP | What to do |
|---|---|---|
validation_failed | 400 | The body or query is malformed; error.details names the field. Fix the request; do not retry it as is. |
unauthenticated | 401 | No token, an expired token (they last an hour), or a key that is revoked or retired. Get a new token with the key; if the key itself is refused, issue a new one in the portal. |
forbidden | 403 | The message says which: the environment is switched off in the portal, production has no address list yet, the call came from an address not on your list, or the account is blocked. Fix it in the portal, or call your account manager for a blocked account. |
not_found | 404 | No such SKU or order in your account. Check the SKU against the catalogue; an order is found by its id or by your reference. |
conflict | 409 | The order is not ready for this — usually codes asked for before delivery. Wait for delivered, then ask again. |
insufficient_balance | 422 | The wallet holds less than the order costs. Nothing was charged. Top up through your account manager (on the sandbox the Admin tops up), then order again. |
quantity_limit_exceeded | 422 | More than one order may carry. Two limits: your account’s per-order quantity, and one card per order for a card worth more than 200 USD unless your account manager raised it. GET /catalog/{sku}/availability gives maxQuantityPerOrder; split the order. |
amount_out_of_range | 422 | A free amount outside minAmount…maxAmount, not a multiple of stepAmount, or above your account’s per-order ceiling. Send an amount inside the band. |
product_unavailable | 422 | The SKU is not selling at the moment. Read the catalogue again; do not retry the same SKU in a loop. |
pricing_rule_incompatible | 422 | A pricing set-up on our side does not fit this card. Not yours to fix: tell your account manager the SKU. |
rate_limited | 429 | Over 60 requests a minute on this key. Wait Retry-After seconds, then continue. |
internal_error | 500 | A failure on our side. Safe to retry with the same reference; quote error.requestId if it persists. |
supplier_unavailable | 502 | The card’s supplier could not be reached. Safe to retry with the same reference. |
supplier_rejected | 502 | The supplier refused the order. It is failed, then refunded, and the wallet is credited. Do not retry under the same reference — that returns the refused order; order again with a new reference, or another SKU. |
service_unavailable | 503 | A part of the platform is down for the moment. Retry later with the same reference. |
8. Rate limits
60 requests a minute per key. Every answer carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; a refusal is 429 with Retry-After in seconds. A reconciliation job on its own key cannot use up your tills’ allowance. A higher limit is a setting on your account: ask your account manager.
9. Moving to production
Change the base URL and the key, nothing else. The public codes, names, values and your prices are the same on both sides, so the code that works on the sandbox works on production unchanged.
Before the first call: sign in to the production portal (a separate invitation), save the address list there — production refuses every call without one — and issue an rg_live_ key. Production spends your real wallet and delivers real cards.
Request collection
The same calls as a Postman collection, with an environment for each side. Import all three, choose the sandbox environment, paste your key into apiKey, and run the requests in order: the token request stores the token, the catalogue request picks a SKU, and the order request makes its own reference.
- The collection
regenerate-retailer-api.postman_collection.json - Sandbox environment
regenerate-sandbox.postman_environment.json - Production environment
regenerate-production.postman_environment.json