SIM Platform Partner API
Version: 0.24.0 · 2026-10-04
Base URL: https://api.roguemobile.hk/v1
Online API reference
Account and API Key
The platform provides a channel account (your channel code) and a fixed API Key. Send both in the headers of each request. No login, temporary token or token refresh is required.
X-Partner-Id: YOUR_CHANNEL_CODE
X-API-Key: YOUR_API_KEY
Content-Type: application/json
Test access with a balance query:
curl 'https://api.roguemobile.hk/v1/balance' \
-H 'X-Partner-Id: YOUR_CHANNEL_CODE' \
-H 'X-API-Key: YOUR_API_KEY'
Replace the placeholders with your credentials. The key has no scheduled expiry; resetting it immediately invalidates the old value. If your channel code changes, update X-Partner-Id. Send these headers from your server over HTTPS, not from a public browser page, and never put keys in URLs. Do not combine them with an Authorization header.
All paths below are relative to /v1; do not add /v1 twice. JSON requests require Content-Type: application/json. Normal responses use data and meta; HTTP error responses use error and meta. A successful /ledger/export returns CSV. Changing the documentation language does not change API fields, state values or error codes. Base program logic on error codes, not the wording or language of the readable message.
Optional fields may be omitted; send null only where the schema explicitly permits it. For example, txid: null or change_type: null returns HTTP 400 instead of generating a transaction ID or choosing immediate renewal. Query parameters must match the documented formats and allowed values. Repeating a single-value parameter, supplying an invalid date or using an invalid boolean returns 400 instead of ignoring the invalid filter and broadening the query.
Minimum integration flow
| Step |
Request |
What to do |
| Choose a plan and city for activation |
GET /regional-catalog?orderable=true |
Save the chosen catalog_entry_id; it identifies both the plan and address |
| Submit a service request |
A POST example for one of the four service types below |
Include one or more items, with no fixed item-count limit; a channel-unique txid is recommended |
| Read the result |
GET /jobs/{id} or a webhook |
Save the returned data.job.id; only final item states confirm success or failure |
Renewal and reactivation use the existing line, so they do not require another catalog lookup. You do not need to call balance, address validation or SIM sync before every request: the platform checks ownership, pricing and the whole-batch balance. Older lines without registered identifiers may need a one-time sync. HTTP 202 means accepted, not completed.
You can start with job queries only. To receive automatic notifications, register an HTTPS callback URL once and obtain your separate Webhook Secret. No per-order callback URL is required. Recovery endpoints are only for exceptions.
Example numbers and catalog IDs are placeholders. ICCIDs must already belong to your channel. Billable operations require configured prices and enough prepaid balance; manual WFC is free. Amounts are USD strings.
Plans, addresses and catalog IDs
Each catalog ID identifies one combination of plan, city, state, ZIP and street address. Your page can let users choose a plan, then a city and address from that plan's catalog entries. Submit the chosen entry's catalog_entry_id; users do not need to enter it manually. The same address with a different plan has a separate entry.
GET /regional-catalog returns configured plan-and-address entries. Filters include zip, state, area_code, product_code and orderable=true. Paginate with page and per_page.
orderable=true returns only orderable entries. With orderable=false or no orderable parameter, all entries matching the other filters are returned.
| Field |
Meaning |
id / catalog_entry_id |
Unique regional catalog entry ID |
revision |
Catalog version; the platform checks it against the selected version only when you submit catalog_revision |
product_code |
Platform product identifier for classification and filtering |
plan_code / plan_id |
Plan code and ID used in service requests |
plan_name |
Display name |
service_address |
Street, city, state and ZIP |
prices |
Channel prices by operation; null means unconfigured |
zip_region |
Read-only city and area-code references, including issued-number history |
orderable |
Whether the current configuration supports ordering; submission revalidates it |
GET /regional-catalog/{id} retrieves a full entry. For activation, catalog_entry_id resolves the plan and address automatically. Optional catalog_revision checks the version. A stale catalog revision or an item's plan or address conflict marks that item failed; other valid items continue in the same batch. An invalid batch structure still rejects the entire request.
GET /zip-regions and GET /zip-regions/{zip} provide city, state and area-code references separately. Area codes do not change a catalog ID or guarantee number availability.
Activation
POST /jobs/activations
{
"txid": "channel-activation-001",
"items": [
{
"esn": "89000000000000000001",
"catalog_entry_id": "CATALOG_ENTRY_ID"
}
]
}
Use the chosen catalog entry ID. The platform fills in the plan, street, city, state and ZIP. You do not need to send plan_code, defaults, customer or mode for this form. Optionally include catalog_revision if you need changes since selection to be rejected. For batch activation, add more items to the same array. Area codes are references, not guaranteed selections.
Renewal
POST /jobs/renewals
{
"txid": "channel-renewal-001",
"change_type": "ONEXPIRY",
"items": [
{
"mdn": "2025550100"
}
]
}
For normal renewal, provide one line identifier per item; supplying txid is recommended. Select either renewal mode with change_type:
| Mode |
change_type |
Cycle and charging time |
| Immediate renewal (default) |
IMMEDIATE |
Starts a new plan cycle on the processing date and provides a new set of plan allowances. Charged upon confirmed success |
| Renewal at expiry |
ONEXPIRY |
Keeps the current cycle and adds one cycle at the existing expiry. Can be submitted early; charged upon confirmed success |
Renewal at expiry can be processed now, extending the expiry upon success. It does not defer submission or charging until the expiry date. For example, when renewing on September 29 with an existing expiry of October 15, the typical new expiry is approximately October 29 for immediate renewal, or November 15 for renewal at expiry. Actual expiry dates depend on the returned result; the channel is charged its configured quote for the operation.
The example above explicitly selects renewal at expiry. Omitting change_type selects IMMEDIATE, immediate renewal. Existing orders retain the mode recorded when submitted. For immediate renewal:
{
"txid": "channel-renewal-immediate-001",
"change_type": "IMMEDIATE",
"items": [
{
"mdn": "2025550100"
}
]
}
No processing date is required. Scheduling or month-count fields such as schedule_date and months are unsupported. For three months, explicitly use ONEXPIRY and make three sequential renewal requests: confirm the first succeeded and verify its expiry information before the second, then repeat before the third. Do not infer an absent expiry date or overlap renewals.
plan_change=false renews the current plan. To renew onto another plan, send plan_change=true with a plan_id from the catalog; that plan must be enabled and priced for your channel. This is a renewal with a target plan, not the unavailable standalone plan-change operation.
Reactivation
POST /jobs/reactivations
{
"txid": "channel-reactivation-001",
"items": [
{
"mdn": "2025550100"
}
]
}
Restores the original line at its current plan's renewal price. Do not supply a new plan. Without an mdn, use a registered ICCID or enrollment_id. Eligibility depends on the actual line state; acceptance does not guarantee restoration.
The optional address is the line service street, city, state and ZIP. Manual WFC uses the same resolution rules:
| Priority |
Address source |
Meaning |
| 1 |
Fields explicitly supplied in this request |
items[].customer overrides defaults; items[].zip takes precedence for ZIP |
| 2 |
The locally registered SIM service address |
Use the complete, valid address saved after that SIM's successful activation or later line synchronization |
| 3 |
The same SIM's latest successful activation snapshot |
If the local SIM address is missing or invalid, use the latest successful activation address for the same ICCID; failed orders are excluded |
| 4 |
The line's current WFC/service address |
If neither local source has a valid address, leave missing fields for the current line record to supply |
The regional catalog contains selectable plans and addresses for activation; an address is never picked arbitrarily by city or ZIP. After a catalog-based activation succeeds, the address used is saved on the SIM and order. Later catalog edits do not rewrite that activation snapshot. The resolved address is stored with the new request and retained while queued or manually retried.
Supplying only ZIP does not make a complete address mandatory; any explicit ZIP override should still match the street, city and state. WFC eligibility depends on the actual result. This is a line service address, not the channel's company or billing address. Empty optional names, email and address_two are treated as omitted; the required activation address must still be complete and valid.
Inspect items[].result.wifi_calling. A WFC failure does not reverse a successful reactivation, which remains billable.
Enable Wi-Fi Calling
POST /jobs/wifi-calling
{"txid":"channel-wfc-001","items":[{"mdn":"2025550100"}]}
Use a registered mdn, esn or enrollment_id. feature_action defaults to Active; only enabling Wi-Fi Calling is supported. Address reuse follows reactivation. This operation is free to the channel, does not debit the balance and creates no debit ledger entry. It still creates item-level service records, job results and signed callbacks.
Use this WFC job's result to confirm the repair. The original reactivation job retains its historical wifi_calling result; a later repair does not rewrite it.
Reading results
GET /jobs/{id}: retrieve by the returned job ID or transaction ID.
GET /jobs?txid=...: look up a batch or item transaction ID.
GET /jobs?status=running: list your channel's jobs. Default page size is 100. Use page, per_page and pagination.has_more to continue.
GET /jobs/{id}/items?status=failed: retrieve failed items in the job. This endpoint returns all matching items in the job without pagination.
An item txid returns only that item's details with match=item_txid. A job ID or batch txid returns the complete batch. One job query reads all items; separate per-SIM queries are unnecessary.
A pending job queried by job ID or txid refreshes the original batch once. Confirmed completed jobs return without an extra lookup, and querying an unsent queued job never submits it. Job lists show the synchronized snapshot. If a refresh fails or another query is already in flight, the returned state may remain unresolved; inspect last_error and query again later.
Successful JSON responses use data and meta. Service results contain data.job and data.items. Use job.id to query the job and items[].id to retry an eligible item.
| Level |
State |
Meaning |
| job |
queued |
Queued; paused=true requires funds and resume |
| job |
running |
Processing or awaiting a confirmed result |
| job |
succeeded |
All items succeeded |
| job |
failed |
All items failed |
| job |
partial |
All items finished, with both successes and failures |
| item |
pending / processing |
Unfinished; no charge yet |
| item |
succeeded |
Successful; charged at the item's quoted price |
| item |
failed |
Failed; no charge |
job.billing.amount is the sum of established item quotes. A locally failed item that could not be quoted has a zero amount, which is not a free offer. Manual WFC is explicitly free. charged_amount is the amount actually debited. items[].billing.price is the item's quote. result preserves verified result fields, such as phone number, Enrollment ID and expiry dates when returned. Not every endpoint returns an expiry date. finished_at is a processing completion timestamp, not a service expiry date.
If a result explicitly returns a malformed phone number, or a renewal, reactivation or WFC result returns a number different from the original, the item remains awaiting verification without a charge and the original job is checked. An omitted optional phone-number field is not a number conflict; this does not trigger a new service submission.
A single item's txid equals the batch txid. For multiple items, each item uses the batch txid, an underscore and the last eight ICCID digits. Returned item transaction IDs can therefore be up to 73 characters. If ICCID suffixes collide within a batch, submit those SIMs in separate batches.
Common service rules
- Each request creates one job with one or more
items and no fixed item-count limit. Results and charges are recorded per SIM.
- We recommend supplying a channel-unique
txid for every batch: 1–64 letters, digits, periods, underscores, colons or hyphens. Resubmitting the same txid returns HTTP 409. Query the original job instead.
- Processing is asynchronous by default; omit
mode or send async. Unfinished jobs return HTTP 202; if all items have already failed local validation, the response is HTTP 200. mode=sync attempts immediate processing and returns 200 when the entire job has a final result, or 202 otherwise. HTTP 200 does not guarantee business success, and 202 means accepted only. Inspect each item's final state.
- Identify each item with
esn (ICCID), mdn (ten-digit US phone number) or enrollment_id. Activation supports esn and mdn; renewal, reactivation and manual WFC support all three. Multiple supplied identifiers must resolve to the same SIM in your channel. For an older SIM without a registered number, first use /cards/{iccid}/sync.
- Processing uses the SIM, plan, address and channel price records already registered with the platform. Submission does not perform per-SIM line lookups or requery the plan catalog. Query and synchronization endpoints are optional standalone tools, not required preparation steps.
- Per-item local address, plan or pricing validation errors are recorded only against that item; valid items continue in one batch. Authentication, ownership, duplicate SIMs, invalid shared parameters and insufficient funds for the valid total still reject the request.
- The entire batch is checked against the channel's balance at its submitted prices. No funds are frozen or debited on acceptance. Balance is checked again before dispatch; insufficient funds pause the job until funds are added and
resume is requested. Only confirmed successful items are charged. Failed items are not charged; partial success charges only the successful items.
- A SIM cannot have overlapping unfinished operations. An unresolved billable job blocks later billable calls for the same channel to prevent overspending without freezing funds. Free WFC and billable work on other SIMs do not block each other; operations on the same SIM must still wait for the preceding operation to finish.
- Activation, renewal and reactivation use your channel's price list. Channels using the unified plan-price setup are not charged SIM or eSIM sales fees again through these operations.
- Area codes are references only. Do not submit
area_code to choose a number. The actual assigned number and area code depend on the completed activation.
Recovery
Internal processing retries do not require another channel submission: wait for the original job's final result. An increased internal attempt count does not create a new order or another charge.
After a service restart, jobs proved not yet dispatched return to the queue automatically. Jobs that may have been sent, or have no dispatch-phase evidence, are checked through their original job only. Channels do not need to create another txid.
Confirmed failures remain failed and unresolved in exceptions; they are not automatically retried. Job queries and configured webhooks return status=failed, the error and retryable. Failed items are not charged. retryable=true permits an explicit retry; it does not require one. Address, plan and pricing errors also remain unresolved while valid items continue. Unresolved is an operations follow-up marker: the API business status remains failed, not pending.
A request rejected before a job exists returns an HTTP error. Business-parameter rejections from authenticated channels are also recorded in exceptions. Without a job there is no item callback; use meta.request_id and the error code for investigation. Correcting parameters of an existing failed job requires a new request with a new txid; explicitly call retry only when reusing the stored parameters after review.
For the following three operations, omit the request body or send an empty JSON object {}; do not resend the original business parameters. A request body requires Content-Type: application/json. Nonempty objects or malformed bodies return 400, bodies over 200,000 bytes return 413, and non-JSON content types return 415.
POST /jobs/{id}/recheck: query the original job without resubmitting the service request. A newly confirmed success is charged once; repeated queries do not debit again. Accepted jobs wait for the final callback, with an original-batch fallback lookup after five minutes without a final result. Uncertain submissions, incomplete callbacks or conflicting evidence can be checked sooner. After 20 attempts, including initial dispatch, attention_required=true and five-minute fallback checks continue; the service is never automatically rebooked.
POST /jobs/{id}/resume: after adding funds, resumes a job paused for insufficient balance, using its original quote. If the balance is still insufficient, it returns HTTP 402 and the job stays paused.
POST /jobs/{id}/items/{itemId}/retry: only for failed items with result.retryable=true and an existing original job/item identity. attempt_count is not mandatory. After reviewing the problem, you can explicitly request a retry while other items in the same batch remain in flight; the retry is queued until earlier processing completes. Original parameters and prices are retained; local ownership, line state, original plan and balance are checked. Successful, processing and unresolved items cannot be retried. Changed parameters require a new job. HTTP 200 means scheduled, not completed; query the job or wait for a webhook to confirm the result.
For duplicate txid (409) or a submission timeout, query the original transaction instead of resubmitting with a new ID. HTTP 402 means insufficient balance. After 401, check the channel account and API Key, including whether the key was reset. Follow Retry-After after 429. error.details.business_code identifies the specific business reason. For support, provide meta.request_id, txid and the job ID, never secret keys.
Webhooks
Register an HTTPS callback URL for your channel, or override it for a job with webhook_url. Events are sent after results are verified and successful items are charged. Duplicate events do not debit again. If no URL is configured, query jobs for results.
Once an event is recorded, delivery is triggered immediately. Due notifications are sent continuously without a fixed per-second or per-cycle count limit. Failed notifications wait for their retry deadline below; actual delivery time depends on the receiver and network.
A verified final item result correlated to the current request updates the order directly, without a mandatory second lookup. For unresolved work, a batch summary alone, missing correlation data, conflicting content or an ambiguous old-versus-new manual retry result requires an original-job lookup; missing callbacks also have a query fallback. A contradictory result received after a confirmed final state preserves the original settlement and raises an exception for investigation; it does not automatically change the status or charge again. Event creation time is not a substitute for a missing processing completion time or service expiry date.
Events: activation.item.succeeded, activation.item.failed, activation.job.completed. Replace activation with renewal or reactivation for those operations; use wifi_calling for manual WFC activation.
Headers:
X-RM-Event: matches JSON type.
X-RM-Delivery-Id: identifies this delivery attempt and changes on redelivery. Deduplicate events using JSON id.
X-RM-Signature: sha256=<hex signature>.
Calculate HMAC-SHA256 over the untouched raw request body using your channel Webhook Secret. Do not parse and reserialize the JSON before verification; do not prefix a timestamp. After verification, durably deduplicate by id and return HTTP 2xx. Return 2xx for valid duplicate events too. After a failed initial delivery, the platform automatically retries up to five times at intervals of 30, 60, 120, 240 and 480 seconds, with exponential backoff capped at 15 minutes: at most six attempts including the initial delivery. An administrator can redeliver the same event from the admin console. Redelivery only sends the notification again; it never repeats the service operation or its charge. Events can arrive more than once or out of order. Query the job for its current authoritative state.
Callbacks do not send X-Partner-Id or X-API-Key, and created_at records when the event was created. The receiver verifies the separate signature described above.
{
"id": "evt_example",
"created_at": "2026-10-02T12:00:00Z",
"type": "activation.item.succeeded",
"data": {
"job_id": "ord_example",
"item_id": "item_example",
"job_txid": "channel-activation-001",
"txid": "channel-activation-001",
"esn": "89000000000000000001",
"status": "succeeded",
"mdn": "2025550100"
}
}
Use webhook-verify.mjs in the download package. With Node.js 22+, run node --test webhook-verify.test.mjs to verify the sample.
Queries, inventory and ledger
| Method |
Path |
Purpose |
| GET |
/status |
Service status; no authentication required |
| GET |
/customers?esn=... |
Channel line details; supply exactly one of esn, mdn or enrollment_id |
| GET |
/mdns/{mdn} |
Line details by phone number |
| GET |
/mdns/{mdn}/usage |
Usage within 90 days; summary by default, from/to for daily records |
| GET |
/esns/{esn}/status |
SIM status |
| POST |
/addresses/validate |
Address and WFC address eligibility only; does not enable WFC |
| GET |
/cards |
Paginated channel inventory |
| POST |
/cards/{iccid}/sync |
Sync an assigned SIM's line, plan and address; no charge |
| GET |
/cards/{iccid}/esim |
Retrieve registered eSIM installation details; no new code is generated |
| GET |
/balance |
Available prepaid balance |
| GET |
/ledger |
Paginated funds added, charges and running balances |
| GET |
/ledger/summary |
Totals for the same filters |
| GET |
/ledger/export |
CSV for all matching entries |
| GET |
/plans |
Available plans and channel prices by ZIP |
| GET |
/regional-catalog |
Plans, addresses, entry IDs and area-code references |
| GET |
/regional-catalog/{id} |
One regional catalog entry |
| GET |
/zip-regions |
ZIP codes, cities, states and area-code references |
| GET |
/zip-regions/{zip} |
One region record |
Line, phone-number and usage queries, and address validation with enrollment_id, return HTTP 409 with error.details.business_code=AMBIGUOUS_IDENTIFIER if the identifier matches multiple SIMs in your channel. Resolve the ownership records first.
For POST /cards/{iccid}/sync, send an empty object {} with Content-Type: application/json. It only retrieves and synchronizes the registered line; it cannot submit a new number or change the plan. A nonempty request object returns HTTP 400.
Ledger from and to are inclusive local dates in America/New_York, matching Washington, DC reconciliation dates and accounting for daylight saving time. Filtering uses actual posting/debit time; timestamps may remain UTC ISO representations of the same instant. Summary covers all filtered entries, independent of pagination. Amounts are USD strings.
The limit is 120 requests per minute per channel, excluding authenticated GET job queries. JSON bodies must not exceed 200,000 bytes. Requests may queue during congestion.
Available: activation, renewal, reactivation, free manual WFC activation, and the queries and account functions above. Standalone deactivation, add-ons, standalone plan changes and manual WFC deactivation remain unavailable. Billable operations reduce the balance only on confirmed success. No automatic wallet funding or online payment collection is included.
Advanced options and compatibility
These options are not required for the normal flow above.
- Custom address: instead of a catalog entry, provide the API
plan_code and a complete address, as shown below. GET /plans?zip=36104 lists plan codes, IDs and channel prices. plan_code is not a display name such as Plan 65 or a product_code. The compatibility cost field is null; use price, prices or job billing for charges.
defaults supplies a batch address; items[].customer overrides customer/address fields and items[].zip overrides ZIP. Names and email are optional. Only carrier TMB is enabled; enrollment_type defaults to HANDOVER (SIM already with the customer), with SHIPMENT for shipping. Activation does not accept address_two; include unit/floor details in address. /addresses/validate is available for custom-address eligibility checks, not a required step when using the catalog.
mode=sync attempts immediate processing. It returns 200 only if complete, and 202 if queued or unresolved. It cannot guarantee synchronous completion.
- If body
txid is absent, Idempotency-Key or the txid header is used; otherwise an ID is generated. Supplying body txid is recommended so that you can recover an uncertain submission by querying it.
- Existing token clients may keep
POST /auth with partner_id and api_key, then Authorization: Bearer <token> (30-minute expiry). This compatibility endpoint is limited to 20 requests per minute per IP. New fixed-key integrations do not call it. Token and fixed-header integrations share the same current request, response and webhook contract.
- The older
Authorization: Bearer <API Key> contract remains available with its original request, response and webhook formats. When migrating from that contract, update all three formats; do not change the header alone. The fixed-header method requires both account and key, with no Authorization header.
Custom-address activation example:
{
"txid": "channel-activation-002",
"defaults": {
"enrollment_type": "HANDOVER",
"address": "1 Example Street",
"city": "Montgomery",
"state": "AL",
"zip": "36104"
},
"items": [
{
"esn": "89000000000000000001",
"plan_code": "PLAN_CODE_FROM_CATALOG"
},
{
"esn": "89000000000000000002",
"plan_code": "PLAN_CODE_FROM_CATALOG"
}
]
}
cost is a retained compatibility field and currently null; it is neither zero cost nor the selling price. The price returned by GET /plans is the renewal price for one cycle, or null if unconfigured. Read prices for other operations and billing for an existing job. Omitted attempt_count does not block ordinary final results; an explicitly lower count is evidence of an older result. For a temporary service rejection that explicitly confirms non-acceptance and is safe to retry, the platform may send again after a delay using the original transaction identity. This is not an automatic retry of a failed business operation; the channel should keep querying its original txid. For network failures, unreadable success bodies or other uncertain outcomes, the platform only queries the original job and does not resubmit the operation.