--- title: API Without the Widget tags: [api, rest, auth, otp, backend, no-widget] order: 100 --- Use Simply Club from your **own UI — your own login screen, app, or backend — no embedded JavaScript widget required.** Everything the Drop-in does over the wire is a plain REST call you can make yourself: log a shopper in with phone + OTP, get a session token, and read their member profile and benefits. > **In short** — Logging a shopper in is **two requests**: the first texts them a one-time code, the second sends that code back and returns a session **token**. You attach that token to every later request to act as that shopper. That's the whole integration — the exact URLs are in the steps below. --- ## Building your own UI This is the path for when you want your **own login screen** — a React component, a Vue page, a native mobile screen — instead of our widget. You draw the UI; you call these endpoints behind it. In practice it's **two screens**: 1. **Phone screen** — a phone input + a "Send code" button → calls `get-otp` (Step 1 below). 2. **Code screen** — a code input + a "Verify" button → calls `validate-otp` (Step 2 below). On success you hold the `access` token and the shopper is logged in. A few things that make this safe and easy from the front end: - **In a browser it just works.** If your page is served from an allow-listed domain, the browser sends the `Origin` header for you and CORS is already open to that domain — no extra config. - **There are no API keys or secrets** in this flow, so it's safe to run entirely client-side. The shopper's OTP is their credential. - **Store the `access` token** like any session token (e.g. `localStorage` or a cookie) and send it as `Authorization: Bearer` on later calls. The rest of this page is the exact calls those two screens make. --- ## The full cycle Login is just the first box. A complete **rewarded checkout** — the same thing the widget does — is five steps. Every call after login carries your `Authorization: Bearer ` token (and the `Origin` header). The boxes flow top to bottom; each one names the endpoint and what you get back: ``` ┌──────────────────────────────────────────────┐ │ 1. LOG IN │ │ POST /users/fast/get-otp/:poiId/:phone │ │ POST /users/fast/validate-otp/:poiId │ │ ⇒ access token (90 days) │ └───────────────────────┬────────────────────────┘ │ send the cart ▼ ┌──────────────────────────────────────────────┐ │ 2. START A TRANSACTION │ │ POST /transaction/create/:poiId │ │ body: { items: [...] } │ │ ⇒ available promos + benefits + totals │ └───────────────────────┬────────────────────────┘ │ shopper picks benefits in your UI ▼ ┌──────────────────────────────────────────────┐ │ 3. LOCK THE SELECTION │ │ POST /transaction/select-and-lock-benefits/ │ │ :poiId │ │ ⇒ confirmed discount for this cart │ └───────────────────────┬────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────┐ │ 4. GET THE TRANSACTION CODE │ │ GET /transaction/get-transaction-code/:poiId │ │ ⇒ { code } → the sc_transaction_code. │ │ Save it on your order. │ └───────────────────────┬────────────────────────┘ │ shopper pays on your checkout ▼ ┌──────────────────────────────────────────────┐ │ 5. AWARD POINTS (from your backend) │ │ POST /transaction/order-webhook │ │ body: order details + sc_transaction_code │ │ ⇒ points awarded, transaction closed │ └──────────────────────────────────────────────┘ ``` | Step | Endpoint | What it's for | Documented in | |---|---|---|---| | 1 | `fast/get-otp` + `fast/validate-otp` | Log the shopper in, get a session token. | §3 below | | 2 | `POST /api/v1/transaction/create/:poiId` | Send the cart; get the promos/benefits the shopper qualifies for. | §5.1 below | | 3 | `POST /api/v1/transaction/select-and-lock-benefits/:poiId` | Lock the benefits the shopper chose. | §5.2 below | | 4 | `GET /api/v1/transaction/get-transaction-code/:poiId` | Get the `sc_transaction_code` to attach to your order. | §5.3 below | | 5 | `POST /api/v1/transaction/order-webhook` | After payment, award points and close the transaction. | [**Webhook Integration Guide**](/content/dropins/webhook-integration-guide) | > Steps 2–4 are the part the widget normally runs for you via its benefits engine — full detail in **§5 (Run a rewarded checkout)**. They all take the same session `Bearer` token from step 1, so you can drive the entire flow from your own client. Step 5 is fired by your **backend** after the shopper pays — without it the shopper sees the discount but no points are awarded. --- ## 1. What you need | # | Requirement | How to get it | |---|---|---| | 1 | A `poiId` | From your Simply Club account manager — an opaque id like `6821f55303f86ff040461ef4`. | | 2 | **Fast login** enabled for that POI | You control this yourself — sign in to the [Dashboard](https://dropins.simplyclub.co.il) as the POI admin and flip the **fast login** toggle in your POI's **Settings**. (It sets `settings.fastLoginEnabled` on the POI; without it the fast routes return `403`.) | | 3 | Your calling domain on the POI's allowed list | If the POI restricts domains, give support the hostname(s) you'll send as the `Origin` header. See [the Origin rule](#2-the-one-rule-send-an-origin-header) below. | There is **nothing to install.** You're just calling HTTPS endpoints. ### Base URL | Environment | `BASE_URL` | |---|---| | Production | `https://dropins.simplyclub.co.il` | | Staging | `https://simply-drop-ins-2.onrender.com` | ### Enabling fast login yourself You don't need to ask anyone — a POI admin turns it on. The easiest way is the [Dashboard](https://dropins.simplyclub.co.il): sign in, open your POI's **Settings**, and toggle **fast login** on. If you'd rather automate it, it's two API calls: ``` # 1) Log in as the POI admin (env = test | prod) POST {BASE_URL}/api/v1/poi/login/:env Content-Type: application/json { "accountId": 2886, "username": "your-admin-user", "password": "your-password" } ``` The response includes your `poiId` and an admin token: ```json { "success": true, "data": { "token": { "token": "" }, "poi": { "_id": "" } } } ``` ``` # 2) Flip the flag — only this field changes, every other setting is left intact PATCH {BASE_URL}/api/v1/poi/:poiId/settings Authorization: Bearer Content-Type: application/json { "fastLoginEnabled": true } ``` That's it — fast login is now live for that POI. --- ## 2. The one rule: send an `Origin` header Simply Club scopes a POI to a set of allowed domains. If your POI has domains configured, **every** request — OTP and authenticated alike — must include an `Origin` (or `Referer`) header whose hostname is on that list. Browsers add this automatically; a backend HTTP client does not, so you must set it yourself: ``` Origin: https://your-allowed-domain.com ``` - If the POI has **no** domain restriction (or it's set to `*`), you can skip the header. - If it's restricted and you omit it, you get `403` with `ORIGIN_REQUIRED`. - If you send a hostname that isn't allowed, you get `403` with `DOMAIN_NOT_ALLOWED`. Use the **same** `Origin` value on the login call and on every call afterward — the session token is bound to it. --- ## 3. Log a shopper in (two calls) ### Step 1 — Request an OTP ``` POST {BASE_URL}/api/v1/users/fast/get-otp/:poiId/:phone Origin: https://your-allowed-domain.com ``` The server texts a code to the phone and returns an `otpChallenge` — a one-time token you must echo back in step 2. The code is valid for **120 seconds**. **200 response:** ```json { "success": true, "data": { "message": "OTP sent", "otpChallenge": "<64-hex-char string>", "isOTPSent": true }, "error": null } ``` ### Step 2 — Validate the OTP and get a token ``` POST {BASE_URL}/api/v1/users/fast/validate-otp/:poiId Origin: https://your-allowed-domain.com Content-Type: application/json { "otp": "123456", "otpChallenge": "" } ``` **200 response:** ```json { "success": true, "data": { "message": "OTP is valid", "access": "", "member": { /* the Simply Club member */ }, "isNewUser": false }, "error": null } ``` `access` is the session token. Store it — it's valid for **90 days**. `isNewUser` is `true` only when the member was just created (a new phone auto-creates a member). --- ## 4. Use the token Put the token in an `Authorization: Bearer` header (keep the same `Origin` header too). These are the endpoints you'll typically call: | Method & path | Returns | |---|---| | `GET /api/v1/users/my-self/:poiId` | The logged-in member's profile. | | `GET /api/v1/users/user-benefits/:poiId` | The member's available benefits. | | `POST /api/v1/users/ping/:poiId` | `{ authenticated: true|false }` — cheap way to check a token is still valid. | | `POST /api/v1/users/logout` | Ends the session (invalidates the token). | Example: ``` GET {BASE_URL}/api/v1/users/my-self/:poiId Authorization: Bearer Origin: https://your-allowed-domain.com ``` --- ## 5. Run a rewarded checkout These are the three calls between login and the order webhook (steps 2–4 of [the full cycle](#the-full-cycle)). All of them require `Authorization: Bearer ` plus the `Origin` header, and they operate on the shopper's open transaction. > The field names `selectedQuntity` and `maxQuntityAllowed` are spelled exactly like that in the API — match them verbatim. ### 5.1 Create a transaction (send the cart) ``` POST {BASE_URL}/api/v1/transaction/create/:poiId Authorization: Bearer Origin: https://your-allowed-domain.com Content-Type: application/json { "items": [ { "id": "1", "sku": "ABC-001", "title": "Olive Oil 500ml", "price": 39.9, "quantity": 2 } ] } ``` `price` is the **net** unit price after any store-side discounts. **200 response** — the benefits the shopper qualifies for: ```json { "success": true, "data": { "message": "Transaction created", "serviceTranNumber": "", "tranDiscountPercent": 0, "promos": [ { "id": 12345, "name": "10% off oils", "description": "...", "templateId": 0, "maxQuntityAllowed": 1, "unitPrice": 7.98, "selectedQuntity": 0, "isAuto": false } ] } } ``` Each entry in `promos` is a benefit the shopper can apply. `templateId` is `0` for a total discount, `1` for cashback; `selectedQuntity` starts at `0`. ### 5.2 Lock the chosen benefits Send back the promos the shopper picked — with `selectedQuntity` set to how many they want — along with the same cart `items`: ``` POST {BASE_URL}/api/v1/transaction/select-and-lock-benefits/:poiId Authorization: Bearer Origin: https://your-allowed-domain.com Content-Type: application/json { "items": [ { "id": "1", "sku": "ABC-001", "title": "Olive Oil 500ml", "price": 39.9, "quantity": 2 } ], "promos": [ { "id": 12345, "templateId": 0, "unitPrice": 7.98, "selectedQuntity": 1 } ] } ``` **200 response:** `{ "success": true, "data": { "message": "Benefits locked" } }` (or `"Tran check completed"`, depending on POI config) — either confirms the discount for this cart. > This needs the open transaction from 5.1. If there's no session yet you get `400` with `Session not found`. ### 5.3 Get the transaction code ``` GET {BASE_URL}/api/v1/transaction/get-transaction-code/:poiId Authorization: Bearer Origin: https://your-allowed-domain.com ``` **200 response:** ```json { "success": true, "data": { "code": "" } } ``` Save `code` (the `sc_transaction_code`) on your order — your backend sends it in the webhook below to award points. ### 5.4 Award points after payment (backend) Once the shopper pays on your checkout, your **backend** POSTs the order — including the `sc_transaction_code` from 5.3 — to close the transaction and award points: ``` POST {BASE_URL}/api/v1/transaction/order-webhook ``` The full payload (`topic`, `domain`, `sc_transaction_code`, `order_id`, `customer_id`) and signature details are in the [**Webhook Integration Guide**](/content/dropins/webhook-integration-guide). Without this call the shopper sees the discount but no points are awarded. --- ## 6. Full walkthrough with curl (login → checkout) The whole cycle as one runnable script. Paste the `otpChallenge` from call 1 into call 2, then the `access` token from call 2 into `ACCESS`: ```bash BASE_URL="https://dropins.simplyclub.co.il" POI_ID="your_poi_id" PHONE="+972501234567" ORIGIN="https://your-allowed-domain.com" # 1) Send the OTP — copy the otpChallenge from the response curl -s -X POST "$BASE_URL/api/v1/users/fast/get-otp/$POI_ID/$PHONE" \ -H "Origin: $ORIGIN" # 2) Validate it — returns { data: { access, member } } curl -s -X POST "$BASE_URL/api/v1/users/fast/validate-otp/$POI_ID" \ -H "Origin: $ORIGIN" \ -H "Content-Type: application/json" \ -d '{ "otp": "123456", "otpChallenge": "PASTE_CHALLENGE_HERE" }' # Reuse the session token for every call below ACCESS="PASTE_ACCESS_TOKEN_HERE" # 3) Create a transaction with the cart — returns { data: { promos: [...] } } curl -s -X POST "$BASE_URL/api/v1/transaction/create/$POI_ID" \ -H "Origin: $ORIGIN" -H "Authorization: Bearer $ACCESS" \ -H "Content-Type: application/json" \ -d '{ "items": [ { "id": "1", "sku": "ABC-001", "title": "Olive Oil 500ml", "price": 39.9, "quantity": 2 } ] }' # 4) Lock the benefits the shopper picked (set selectedQuntity) curl -s -X POST "$BASE_URL/api/v1/transaction/select-and-lock-benefits/$POI_ID" \ -H "Origin: $ORIGIN" -H "Authorization: Bearer $ACCESS" \ -H "Content-Type: application/json" \ -d '{ "items": [ { "id": "1", "sku": "ABC-001", "title": "Olive Oil 500ml", "price": 39.9, "quantity": 2 } ], "promos": [ { "id": 12345, "templateId": 0, "unitPrice": 7.98, "selectedQuntity": 1 } ] }' # 5) Get the transaction code — save data.code on your order curl -s "$BASE_URL/api/v1/transaction/get-transaction-code/$POI_ID" \ -H "Origin: $ORIGIN" -H "Authorization: Bearer $ACCESS" # 6) After the shopper pays, your BACKEND fires the order webhook with that # code to award points — see §5.4 and the Webhook Integration Guide. ``` --- ## 7. When something fails Every error uses the same envelope — `success: false` with an `errorCode`, plus `friendlyMessage` / `friendlyMessageHebrew` you can show a shopper. The common ones: | HTTP | `errorCode` | Meaning | |---|---|---| | 400 | `OTP_REQUIRED` | No code in the validate body. | | 404 | `OTP_INVALID` | Wrong or unknown code. | | 400 | `OTP_EXPIRED` | Code older than 120 seconds — request a new one. | | 403 | `OTP_CHALLENGE_MISMATCH` | `otpChallenge` doesn't match the one from step 1. | | 401 | (auth) | Missing/expired `Authorization` token on an authenticated call. | | 403 | `ORIGIN_REQUIRED` / `DOMAIN_NOT_ALLOWED` | `Origin` header missing or not on the POI's allowed list. | | 403 | (POI gate) | `fastLoginEnabled` is off for this POI. | The complete error table and security details live in [**Fast Login**](/content/login/fast-login). --- ## Related | If you want to… | Read | |---|---| | Skip the OTP screen — your backend already knows the shopper | [**Login From Backend**](/content/login/server-handoff-login) | | Embed the full UI instead of calling the API yourself | [**Getting Started**](/content/getting-started) | | Understand the phone-only fast-login channel in depth | [**Fast Login**](/content/login/fast-login) |