מקצה לקצה: מפתח, טוקן, קטלוג, הזמנה וקודים. חמש קריאות. כל השאר במסמך הזה הוא פירוט של אחת מהן. פתחו מול סביבת הבדיקות — https://api-sandbox.regenerate.co.il/v1, מפתחות שמתחילים ב-rg_test_, מערכות הבדיקה של הספקים וללא כסף אמיתי — ואחר כך החליפו את כתובת הבסיס לייצור, https://api.regenerate.co.il/v1, עם מפתח שמתחיל ב-rg_live_. מפתח עובד רק בסביבה שהנפיקה אותו. הדוגמאות שלהלן משתמשות בייצור.
1. קבלת המפתח
מנהל הלקוח מנפיק אותו, ופורטל ReGenerate מציג אותו פעם אחת באותו רגע. לאחר מכן לא ניתן לשחזר אותו — אנחנו שומרים גיבוב ולא את המפתח — ולכן שמרו אותו מיד במנגנון הסודות שלכם.
אם המפתח אבד או נחשף, החליפו אותו מהפורטל. המפתח הקודם ממשיך לעבוד 24 שעות, כדי שתספיקו להטמיע את החדש בכל הסניפים בלי השבתה. אם הוא חייב להיעצר עכשיו — מפתח שהגיע לידיים הלא נכונות — בטלו אותו: הוא נדחה מאותו רגע, וכך גם הטוקנים שהונפקו איתו.
2. החלפת המפתח בטוקן
המפתח נשלח ל-endpoint אחד בלבד. כל שאר הקריאות נושאות טוקן שתוקפו פג בתוך שעה, וכך הסוד ארוך-הטווח כמעט אינו עובר ברשת.
כל הדוגמאות שלהלן שולחות $TOKEN, ולכן שמרו את הערך במקום שממנו שאר ההרצה יכולה לקרוא אותו.
curl -s -X POST https://api.regenerate.co.il/v1/auth/token \
-H 'content-type: application/json' \
-d '{"apiKey":"rg_live_4XK9ABCD_p7Qm2ZtVx8Ln4Rc9Wd3Yf6Hs"}'
# {"accessToken":"eyJpc3M...","tokenType":"Bearer","expiresIn":3600}
# שמרו אותו להמשך:
TOKEN=$(curl -s -X POST https://api.regenerate.co.il/v1/auth/token \
-H 'content-type: application/json' \
-d '{"apiKey":"rg_live_4XK9ABCD_p7Qm2ZtVx8Ln4Rc9Wd3Yf6Hs"}' \
| sed -n 's/.*"accessToken":"\([^"]*\)".*/\1/p')
3. קריאת הקטלוג
שורה אחת לכל SKU. המחיר הוא שלכם: אחוז המרווח של החשבון כבר כלול בו, כך שמה שאתם רואים הוא מה שההזמנה תעלה.
SKU הוא בעל ערך קבוע או בעל ערך פתוח. לקבוע יש ערך נקוב ומזמינים ממנו כמות. לפתוח יש pricingMode בערך range, ושולחים requestedAmount בתוך minAmount…maxAmount, בכפולות של stepAmount. faceValue הוא במטבע של הכרטיס עצמו, faceCurrency; price הוא תמיד בשקלים, ו-faceValueInIls הוא הערך הנקוב בשקלים לפי אותו שער.
שמרו במטמון לדקות ולא לשעות. המחירים נעים עם שער החליפין.
ההזמנה נענית מיד, לפני שהספק סיים. רשמו webhook והמתינו לעדכון, או בדקו את ההזמנה כל כמה שניות.
המשיכו לבדוק כל עוד הסטטוס הוא created או processing. עצרו ב-delivered ושלפו את הקודים; עצרו גם ב-failed או refunded — אלה סופיים, והארנק כבר זוכה. הטעות שכדאי להימנע ממנה היא להתייחס לכל מה שאינו processing כסופי: התשובה הראשונה היא created.
כל סכום הוא מספר שלם באגורות. 1234 הם ₪12.34. אין בממשק הזה סכומים בנקודה צפה, לא בכיוון אחד ולא בשני, כי שגיאת עיגול במחיר היא שגיאת עיגול בתשלום.
המפתח מגיע לחשבון שלכם בלבד
אין פרמטר שמרחיב את ההיקף. מזהה הזמנה של חשבון אחר יחזיר 404 ולא 403: השאלה אם הוא קיים אינה שאלה שהממשק הזה עונה עליה.
אין כאן endpoints של ניהול
הם אינם מוסתרים מאחורי הרשאה. הם פשוט אינם חלק מהממשק הזה, ולכן אין מה למצוא בניחוש נתיבים.
הזמנים ב-UTC
כל חותמת זמן היא ISO 8601 ב-UTC. סינון לפי תאריך מתבצע לפי ימי לוח בשעון ישראל, כי זה היום שחנות מתכוונת אליו כשהיא אומרת "היום".
שדות מתווספים
ייתכן שיופיע בתשובה שדה שלא היה שם בשבוע שעבר. פרסרו בסלחנות והתעלמו ממה שאינכם מכירים. שום שדה לא יוסר ולא ישנה טיפוס בלי קידומת גרסה חדשה.
אתם קובעים מאילו כתובות מותר לקרוא ל-API
בפורטל, תחת פרטי ה-API, רושמים את הכתובות שמהן ההתממשקות שלכם קוראת — כתובות בודדות, או בלוקים שאינם רחבים מ-/24 (IPv4) או מ-/64 (IPv6). לכל סביבה פורטל משלה, וכל פורטל מנהל רק את הסביבה שלו: הייצור ב-https://app.regenerate.co.il, סביבת הבדיקות ב-https://sandbox.regenerate.co.il. בייצור הרשימה היא חובה: עד שתישמר, כל בקשת טוקן בייצור נדחית ב-forbidden עם הודעה שאומרת היכן לשמור אותה, ורשימת ייצור שנשמרה אפשר להחליף אך לא לרוקן. רשימת סביבת הבדיקות היא רשות; בלעדיה אין שם הגבלה. כשקיימת רשימה, טוקן מונפק רק לכתובות שבה וכל קריאה נבדקת מולה; קריאה מכל מקום אחר נדחית ב-forbidden ונרשמת, כך שלהתממשקות שנדחתה יש תשובה מוכנה. לכל סביבה יש גם מתג הפעלה: כשהוא כבוי, כל קריאה עם מפתחות הסביבה נדחית ב-forbidden עד שיופעל שוב. כל שינוי — ברשימה, במתג, בהחלפת מפתח או בביטולו — דורש קוד חד-פעמי טרי מאפליקציית האימות.
הסתמכו על הקוד ולא על ההודעה
לכל שגיאה יש error.code יציב. ההודעה נועדה לאדם שקורא לוג ועשויה להיות מנוסחת מחדש בכל רגע.
שגיאות
לכל כשל אותו מבנה. code יציב וניתן להסתמך עליו; כדאי לתעד את requestId, כי ציטוט שלו מאפשר לנו לאתר את הקריאה המדויקת.
{
"error": {
"code": "insufficient_balance",
"message": "wallet balance 120000 is below the required 392400",
"requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
}
}
קוד
HTTP
המשמעות
validation_failed
400
The request body or query did not validate. `details` names the fields.
unauthenticated
401
No token, an expired token, or a key that is not valid.
forbidden
403
The account is blocked, the resource belongs to another account, the environment is switched off in the portal, production has no address list yet, or the call came from an address that is not on your allowlist for this environment. The message says which.
not_found
404
No such order, SKU or callback in your account.
conflict
409
The order is not in a state that allows this. Retrieving codes before delivery is the common case.
insufficient_balance
422
The order costs more than the wallet holds. Nothing was charged and no order was created.
quantity_limit_exceeded
422
Above the per-order quantity agreed for your account.
amount_out_of_range
422
A free-amount order outside the band the SKU allows.
product_unavailable
422
The SKU is not currently sellable.
supplier_unavailable
502
The upstream supplier could not be reached. Safe to retry with the same reference.
supplier_rejected
502
The supplier refused the order. The wallet was refunded automatically.
rate_limited
429
Above the per-key limit. Wait for `Retry-After` seconds.
internal_error
500
Something failed on our side. Safe to retry with the same reference.
מתי לנסות שוב
שגיאת 5xx או כשל רשת בטוחים לניסיון חוזר עם אותה אסמכתא. זו כל מטרתה: הניסיון החוזר או יוצר את ההזמנה, או מחזיר את זו שכבר נוצרה.
שגיאת 429 בטוחה לניסיון חוזר אחרי מספר השניות שב-Retry-After.
שגיאת 4xx אחרת תיכשל שוב. תקנו את הבקשה.
לעולם אל תנסו שוב עם אסמכתא חדשה אחרי timeout. זה אינו ניסיון חוזר אלא הזמנה שנייה.
הגבלת קצב
60req / min / key
שישים בקשות בדקה, לפי מפתח ולא לפי חשבון. תהליך התאמה לילי שרץ על מפתח משלו לא יוכל לכן לנצל את המכסה שהקופות זקוקות לה.
החלון קבוע ואורכו דקה. כל תשובה נושאת את מצבו, בין אם נדחתה ובין אם לא, כדי שתוכלו לווסת את הקצב במקום לגלות את התקרה בהתנגשות בה.
הספירה אחת. היא משותפת לכל השרתים שלנו שעונים לכם, ולכן המגבלה היא אותו מספר לא משנה לאיזו מכונה הגיעה הבקשה, ואינה עולה בשקט כשאנחנו מוסיפים כוח מחשוב.
אם דרושה מכסה גבוהה יותר, פנו למנהל הלקוח. זו הגדרה בחשבון ולא שינוי קוד.
X-RateLimit-Limit
מספר הבקשות המותר בחלון הנוכחי.
X-RateLimit-Remaining
כמה נותרו בו.
X-RateLimit-Reset
זמן Unix בשניות שבו החלון מתאפס.
Retry-After
ב-429 בלבד: כמה שניות להמתין.
עימוד
ה-endpoints המחזירים רשימה מקבלים page ו-pageSize. העימוד מתחיל ב-1 ו-pageSize מוגבל ל-50.
total סופר את כל השורות התואמות את הסינון ולא את השורות בעמוד הנוכחי, כך שמספר העמודים הוא ceil(total / pageSize).
התוצאות מסודרות מהחדש לישן. אם נכתבות שורות בזמן שאתם עוברים בין העמודים, עדיף לסנן לטווח תאריכים סגור, אחרת תראו שורה פעמיים.
Webhooks
הזמנה גדולה מסתיימת כשהספק מסיים אותה, ולעיתים זה דקות אחרי התשובה. אפשר לבדוק במחזוריות, וזה מתועד, אבל webhook פירושו שהמערכת שלכם מגיבה לאספקה במקום לשאול עליה.
ה-webhook לעולם אינו מכיל קוד. הוא מודיע שהזמנה הגיעה למצב מסוים; את הקודים שולפים אחר כך דרך הממשק. לכן endpoint שהוגדר בטעות אינו יכול להדליף לכם את המלאי.
הכתובת חייבת להיות https.
אנחנו חוזרים על מסירה שנכשלה שש פעמים במרווחים גדלים, לאורך כשעה. השיבו 2xx ברגע שקיבלתם את האירוע, ובצעו את העבודה אחר כך.
ייתכן שה-endpoint שלכם ייקרא יותר מפעם אחת לאותו אירוע. הפכו את הטיפול לאידמפוטנטי לפי orderId ו-event.
כל בקשה נושאת X-Regenerate-Signature, X-Regenerate-Timestamp ו-X-Regenerate-Event. החתימה היא HMAC-SHA256 על timestamp + "." + rawBody, עם הסוד שקיבלתם ברישום. השוו אותה בזמן קבוע, ודחו חותמת זמן ישנה מחמש דקות — חותמת הזמן נמצאת בתוך החומר החתום בדיוק כדי שקריאה שנלכדה לא תוכל לשוב ולהישלח אליכם לנצח.
import { createHmac, timingSafeEqual } from 'node:crypto';
function isOurs(rawBody, headers, secret) {
const timestamp = Number(headers['x-regenerate-timestamp']);
if (!Number.isFinite(timestamp)) return false;
if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
const expected = createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
const a = Buffer.from(expected, 'utf8');
const b = Buffer.from(headers['x-regenerate-signature'] ?? '', 'utf8');
return a.length === b.length && timingSafeEqual(a, b);
}
תיאור ה-Endpoints
Authentication
Trading a key for a short-lived token.
POST/v1/auth/tokenללא צורך בטוקן
Exchange an API key for a short-lived token
The only endpoint that accepts the API key itself. Everything else takes the token this returns, so the long-lived secret sitting in your points of sale crosses the wire as seldom as possible.
The token expires within the hour. Fetch a new one when it does, or shortly before; there is no refresh flow, because exchanging the key again is simpler than one.
The key may travel in the body or in an X-API-Key header, whichever suits your client.
פרמטרים
X-API-Key
כותרת
string
The key, as an alternative to sending it in the body.
{
"error": {
"code": "unauthenticated",
"message": "The access token is not valid.",
"requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
}
}
Catalog
What you can buy, and what it costs you.
GET/v1/catalog
Products, denominations and your price
One entry per SKU, which is the unit you order. price is what this order will cost your wallet, with your account’s margin already applied.
A SKU is either fixed-value or open-value. A fixed one has a faceValue and you order a quantity of it. An open one has pricing.mode of range and a band, and you send requestedAmount inside that band when you order.
Cache it for minutes rather than hours: prices move with the exchange rate.
{
"error": {
"code": "unauthenticated",
"message": "The access token is not valid.",
"requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
}
}
GET/v1/catalog/{sku}/availability
Availability before ordering
A cheap pre-check: whether the SKU is sellable, and the largest quantity your account may put in one order.
It is advisory. The binding check happens inside the order, in the same transaction that spends the money, so treat a true here as "worth trying" rather than as a reservation.
{
"error": {
"code": "unauthenticated",
"message": "The access token is not valid.",
"requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
}
}
404No such SKU.
errorחובה
object
{
"error": {
"code": "not_found",
"message": "No such SKU.",
"requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
}
}
Orders
Placing orders and collecting codes.
POST/v1/orders
Place an order
reference is your own idempotency key and it is required. Send the same reference again and you get the same order back with status 200 instead of 201 — never a second charge. This is what makes a till safe to retry after a timeout, which is the case that matters: a retry without one is how a retailer buys three hundred cards twice.
Quantity is bounded only by the per-order limit agreed for your account. Our suppliers have batch limits of their own — one of them delivers five cards at a time — and we split large orders internally, so a three-hundred-card order behaves like a three-card one.
The response is immediate and the order comes back created, before the supplier has answered. Wait for the callback, or poll while the status is created or processing, and fetch the codes once it is delivered.
גוף הבקשה
skuחובה
string
quantityחובה
integer
referenceחובה
string
Your idempotency key. Must be unique per order and stable across retries of that order.
requestedAmount
integer
Required on an open-value SKU, refused on a fixed one. Minor units, inside the SKU band.
200This reference was used before. The original order is returned and nothing was charged again.
orderIdחובה
string (uuid)
referenceחובה
string | null
The reference you sent.
skuחובה
string
productName
string
quantityחובה
integer
statusחובה
string
Non-terminal: `created`, which is what a new order comes back as, and `processing`. Terminal: `delivered`, `failed` and `refunded`, and the last two mean the wallet has already been put back. Poll while it is non-terminal. createdprocessingdeliveredfailedrefunded
unitPrice
integer (int64)
Price for one card. Minor units: 1234 is ₪12.34.
totalPriceחובה
integer (int64)
What was taken from the wallet. Minor units: 1234 is ₪12.34.
requestedAmount
integer | null
currencyחובה
string
ILS
codesAvailable
boolean
Whether `GET /v1/orders/{id}/codes` will return them now.
createdAt
string (date-time)
When the order was accepted. UTC, ISO 8601.
completedAt
string (date-time) | null
201The order was created and your wallet was debited.
orderIdחובה
string (uuid)
referenceחובה
string | null
The reference you sent.
skuחובה
string
productName
string
quantityחובה
integer
statusחובה
string
Non-terminal: `created`, which is what a new order comes back as, and `processing`. Terminal: `delivered`, `failed` and `refunded`, and the last two mean the wallet has already been put back. Poll while it is non-terminal. createdprocessingdeliveredfailedrefunded
unitPrice
integer (int64)
Price for one card. Minor units: 1234 is ₪12.34.
totalPriceחובה
integer (int64)
What was taken from the wallet. Minor units: 1234 is ₪12.34.
requestedAmount
integer | null
currencyחובה
string
ILS
codesAvailable
boolean
Whether `GET /v1/orders/{id}/codes` will return them now.
{
"error": {
"code": "validation_failed",
"message": "The request did not validate.",
"requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
}
}
401The access token is not valid.
errorחובה
object
{
"error": {
"code": "unauthenticated",
"message": "The access token is not valid.",
"requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
}
}
404No such SKU.
errorחובה
object
{
"error": {
"code": "not_found",
"message": "No such SKU.",
"requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
}
}
422The wallet does not hold enough. No order was created.
errorחובה
object
{
"error": {
"code": "insufficient_balance",
"message": "The wallet does not hold enough. No order was created.",
"requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
}
}
{
"error": {
"code": "unauthenticated",
"message": "The access token is not valid.",
"requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
}
}
GET/v1/orders/{id}
Order status
id may be our order id or the reference you sent. Both work, deliberately: a till whose request timed out knows only what it sent, and telling it to find our id first would be telling it to do the impossible.
פרמטרים
idחובה
נתיב
string
Our order id, or your own reference. TILL-4471-20260914-0007
תשובות
200The order.
orderIdחובה
string (uuid)
referenceחובה
string | null
The reference you sent.
skuחובה
string
productName
string
quantityחובה
integer
statusחובה
string
Non-terminal: `created`, which is what a new order comes back as, and `processing`. Terminal: `delivered`, `failed` and `refunded`, and the last two mean the wallet has already been put back. Poll while it is non-terminal. createdprocessingdeliveredfailedrefunded
unitPrice
integer (int64)
Price for one card. Minor units: 1234 is ₪12.34.
totalPriceחובה
integer (int64)
What was taken from the wallet. Minor units: 1234 is ₪12.34.
requestedAmount
integer | null
currencyחובה
string
ILS
codesAvailable
boolean
Whether `GET /v1/orders/{id}/codes` will return them now.
createdAt
string (date-time)
When the order was accepted. UTC, ISO 8601.
completedAt
string (date-time) | null
401The access token is not valid.
errorחובה
object
{
"error": {
"code": "unauthenticated",
"message": "The access token is not valid.",
"requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
}
}
404No such order in your account.
errorחובה
object
{
"error": {
"code": "not_found",
"message": "No such order in your account.",
"requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
}
}
GET/v1/orders/{id}/codes
Retrieve delivered codes
Available once the order is delivered. Asking earlier returns 409, not an empty list, so a polling loop cannot mistake "not yet" for "none".
Every retrieval is recorded against the key that made it. Store what you receive: this is a retrieval endpoint, not a vault, and treat the response as you would a card number.
{
"error": {
"code": "validation_failed",
"message": "The URL is not https, or no events were given.",
"requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
}
}
401The access token is not valid.
errorחובה
object
{
"error": {
"code": "unauthenticated",
"message": "The access token is not valid.",
"requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
}
}
GET/v1/webhooks
List your callbacks
Without the signing secrets, which are not recoverable.