# OneGate REST API — Quick Start (eSIM)

> **API version 1.1.0** (2026-10-13), matching the API reference.
>
> For developers evaluating an integration with OneGate. In about 20 minutes you will know how the API works, what a complete eSIM order looks like and what you need to build. The full reference, with every endpoint and parameter, is at <https://onegate.ro/api.md>.
>
> ⚠️ Every example uses **dummy data** (tokens, IDs, activation codes). Replace the placeholders with your own values.

---

## 1. What you are integrating

OneGate is a B2B platform that sells digital services to partners. This guide covers **eSIM data plans**: about 5,500 plans for travellers, covering single countries and regions (Europe, Balkans, Global and others). Your system uses the REST API to:

1. **Read the catalog**: plans, net prices in your currency, coverage, data allowance, validity, images.
2. **Place orders** on your partner account.
3. **Pay** them from your account: the balance of your prepaid wallet, or credit that is invoiced monthly.
4. **Get the eSIM** (activation code, QR data, install links) and give it to your customer.
5. *Optionally*, show your customer their **data usage**.

You own the relationship with your customer. OneGate issues the eSIM with the upstream carrier and invoices your account. You never talk to a carrier, and you never handle card payments for your B2B orders.

---

## 2. What you receive at onboarding

| Item | Description |
|------|-------------|
| **API email + password** | The credentials your server exchanges for a token. You can also find them in the partner dashboard under **API** (`/admin/apis`). |
| **`site_id`** | The ID to send on every order you create. |
| **Sandbox access** | Test credentials, so you can place orders without real fulfilment or invoicing. Build and test there first, then switch to production credentials. |
| **Payment model** | **Prepay** (orders are paid from your wallet balance; top up by bank transfer or card in the dashboard) or **postpay** (orders go on credit and you get one invoice a month, with an itemised Excel annex). The API calls are the same for both. |

---

## 3. Conventions

| | |
|---|---|
| **Base URL** | `https://onegate.ro/api/vs1/` |
| **Protocol** | HTTPS only. |
| **Format** | Every path ends in `.json` (or `.xml`). A path without the extension returns `406`. |
| **Request body** | `application/x-www-form-urlencoded` or JSON. Nested data uses brackets: `order_items[0][service_item_id]=1234`. |
| **Auth** | `Authorization: Bearer <token>` on every call except login and the health check. |
| **Money** | Catalog prices are **net (VAT excluded)**, with the VAT and the total alongside. Parse amounts as decimals: on orders they are strings (`"12.84"`), on items just created they can be numbers. OneGate sets every amount, status and exchange rate; you set your own selling price for your customers. |
| **Data isolation** | Every request is scoped to your account. You never see another partner's data, and carrier costs are never exposed. |

Every response uses the same envelope:

```json
{ "status": 200, "message": "...", "data": { }, "errors": { } }
```

`status` mirrors the HTTP code. On success the payload is in `data`. On a validation failure (`400`), `errors` maps each field to its message.

Check connectivity without credentials:

```bash
curl https://onegate.ro/api/vs1/Hello/index.json
```

---

## 4. Authenticate

Exchange your API credentials for a JWT:

```bash
curl -X POST https://onegate.ro/api/vs1/Admins/login.json \
  -d "email=api@example.com" \
  -d "password=dummy-password"
```

```json
{
  "status": 200,
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.DUMMY.PAYLOAD",
    "expire": { "timestamp": 1893456000, "string": "+7 days" }
  }
}
```

The token is valid for **7 days**. Store it with its `expire.timestamp` (in a file, cache or database row shared by all your workers) and reuse it. Log in again when it expires, or when a call returns `401` (then retry that call once). Don't log in before every request.

---

## 5. The eSIM catalog

eSIM plans are the service items of service `5`. Fetch them page by page (at most 100 per page):

```bash
curl "https://onegate.ro/api/vs1/ServiceItems/index.json?service_id=5&with_contain=1&currency_ids=2&limit=100&page=1" \
  -H "Authorization: Bearer <token>"
```

```json
{
  "status": 200,
  "total": 100,
  "pagination": { "current_page": 1, "pages": 56, "next_url": "https://onegate.ro/api/vs1/service-items.json?...&page=2" },
  "data": [
    {
      "id": 1234,
      "service_id": 5,
      "code": "esim_10GB_30D_TR_V2",
      "name": "esim_10GB_30D_TR_V2",
      "description": "eSIM, 10GB, 30 Days, Turkey, V2",
      "is_active": true,
      "service_item_category": { "id": 13, "name": "Standard Fixed" },
      "prices": {
        "2": { "currency_id": 2, "vat_rate": 21, "net_unit_value": 12.84, "vat_value": 2.7, "total_value": 15.54 }
      },
      "details": {
        "countries": "TR",
        "countries_names": "[\"Turkey\"]",
        "roaming": "N/A",
        "operators": "Turkcell,Vodafone",
        "data_amount": "10000",
        "duration": "30",
        "speed": "4G,5G",
        "is_unlimited": "0",
        "is_autostart": "0",
        "billing_type": "FixedCost",
        "allowances": "[{\"type\":\"DATA\",\"service\":\"ROAMING\",\"amount\":10000,\"unit\":\"MB\",\"is_unlimited\":false}]"
      },
      "media_files": [
        { "type": "image", "lang": "en", "url": "https://cdn.example.com/en/esim_10GB_30D_TR_V2.webp", "vs": 1 }
      ]
    }
  ]
}
```

How to read a plan:

| Field | Meaning |
|-------|---------|
| `id` | What you send when you order. |
| `code` | Stable slug. Use it as the key of the plan in your database. (`name` holds the same value.) |
| `description` | Human-readable summary. You will probably build your own title from `details`. |
| `service_item_category.name` | Plan family: `Standard Fixed` (fixed data allowance), `Standard Long Duration`, `Standard Unlimited Lite` / `Essential` / `Plus`, `Premium Unlimited`. |
| `prices[currency_id]` | Net price, VAT and total, per unit. Get the currency IDs from `Currencies/index.json?format=list` (for example `1` = RON, `2` = EUR). |
| `details.countries` | Coverage: a 2-letter ISO code (`TR`), or a region code (`Europe+`, `Balkans`, `Global`, …). |
| `details.countries_names` | JSON array with the display name of the coverage; use it for region codes. |
| `details.roaming` | For a region plan, the CSV list of ISO codes of the countries it covers. `N/A` for single-country plans. |
| `details.data_amount` | Data allowance in MB. `-1` means unlimited (check `is_unlimited` too). |
| `details.duration` | Validity in days. |
| `details.operators`, `speed` | CSV of mobile networks and network generations. |
| `media_files` | Product images, one per language (`lang: null` = every language). |

Two conventions to handle in your parser: every value in `details` is a **string**, and a missing value is the literal string **`N/A`**. Fields such as `countries_names` and `allowances` are **JSON encoded inside the string**, so decode them.

Pagination: `total` is the number of rows on this page. Loop until `pagination.current_page` reaches `pagination.pages`.

---

## 6. The order flow

Every order takes the same calls:

```
1. POST Orders/add                        → order created           (status NEW)
2. POST Transactions/add                  → payment record          (status NEW)
3. PUT  Transactions/processPayment/{id}  → paid from your account  → order PAID
4. POST Orders/process/{uuid}             → eSIM issued             → order COMPLETED
5. GET  Orders/view/{uuid}                → read the activation data
```

| Response of `Orders/process` | Meaning |
|------------------------------|---------|
| `200` | Processed; `data` holds the fresh order. |
| `400` | The order is not paid. |
| `404` | Order not found. |
| `409` | The order is already being processed. Wait, then read the order. |

Statuses to follow, on `Orders/view`:

```
Order:       NEW(1) → PAID(4) → PROCESSING(5) → COMPLETED(7) | PARTIALLY_COMPLETED(6) | CANCELED(8)
Order item:  NEW(101) → AWAITING_PROCESSING(103) → PROCESSING(104) → COMPLETED(107) | FAILED(108)
```

For a partner account, payment (step 3) completes inside the call: there is no redirect and no webhook. If you skip step 4, a background job issues paid orders within a few minutes; calling it gives your customer the eSIM right away.

---

## 7. A complete eSIM order, step by step

Your customer buys **one eSIM for Turkey, 10 GB, 30 days**. From your local catalog (§8.1) you know that plan is `id = 1234`, and you pay in EUR (`currency_id = 2`).

### 7.1 Create the order

```bash
curl -X POST https://onegate.ro/api/vs1/Orders/add.json \
  -H "Authorization: Bearer <token>" \
  -d "site_id=10" \
  -d "currency_id=2" \
  -d "lang=en" \
  -d "order_items[0][service_item_id]=1234" \
  -d "order_items[0][quantity]=1"
```

```json
{
  "status": 200,
  "message": "The entry has been saved.",
  "data": {
    "id": 5001,
    "uuid": "OA1B2C3D4E",
    "status_id": 1,
    "currency_id": 2,
    "net_value": "12.84",
    "vat_value": "2.70",
    "total_value": "15.54",
    "order_items": [
      { "id": 9001, "uuid": "XK7P2QRS", "service_item_id": 1234, "quantity": 1, "status_id": 101, "total_value": 15.54 }
    ]
  }
}
```

**Save `id`, `uuid` and `order_items[].id` against your own order now**, before the next call. The price in the response is the price you pay: OneGate computes it from the catalog when the order is created, and ignores any amount or status in your request. On a prepaid account, a balance that is too low gives `400` here and nothing is charged.

### 7.2 Create the payment record

```bash
curl -X POST https://onegate.ro/api/vs1/Transactions/add.json \
  -H "Authorization: Bearer <token>" \
  -d "order_id=5001"
```

```json
{ "status": 200, "data": { "id": 7001, "uuid": "T9F8E7D6C", "order_id": 5001, "status_id": 201 } }
```

The amounts are set from the order; anything you send about money is ignored.

### 7.3 Pay

```bash
curl -X PUT https://onegate.ro/api/vs1/Transactions/processPayment/T9F8E7D6C.json \
  -H "Authorization: Bearer <token>"
```

```json
{ "status": 200, "data": { "id": 7001, "uuid": "T9F8E7D6C", "status_id": 205, "order": { "status_id": 4 } } }
```

`status_id = 205` (SUCCEEDED) on the transaction and `4` (PAID) on the order. `400` means the transaction is not payable (for example, the order changed after the transaction was created: create a new one), or the prepaid balance is too low.

### 7.4 Issue the eSIM

```bash
curl -X POST https://onegate.ro/api/vs1/Orders/process/OA1B2C3D4E.json \
  -H "Authorization: Bearer <token>"
```

OneGate orders the eSIM from the carrier during this call, which usually takes a few seconds. Give it an HTTP timeout of at least 30 seconds. On `200`, `data` is the updated order. On a timeout or a `409`, the eSIM is not lost: go to the next step and read the order.

### 7.5 Read the activation data

```bash
curl https://onegate.ro/api/vs1/Orders/view/OA1B2C3D4E.json \
  -H "Authorization: Bearer <token>"
```

```json
{
  "status": 200,
  "data": {
    "id": 5001,
    "uuid": "OA1B2C3D4E",
    "status_id": 7,
    "order_items": [
      {
        "id": 9001,
        "uuid": "XK7P2QRS",
        "service_item_id": 1234,
        "status_id": 107,
        "quantity": 1,
        "reference_meta": "[{\"iccid\":\"8944000000000000000\",\"matchingId\":\"AAAAA-BBBBB-CCCCC-DDDDD\",\"rspUrl\":\"rsp.example.io\",\"bundle\":\"esim_10GB_30D_TR_V2\",\"appleInstallUrl\":\"https://esimsetup.apple.com/esim_qrcode_provisioning?carddata=LPA:1$rsp.example.io$AAAAA-BBBBB-CCCCC-DDDDD\",\"androidInstallUrl\":\"https://esimsetup.android.com/esim_qrcode_provisioning?carddata=LPA:1$rsp.example.io$AAAAA-BBBBB-CCCCC-DDDDD\"}]"
      }
    ]
  }
}
```

When the item is `COMPLETED` (107), `reference_meta` is a **JSON string**. Decoded, it is an **array with one object per eSIM**; `quantity = 2` gives two eSIMs, each with its own ICCID and code:

| Field | Use |
|-------|-----|
| `iccid` | The eSIM's identifier. Show it to your customer and use it in support. |
| `rspUrl` | SM-DP+ address. |
| `matchingId` | Activation code. |
| `appleInstallUrl` / `androidInstallUrl` | One-tap install links for iOS and Android. |
| `bundle` | The plan `code`. |

### 7.6 Give the eSIM to your customer

For each eSIM in the array:

1. **QR code**: encode the string `LPA:1$<rspUrl>$<matchingId>`, for example `LPA:1$rsp.example.io$AAAAA-BBBBB-CCCCC-DDDDD`. The customer scans it in the phone's eSIM settings.
2. **Manual install**: show `rspUrl` and `matchingId` as two separate, copyable fields. iPhones ask for them separately ("SM-DP+ Address" and "Activation Code").
3. **One-tap install**: a button to `appleInstallUrl` on iOS, `androidInstallUrl` on Android, for customers who read your page on the phone itself.

Put all three on your order confirmation page and in the email you send your customer.

### 7.7 Show data usage (optional)

```bash
curl https://onegate.ro/api/vs1/OrderItems/usage/9001.json \
  -H "Authorization: Bearer <token>"
```

```json
{
  "status": 200,
  "data": {
    "status": "normal",
    "iccid": "8944000000000000000",
    "bundle_description": "eSIM, 10GB, 30 Days, Turkey",
    "data": { "total_gb": 10, "used_gb": 5.2, "remaining_gb": 4.8, "usage_percentage": 52 },
    "validity": { "start": "2026-07-01T00:00:00Z", "end": "2026-07-31T00:00:00Z", "is_expired": false }
  }
}
```

You send the order item `id`, never the ICCID. Before the customer installs the eSIM (or after it has expired), `data.status` is `no_active_bundles` or `no_data`, still with a `200`. `502` means the carrier did not answer; try again later.

---

## 8. Implementation tips

### 8.1 Catalog: cache it, don't browse it live

- **Sync the catalog into your own database once a day**, in a background job, and serve your pages and search from there. Don't call `ServiceItems` while a customer browses: fetching the whole eSIM catalog takes about 56 paginated requests.
- **Key plans by `code`** and store the OneGate `id` beside it for ordering.
- **Upsert, then deactivate** plans that were missing from the feed. Deactivate only when the sync fetched a normal number of plans (for example, more than a few hundred). A partial or failed fetch must never empty your shop.
- **Clear your page caches** after each sync, so prices and availability change together.
- **Request only the currencies you sell**, with `currency_ids=1,2`. Prices move with carrier costs and exchange rates, which is why a daily sync is enough and a weekly one is not.
- **Don't look up products one by one.** `Orders/view` returns `service_item_id`, not the product. Resolve it from your local catalog instead of calling `ServiceItems/view` for every item you display.

### 8.2 Using the attributes

- **Destination pages**: group plans by `details.countries`. For region plans, use `countries_names` for the title and `roaming` for the list of covered countries. A search for "Italy" should also find the region plans whose `roaming` includes `IT`.
- **Plan cards**: build the title from `data_amount` (MB → GB, `-1` → "Unlimited"), `duration` and the category name. Show `operators` and `speed` on the product page.
- **Images**: `media_files` URLs are stable. Use them directly, or copy them to your CDN and download again only when `vs` changes.

### 8.3 Placing orders

- **One destination plan per order item.** Use `quantity` for several eSIMs of the same plan (one per traveller); each one is a separate eSIM with its own ICCID.
- **Save the OneGate IDs right after `Orders/add`**, before paying. If a later call fails or times out, you can resume the same order instead of creating a new one.
- **Change an order only while it is `NEW`**, with `Orders/edit` (product, quantity, contact data; the API reference lists the accepted fields). Then create a new payment record before paying. Once paid, the content of an order is final: changing it returns `409`.
- **Never create a second order when a call times out.** Read the order you saved with `Orders/view/{uuid}` and continue from its status: pay it if it is `NEW`, issue it if it is `PAID`, read the eSIM if it is `COMPLETED`.
- **Call `Orders/process` right after payment**, and keep a job that polls `Orders/view` (for example every 30 seconds for 10 minutes) for orders that are not yet `COMPLETED`. An order item in `FAILED` (108) needs your support team: contact OneGate with the order `uuid`.
- **Prepay accounts**: monitor the `400` "insufficient balance" errors and alert whoever tops up the wallet. Top-ups are made in the partner dashboard.

### 8.4 Treat activation data as a secret

- Whoever has the `matchingId` can install the eSIM. Show it only to the customer who bought it, behind a login or an unguessable link.
- **Generate the QR code on your own servers** with a QR library. Don't send the `LPA:1$…` string to a public QR-code service.
- Don't write `reference_meta` to your production logs. Log full requests and responses in sandbox; in production, log only errors.

### 8.5 HTTP client

- Timeouts: about 10 seconds for most calls, at least 30 seconds for `Orders/process`.
- On `401`: log in again and retry once. If login itself fails, stop and alert; don't loop.
- On network errors or `5xx`: retry reads with backoff. For writes, read the resource before retrying (see §8.3).

### 8.6 Error codes

| Code | What to do |
|------|------------|
| `400` | Validation or state error. Log `errors` / `message`; don't retry unchanged. |
| `401` | Token expired or invalid. Log in again, then retry once. |
| `403` / `404` | The resource doesn't exist or isn't visible to your account. |
| `405` / `406` | Wrong HTTP verb, or missing `.json` extension. Fix the call. |
| `409` | Already in progress or already done, or the order can no longer be changed. Read the resource instead of retrying. |
| `5xx` | Temporary. Retry with backoff. |

---

## 9. Scoping your implementation

Use this checklist to estimate the effort. Items marked *optional* depend on your product.

| # | Work item | Notes |
|---|-----------|-------|
| 1 | API client | Login and shared token cache, Bearer header, re-login on `401`, response envelope, timeouts, error logging. |
| 2 | Daily catalog sync | Paginated import of the eSIM plans (§5, §8.1), parsing of `details` (strings, `N/A`, embedded JSON), upsert by `code`, safe deactivation, cache clearing. |
| 3 | Catalog presentation | Destination and region pages, plan cards, search by country including region coverage, images (§8.2). |
| 4 | Pricing | Read `prices` in your currency, apply your markup, show VAT the way your market requires. |
| 5 | Checkout → order | `Orders/add` → `Transactions/add` → `processPayment` → `Orders/process`; save the OneGate IDs; resume after a timeout (§7, §8.3). |
| 6 | Status tracking | A background job that polls orders not yet `COMPLETED`; handling of `FAILED` / `PARTIALLY_COMPLETED` (support, refund to your customer). |
| 7 | eSIM delivery | Decode `reference_meta`, generate the QR codes, show the manual codes and install links on your page and in your email (§7.6, §8.4). |
| 8 | *Optional:* data usage | Customer page using `OrderItems/usage/{id}` (§7.7). |
| 9 | *Optional:* balance alerts (prepay) | Alert on "insufficient balance" errors. |
| 10 | Sandbox testing, then go live | Switch credentials and `site_id`; place one real order and install it on a phone before launch. |

The integration needs no webhooks, no card handling and no carrier-specific code: only synchronous REST calls, a daily sync job and a polling job.

---

## 10. Resources

- **Full API reference (Markdown):** <https://onegate.ro/api.md> — every endpoint, parameter and response. It works well as context for an AI coding assistant.
- **Postman documentation:** <https://onegate.ro/docs> — interactive, with ready-to-send requests.
- **Contact:** contact@digitalwap.ro — onboarding, sandbox access, commercial terms.
