Introduction

Spenza provides various APIs to manage the mobile subscriptions offered in the Spenza marketplace. This document provides the information required to interact with Spenza APIs.

Authentication Headers

All API endpoints (except the authentication endpoint) require a Bearer token in the Authorization header. You must first obtain this token using the Authentication API.

Required Headers
Header Value Description
Authorization Bearer YOUR_TOKEN The access token obtained from the Authentication API
Content-Type application/json All requests must send and receive JSON data
Example Headers
Authorization: Bearer eyJhbGciOiJ...
Content-Type: application/json

Note: Replace YOUR_TOKEN with the actual access token received from the Authentication API.

Authentication

All API calls require authentication using a Bearer Token. You must first obtain this token using the Authenticate API.

GET /api/v1/authenticate

Request Body

{
    "key": "api-key",
    "secret": "api-secret"
}

Required Fields

Field Type Description
key string Your API key
secret string Your API secret

Response


    {
        "message": "Authentication Success.",
        "accessToken": "Your Auth Token for other APIs.",
        "expiration": "token expiration time"
    }
    

Get Sim by ICCID

GET /api/v1/sim-by-iccid

Request Body

{
    "ICCID": "9999967890123456789"
}

Required Fields

Field Type Description
ICCID string The ICCID of the SIM card

Response

{
    "message": "Success.",
    "simInfo": {
        "status": {
            "user": "Assigned",
            "device": "Assigned"
        },
        "operator": {
            "custNbr": null
        },
        "ICCID": "9999967890123456784",
        "activationCode": "",
        "source": "Other",
        "isDeleted": false,
        "recommendedPlan": "60af49af21647e2ca0b15f51",
        "_id": "632808a8e85110002e3ae629",
        "network": "T-Mobile",
        "Operator": "T-Mobile",
        "phoneNumber": "78993093857",
        "group": "63280750e85110002e3ae624",
        "account": "63280446e85110002e3ae54e",
        "phoneLineStatus": "ACTIVATED"
    }
}

Get Plan By planId

GET /api/v1/plan-by-id

Request Parameters

planId: "AT0001"

Required Fields

Field Type Description
planId string Unique identifier for the plan

Purchase Plan

POST /api/v1/purchase-plan

Request Body

{
    "ICCID": "8901260853182965429",
    "productId": "product1",
    "activateNow": true,
    "scheduledDate": "2024-04-20"
}

Required Fields

Field Type Description
ICCID string The ICCID of the SIM card
productId string The ID of the plan to purchase
activateNow boolean Whether to activate the plan immediately
scheduledDate string Date to schedule activation (YYYY-MM-DD format)

Response

{
    "message": "Success.",
    "subscription": {
        "stripe": {
            "subId": "sub_1LkKThAq7JKDyJICNJ3l1WWv",
            "scheduleId": null,
            "price": null
        },
        "operatorRes": {
            "MDN": null,
            "description": null
        },
        "planName": "EXPLAN_100",
        "planCode": "EXPLAN_001",
        "status": "ACTIVE",
        "initiatedDate": "2022-08-21T04:24:06.473Z",
        "startDate": "2022-08-21T04:24:05.000Z",
        "endDate": "2022-11-21T04:24:05.000Z",
        "scheduledDate": "2022-09-20T00:00:00.000Z",
        "cancelledDate": null,
        "autoTopUp": true,
        "subId": "1663734246474",
        "Operator": "T-Mobile",
        "network": "T-Mobile",
        "orderId": "1739-597673-2114"
    }
}

Activate Subscription

PUT /api/v1/activate-subscription

Request Body

{
    "ICCID": "8901260853182965429",
    "productId": "product1"
}

Required Fields

Field Type Description
ICCID string The ICCID of the SIM card
productId string The ID of the plan to activate

Response

{
    "message": "Success",
    "subscription": {
        "stripe": {
            "subId": "sub_1LkKThAq7JKDyJICNJ3l1WWv",
            "scheduleId": null,
            "price": null
        },
        "operatorRes": {
            "MDN": null,
            "description": null
        },
        "planName": "EXPLAN_101",
        "planCode": "EXPLAN_003",
        "status": "ACTIVE",
        "initiatedDate": "2022-08-21T04:24:06.473Z",
        "startDate": "2022-08-21T04:24:05.000Z",
        "endDate": "2022-11-21T04:24:05.000Z",
        "scheduledDate": "2022-09-20T00:00:00.000Z",
        "autoTopUp": true,
        "subId": "1663734246474",
        "Operator": "T-Mobile",
        "network": "T-Mobile",
        "orderId": "1739-597673-2114"
    }
}

Cancel Subscription

POST /api/v1/cancel-subscription

Request Body

{
    "ICCID": "9999967890123456789"
}

Required Fields

Field Type Description
ICCID string The ICCID of the SIM card

Response

{
    "message": "Subscription Cancelled.",
    "subscription": {
        "stripe": {
            "subId": "sub_1LkKThAq7JKDyJICNJ3l1WWv",
            "scheduleId": null,
            "price": null
        },
        "operatorRes": {
            "MDN": null,
            "description": null
        },
        "planName": "EXPLAN_100",
        "planCode": "EXPLAN_001",
        "status": "CANCELLED",
        "initiatedDate": "2022-08-21T04:24:06.473Z",
        "startDate": "2022-08-21T04:24:05.000Z",
        "endDate": "2022-11-21T04:24:05.000Z",
        "scheduledDate": "2022-09-20T00:00:00.000Z",
        "autoTopUp": true,
        "subId": "1663734246474",
        "Operator": "T-Mobile",
        "network": "T-Mobile",
        "orderId": "1739-597673-2114"
    }
}

Get Usage

POST /api/v1/telco/getUsage

Request Body

{
    "subId": "1745863440782"
}

Required Fields

Field Type Description
subId string The ID of the subscription (found in subId field of subscription object)

Response

{
    "result": {
        "usageData": 0,
        "usageCalls": 0,
        "usageSms": 0,
        "usageUpdatedAt": 1747223324287,
        "telcoStatus": "ACTIVATED",
        "usageDataConverted": {
            "format": "B",
            "value": 0
        },
        "network": "Verizon",
        "networkCode": "Verizon"
    }
}

Get Subscription by ICCID

GET /api/v1/subscription-by-iccid

Request Body

{
    "ICCID": "9999967890123456789"
}

Required Fields

Field Type Description
ICCID string The ICCID of the SIM card

Response

{
    "message": "Success.",
    "subscription": {
        "stripe": {
            "subId": "sub_1LkKThAq7JKAyJICNJ3l1WWv",
            "scheduleId": null,
            "price": null
        },
        "operatorRes": {
            "MDN": null,
            "description": null
        },
        "planName": "T-Mobile100",
        "planCode": "T-Mobile0001",
        "status": "Pending",
        "initiatedDate": "2022-09-21T04:24:06.473Z",
        "startDate": "2022-09-21T04:24:05.000Z",
        "endDate": "2022-10-21T04:24:05.000Z",
        "scheduledDate": "2022-09-20T00:00:00.000Z",
        "cancelledDate": null,
        "autoTopUp": true,
        "subId": "1663734246473",
        "Operator": "T-Mobile",
        "network": "T-Mobile",
        "sim": "632a916b961e53002e1298d7",
        "account": "63280446e85110002e3ae54e",
        "orderId": "1339-597673-2114"
    }
}

Renew Phone Number

POST /api/v1/renew-phone-number

Requests a new phone number for the specified SIM card. This endpoint is rate-limited based on your plan configuration.

Request Body

{
    "ICCID": "9999967890123456789"
}

Required Fields

Field Type Description
ICCID string The ICCID of the SIM card

Rate Limiting & IP Restrictions

Monthly Rate Limits: This endpoint is subject to monthly rate limits based on your plan configuration. The limit is defined in the plan's apiLimits.renewPhoneNumber property. If no limit is set, all requests are allowed.

IP Tracking: All requests are logged with the originating IP address from the x-forwarded-for header or connection IP. IP addresses are monitored for security and auditing purposes.

Response

{
    "message": "Request placed successfully, new number will be assigned soon"
}

Validate ICCID (Public)

POST /api/v1/public/validate-iccid

Validates the format and checksum of an ICCID. This is a public endpoint and does not require authentication.

Request Body

{
    "ICCID": "8901260853182965429"
}

Required Fields

Field Type Description
ICCID string SIM card ICCID to validate

Response

{
    "isValid": true,
    "message": "Valid ICCID."
}

Validate IMEI (Public)

POST /api/v1/public/validate-imei

Validates the format and Luhn checksum of an IMEI. This is a public endpoint and does not require authentication.

Request Body

{
    "IMEI": "356938035643809"
}

Required Fields

Field Type Description
IMEI string Device IMEI to validate

Response

{
    "isValid": true,
    "message": "Valid IMEI."
}

Purchase eSIM (Async)

POST /api/v1/purchase-esim

Initiates asynchronous eSIM purchase and phone number provisioning. The API accepts optional user information. If no user data is provided, the system will use the customer's default employee information.

Authentication

Requires JWT token in Authorization header: Bearer <token>

Request Body Examples

# Example 1: Basic eSIM Purchase
{
    "imei": "451014850281267",
    "simId": "TEST_SPENZA"
}
# Example 2: eSIM Purchase With User Information
{
    "imei": "451014850281267",
    "simId": "SpenzaJ_ESIM",
    "user": {
        "name": "Testung",
        "email": "test+8@spenza.com",
        "phoneNumber": "2031134322",
        "address": "47 W 13th St",
        "city": "New York",
        "state": "New York",
        "zipcode": "10011"
    }
}

Required Fields

Field Type Required Description
imei string Yes Device IMEI number (exactly 15 digits)
simId string Yes SIM Product ID / Product name (e.g., "SpenzaJ_ESIM", "TEST_SPENZA")
user object No Optional user/employee information. If not provided, system will use customer's default employee.

User Object Fields (Optional, but if provided, name, email, and zipcode are required)

Field Type Required Description
name string Yes* User's full name
email string Yes* User's email address
zipcode string Yes* 5-digit US ZIP code for location-based number provisioning
phoneNumber string No User's phone number
address string No User's street address
city string No User's city
state string No User's state

* Required only if user object is provided

Location-Based Number Assignment

Important: The system uses the ZIP code to provision phone numbers appropriate for the specified location:

Success Response (200 OK)

{
    "requestId": "64dfa874-f157-431a-a6db-d8e5a915ea12",
    "status": "PENDING",
    "message": "eSIM purchase initiated successfully"
}

Error Responses

// 400 Bad Request - Missing Required Fields
{
    "statusCode": 400,
    "message": "imei and simId are required fields",
    "error": "Bad Request"
}

// 400 Bad Request - Invalid IMEI
{
    "statusCode": 400,
    "message": "IMEI must be exactly 15 digits",
    "error": "Bad Request"
}

// 400 Bad Request - Invalid IMEI Checksum
{
    "statusCode": 400,
    "message": "Invalid IMEI number - failed Luhn checksum",
    "error": "Bad Request"
}

// 400 Bad Request - Missing User Name
{
    "statusCode": 400,
    "message": "name should not be empty",
    "error": "Bad Request"
}

// 400 Bad Request - Missing User Email
{
    "statusCode": 400,
    "message": "email should not be empty",
    "error": "Bad Request"
}

// 400 Bad Request - Invalid Email Format
{
    "statusCode": 400,
    "message": "email must be a valid email address",
    "error": "Bad Request"
}

// 400 Bad Request - Missing User Zipcode
{
    "statusCode": 400,
    "message": "zipcode should not be empty",
    "error": "Bad Request"
}

// 400 Bad Request - Invalid ZIP Code Format (in user object)
{
    "statusCode": 400,
    "message": "zipcode must be a valid 5-digit US ZIP code",
    "error": "Bad Request"
}

// 400 Bad Request - No ZIP Code Available
{
    "statusCode": 400,
    "message": "No zipCode available. Please provide user information with zipCode or ensure customer/employee has a zipCode on file.",
    "error": "Bad Request"
}

// 400 Bad Request - User Data Mismatch with Existing Employee
{
    "statusCode": 400,
    "message": "User with email test@example.com already exists. Data mismatch detected: zipcode (provided: 10011, existing: 94102), state (provided: New York, existing: California). Please use the existing employee's information.",
    "error": "Bad Request"
}

// 400 Bad Request - Product Not Found
{
    "statusCode": 400,
    "message": "Product not found",
    "error": "Bad Request"
}

// 400 Bad Request - Product Not Available
{
    "statusCode": 400,
    "message": "Product not available for this customer",
    "error": "Bad Request"
}

// 401 Unauthorized - Missing or invalid token
{
    "statusCode": 401,
    "message": "Authentication required",
    "error": "Unauthorized"
}

// 500 Internal Server Error
{
    "statusCode": 500,
    "message": "eSIM purchase failed",
    "error": "Internal Server Error"
}

Get eSIM Purchase Status (Async)

GET /api/v1/purchase-esim/:requestId

Retrieves the current status of an eSIM purchase. Poll this endpoint to check when the asynchronous purchase completes.

Authentication

Requires JWT token in Authorization header: Bearer <token>

URL Parameters

Parameter Type Description
requestId string UUID returned from the purchase initiation endpoint

Success Response (200 OK)

// Status: PENDING (still processing)
{
    "requestId": "550e8400-e29b-41d4-a716-446655440000",
    "status": "PENDING",
    "createdAt": "2024-12-05T10:00:00.000Z",
    "updatedAt": "2024-12-05T10:00:00.000Z"
}

// Status: PROCESSING (in progress)
{
    "requestId": "550e8400-e29b-41d4-a716-446655440000",
    "status": "PROCESSING",
    "createdAt": "2024-12-05T10:00:00.000Z",
    "updatedAt": "2024-12-05T10:00:15.000Z"
}

// Status: SUCCESS (completed)
{
    "requestId": "550e8400-e29b-41d4-a716-446655440000",
    "status": "SUCCESS",
    "result": {
        "mdn": "1234567890",
        "iccid": "89012345678901234567",
        "qrCode": "https://s3.amazonaws.com/esim-activation-code/customer-id/iccid-esim-activation-qrcode.png"
    },
    "createdAt": "2024-12-05T10:00:00.000Z",
    "updatedAt": "2024-12-05T10:00:45.000Z"
}

// Status: FAILED (error occurred)
{
    "requestId": "550e8400-e29b-41d4-a716-446655440000",
    "status": "FAILED",
    "failureReason": "Insufficient inventory",
    "createdAt": "2024-12-05T10:00:00.000Z",
    "updatedAt": "2024-12-05T10:00:30.000Z"
}

Error Responses

// 404 Not Found - Request doesn't exist or unauthorized
{
    "statusCode": 404,
    "message": "Purchase request not found",
    "error": "Not Found"
}

// 401 Unauthorized
{
    "statusCode": 401,
    "message": "Authentication required",
    "error": "Unauthorized"
}

Register Webhook

POST /api/v1/webhooks/register

Registers webhook callback URLs for a specific operator and CDP. The eventType is provided in the request body and controls which URL fields are required. Delivery authentication is configured via the authentication object (e.g., HTTP Basic).

Request Body

{
    "operator": "SpenzaJ",
    "cdpType": "SpenzaJ",
    "eventType": "both",
    "authentication": {
        "type": "basic",
        "username": "webhook-user",
        "password": "super-secret"
    },
    "webhookUrls": {
        "messageUrl": "https://webhook.site/dcc5393e-cb1e-4005-9e80-1c80b7a011af",
        "callbackMessageUrl": "https://webhook.site/dcc5393e-cb1e-4005-9e80-1c80b7a011af",
        "voiceUrl": "https://webhook.site/dcc5393e-cb1e-4005-9e80-1c80b7a011af",
        "callbackVoiceUrl": "https://webhook.site/dcc5393e-cb1e-4005-9e80-1c80b7a011af"
    },
    "retryPolicy": {
        "maxAttempts": 5,
        "backoffStrategy": "fixed",
        "initialDelaySeconds": 2,
        "maxDelaySeconds": 300
    },
    "status": "active",
    "description": "Combined SMS and Voice webhooks"
}

Field Details

Field Type Description
operator string Operator identifier.
cdpType string Operator CDP type (e.g. SpenzaJ, SpenzaG). Must match the CDP type configured in the system.
eventType string sms, voice, or both. Determines which URL fields are required.
authentication.type string Delivery auth type (e.g. basic, none).
authentication.username string Username used when authentication.type = basic.
authentication.password string Password used when authentication.type = basic.
webhookUrls.messageUrl* string (URL) Inbound SMS webhook. Required for sms or both.
webhookUrls.callbackMessageUrl* string (URL) DLR webhook for SMS. Required for sms or both.
webhookUrls.voiceUrl* string (URL) Inbound voice webhook. Required for voice or both.
webhookUrls.callbackVoiceUrl* string (URL) Voice delivery status webhook. Required for voice or both.
retryPolicy.maxAttempts number Maximum retry attempts (default 5).
retryPolicy.backoffStrategy string Backoff strategy (e.g., fixed, exponential).
retryPolicy.initialDelaySeconds number Initial delay before first retry, in seconds.
retryPolicy.maxDelaySeconds number Maximum delay between retries, in seconds.
status string Registration status, e.g. active or inactive.
description string Human-readable description.

Fields marked with * are conditionally required based on eventType (sms, voice, or both).

Response

{
    "message": "Webhook registered",
    "registration": {
        "_id": "69146d70ed68e3890a45c228",
        "operator": "SpenzaJ",
        "cdpType": "SpenzaJ",
        "eventType": "both",
        "authentication": {
            "type": "basic",
            "username": "webhook-user"
        },
        "webhookUrls": {
            "messageUrl": "https://webhook.site/dcc5393e-cb1e-4005-9e80-1c80b7a011af",
            "callbackMessageUrl": "https://webhook.site/dcc5393e-cb1e-4005-9e80-1c80b7a011af",
            "voiceUrl": "https://webhook.site/dcc5393e-cb1e-4005-9e80-1c80b7a011af",
            "callbackVoiceUrl": "https://webhook.site/dcc5393e-cb1e-4005-9e80-1c80b7a011af"
        },
        "retryPolicy": {
            "maxAttempts": 5,
            "backoffStrategy": "fixed",
            "initialDelaySeconds": 2,
            "maxDelaySeconds": 300
        },
        "status": "active",
        "description": "Combined SMS and Voice webhooks",
        "createdAt": "2025-01-10T09:15:01.000Z",
        "updatedAt": "2025-01-10T09:15:01.000Z"
    }
}

Response objects always include both SMS and voice URL fields; unused fields are returned as null. Save the _id field to use when deleting this registration.

List Webhooks

GET /api/v1/webhooks/registrations

Retrieve registered webhook callbacks for the authenticated account. Use optional query parameters to filter results.

Query Parameters

Parameter Type Description
eventType string Filter by sms, voice, or both
operator string Filter by operator CDP type (e.g., SpenzaJ)

Response

{
    "registrations": [
        {
            "_id": "69146d70ed68e3890a45c228",
            "operator": "SpenzaJ",
            "cdpType": "SpenzaJ",
            "eventType": "both",
            "authentication": {
                "type": "basic",
                "username": "webhook-user"
            },
            "webhookUrls": {
                "messageUrl": "https://webhook.site/dcc5393e-cb1e-4005-9e80-1c80b7a011af",
                "callbackMessageUrl": "https://webhook.site/dcc5393e-cb1e-4005-9e80-1c80b7a011af",
                "voiceUrl": "https://webhook.site/dcc5393e-cb1e-4005-9e80-1c80b7a011af",
                "callbackVoiceUrl": "https://webhook.site/dcc5393e-cb1e-4005-9e80-1c80b7a011af"
            },
            "retryPolicy": {
                "maxAttempts": 5,
                "backoffStrategy": "fixed",
                "initialDelaySeconds": 2,
                "maxDelaySeconds": 300
            },
            "status": "active",
            "description": "Combined SMS and Voice webhooks",
            "createdAt": "2025-01-10T09:15:01.000Z",
            "updatedAt": "2025-01-10T09:15:01.000Z"
        }
    ]
}

Each entry includes both SMS and voice URL fields. Unused fields are returned as null.

Delete Webhook

DELETE /api/v1/webhooks/registrations/:id

Delete a webhook registration by its registration ID.

Path Parameters

Parameter Type Description
id string (MongoId) The registration ID (obtained from List Webhooks endpoint)

Response

{
    "message": "Webhook registration deleted successfully"
}

Error Responses

# Registration Not Found
{
    "error": "Webhook registration not found"
}

# Unauthorized
{
    "error": "Unauthorized"
}

Register SIP Config

POST /api/v1/customer/register-sip

Register or update your SIP configuration.

Request Body

{
    "sipIngressIp": "203.0.113.10",
    "sipPort": 5060,
    "transport": "udp",
    "codecs": ["ulaw", "alaw"],
    "didRanges": ["+1415000xxxx", "+1818444xxxx"],
    "allowedSourceIps": ["203.0.113.10", "203.0.113.11"]
}

Required Fields

Field Type Description
sipIngressIp string Your SIP ingress IP address

Response

{
    "message": "SIP configuration registered successfully",
    "sipConfig": {
        "sipIngressIp": "203.0.113.10",
        "sipPort": 5060,
        "transport": "udp",
        "codecs": ["ulaw", "alaw"],
        "didRanges": ["+1415000xxxx", "+1818444xxxx"],
        "allowedSourceIps": ["203.0.113.10", "203.0.113.11"]
    },
    "isSIPEnabled": true
}

Get SIP Config

GET /api/v1/customer/register-sip

Retrieve your current SIP configuration.

Response

{
    "isSIPEnabled": true,
    "sipConfig": {
        "sipIngressIp": "203.0.113.10",
        "sipPort": 5060,
        "transport": "udp",
        "codecs": ["ulaw", "alaw"],
        "didRanges": ["+1415000xxxx", "+1818444xxxx"],
        "allowedSourceIps": ["203.0.113.10", "203.0.113.11"]
    }
}

Delete SIP Config

DELETE /api/v1/customer/register-sip

Delete your SIP configuration and disable SIP.

Response

{
    "message": "SIP configuration deleted successfully"
}

V3 Async APIs - Introduction

The V3 API endpoints use an asynchronous pattern for long-running operations. Instead of waiting for the operation to complete, these endpoints return immediately with a transactionId that you can use to poll for status updates.

Key Features

Async Workflow

Step 1: Call any V3 endpoint (e.g., /api/v3/purchase-plan)
Step 2: Receive immediate response with transactionId and statusEndpoint
Step 3: Poll the status endpoint regularly (recommended: every 5 seconds)
Step 4: When status is COMPLETED, retrieve the final result

Transaction Statuses

Status Description
PENDING Transaction created, waiting to be processed
PROCESSING Operation is currently being executed
COMPLETED Operation finished successfully, result available
FAILED Operation failed, error message available

Benefits Over V1/V2

Get Transaction Status

GET /api/v3/transaction/:transactionId/status

Retrieves the current status of an async transaction. This endpoint is public and does not require authentication - the transaction ID acts as the authorization token.

URL Parameters

Parameter Type Description
transactionId string The transaction ID returned from the async API call

Response (PENDING)

{
    "success": true,
    "transactionId": "v3_1698765432100_abc123",
    "status": "PENDING",
    "message": "Transaction is pending",
    "timestamp": "2024-01-15T10:30:00.000Z",
    "createdAt": "2024-01-15T10:30:00.000Z",
    "updatedAt": "2024-01-15T10:30:00.000Z"
}

Response (COMPLETED)

{
    "success": true,
    "transactionId": "v3_1698765432100_abc123",
    "status": "COMPLETED",
    "message": "Transaction completed successfully",
    "timestamp": "2024-01-15T10:32:00.000Z",
    "result": {
        "subscription": {
            "subscriptionId": "sub_12345",
            "status": "ACTIVE",
            "planName": "Premium Plan"
        }
    },
    "createdAt": "2024-01-15T10:30:00.000Z",
    "updatedAt": "2024-01-15T10:32:00.000Z",
    "processingStartedAt": "2024-01-15T10:30:01.000Z",
    "processingCompletedAt": "2024-01-15T10:32:00.000Z"
}

Response (FAILED)

{
    "success": true,
    "transactionId": "v3_1698765432100_abc123",
    "status": "FAILED",
    "message": "Transaction failed",
    "timestamp": "2024-01-15T10:31:00.000Z",
    "error": "Insufficient balance",
    "createdAt": "2024-01-15T10:30:00.000Z",
    "updatedAt": "2024-01-15T10:31:00.000Z"
}

Performance: Status checks hit Redis cache first (~1-2ms), falling back to MongoDB only if needed (~20-50ms). Cache has 24-hour TTL.

Purchase Plan (Async)

POST /api/v3/purchase-plan

Initiates an asynchronous plan purchase. Returns immediately with a transaction ID for status polling.

Request Body

{
    "iccid": "8901260853182965429",
    "productName": "Premium_Plan_100GB",
    "activateNow": true,
    "scheduleDate": "2024-04-20"
}

Required Fields

Field Type Description
iccid string The ICCID of the SIM card
productName string The name/ID of the plan to purchase
activateNow boolean Optional. Defaults to true if scheduleDate omitted, false if scheduleDate provided
scheduleDate string Optional. Date to schedule activation (YYYY-MM-DD format)

Response

{
    "success": true,
    "transactionId": "v3_1698765432100_abc123",
    "statusEndpoint": "/api/v3/transaction/v3_1698765432100_abc123/status",
    "message": "Transaction initiated",
    "timestamp": "2024-01-15T10:30:00.000Z"
}

Timeout: 15 minutes. If the operation doesn't complete within this time, it will be marked as FAILED.

Activate Subscription (Async)

PUT /api/v3/activate-subscription

Activates a previously purchased subscription asynchronously.

Request Body

{
    "iccid": "8901260853182965429",
    "productId": "product_12345"
}

Required Fields

Field Type Description
iccid string The ICCID of the SIM card
productId string The ID of the plan to activate

Response

{
    "success": true,
    "transactionId": "v3_1698765432101_def456",
    "statusEndpoint": "/api/v3/transaction/v3_1698765432101_def456/status",
    "message": "Activation initiated",
    "timestamp": "2024-01-15T10:30:00.000Z"
}

Timeout: 15 minutes

Cancel Subscription (Async)

POST /api/v3/cancel-subscription

Cancels an active subscription asynchronously.

Request Body

{
    "iccid": "8901260853182965429"
}

Required Fields

Field Type Description
iccid string The ICCID of the SIM card with the subscription to cancel

Response

{
    "success": true,
    "transactionId": "v3_1698765432102_ghi789",
    "statusEndpoint": "/api/v3/transaction/v3_1698765432102_ghi789/status",
    "message": "Cancellation initiated",
    "timestamp": "2024-01-15T10:30:00.000Z"
}

Timeout: 15 minutes

eSIM Purchase (Async)

POST /api/v3/esim-purchase

Purchases and provisions an eSIM asynchronously. This operation can take up to 45 minutes.

Request Body

{
    "deviceId": "device_12345",
    "planId": "plan_67890",
    "imei": "356938035643809",
    "activateNow": true
}

Required Fields

Field Type Description
deviceId string The device identifier
planId string The ID of the plan to purchase
imei string Device IMEI number (15 digits)
activateNow boolean Whether to activate immediately

Response

{
    "success": true,
    "transactionId": "v3_1698765432103_jkl012",
    "statusEndpoint": "/api/v3/transaction/v3_1698765432103_jkl012/status",
    "message": "eSIM purchase initiated",
    "timestamp": "2024-01-15T10:30:00.000Z"
}

Timeout: 60 minutes. eSIM provisioning can take up to 45 minutes, so this operation has an extended timeout.

Renew Phone Number (Async)

POST /api/v3/renew-phone-number

Requests a new phone number for a SIM card asynchronously. Subject to rate limits based on your plan.

Request Body

{
    "iccid": "8901260853182965429"
}

Required Fields

Field Type Description
iccid string The ICCID of the SIM card

Response

{
    "success": true,
    "transactionId": "v3_1698765432104_mno345",
    "statusEndpoint": "/api/v3/transaction/v3_1698765432104_mno345/status",
    "message": "Phone number renewal initiated",
    "timestamp": "2024-01-15T10:30:00.000Z"
}

Rate Limiting

Monthly Rate Limits: This endpoint is subject to monthly rate limits based on your plan configuration. The limit is defined in the plan's apiLimits.renewPhoneNumber property.

Timeout: 15 minutes

Error Codes

HTTP Code Message Description
400 Missing Fields Required fields are missing in the request
400 Invalid API Credentials The provided API credentials are invalid
400 Plan Not Found The requested plan does not exist
400 Sim Not Found The requested SIM does not exist
400 Subscription Not Found The requested subscription does not exist
429 Rate Limit Exceeded Monthly rate limit exceeded for API requests
401 Unauthorized Invalid or missing authentication token
403 Forbidden Access denied or insufficient permissions
500 Internal Server Error Something went wrong at our end

Getting Started

# API Base URL
https://api-prod.spenza.com
# Required Headers
# All authenticated endpoints require these headers
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
# Authentication Flow
# Step 1: Get your API credentials from Spenza
# Step 2: Call /api/v1/authenticate to get access token
# Step 3: Use the token in all subsequent API calls

# Example: Authenticate first
$ curl \
    -X GET 'https://api-prod.spenza.com/api/v1/authenticate' \
    -H 'Content-Type: application/json' \
    -d '{
    "key": "your-api-key",
    "secret": "your-api-secret"
}'
# Response
{
    "message": "Authentication Success.",
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "expiration": "2024-01-16T10:30:00.000Z"
}
# Using the Token
# Include the token in Authorization header
$ curl \
    -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' \
    -H 'Content-Type: application/json' \
    'https://api-prod.spenza.com/api/v1/sim-by-iccid'

Authentication Example

# Request
# Get your authentication token
$ curl \
    -X GET 'https://api-prod.spenza.com/api/v1/authenticate' \
    -H 'Content-Type: application/json' \
    -d '{
    "key": "api-key",
    "secret": "api-secret"
}'
# Response
{
    "message": "Authentication Success.",
    "accessToken": "Your Auth Token for other APIs.",
    "expiration": "token expiration time"
}
# Example Usage with Token
# Use the received token in subsequent API calls
$ curl \
    -X GET 'https://api-prod.spenza.com/api/v1/sim-by-iccid' \
    -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
    "ICCID": "9999967890123456789"
}'
# Error Responses
# Missing Fields
{
    "errors": [{
        "message": "Api Key and Secrets are Required Fields.",
        "fields": ["key", "secret"]
    }]
}

# Missing field key
{
    "errors": [{
        "message": "Api Key is Required Field.",
        "fields": ["key"]
    }]
}

# Missing field secret
{
    "errors": [{
        "message": "Api Secret is Required Field.",
        "fields": ["secret"]
    }]
}

# Invalid API Credentials
{
    "errors": [{
        "message": "Invalid Api Credentials."
    }]
}

# Internal Server Error
{
    "errors": [{
        "message": "Something is Wrong at our end. Our Engineers are being Notified."
    }]
}

Get SIM

# Request
# Retrieve SIM information using ICCID
$ curl \
    -X GET 'https://api-prod.spenza.com/api/v1/sim-by-iccid' \
    -H 'Authorization: Bearer YOUR_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
    "ICCID": "9999967890123456789"
}'
# Response
{
    "message": "Success.",
    "simInfo": {
        "status": {
            "user": "Assigned",
            "device": "Assigned"
        },
        "operator": {
            "custNbr": null
        },
        "ICCID": "9999967890123456784",
        "activationCode": "",
        "source": "Other",
        "isDeleted": false,
        "recommendedPlan": "60af49af21647e2ca0b15f51",
        "_id": "632808a8e85110002e3ae629",
        "network": "T-Mobile",
        "Operator": "T-Mobile",
        "phoneNumber": "78993093857",
        "group": "63280750e85110002e3ae624",
        "account": "63280446e85110002e3ae54e",
        "phoneLineStatus": "ACTIVATED"
    }
}
# Error Responses
# Missing field ICCID
{
    "errors": [{
        "message": "ICCID is required field.",
        "field": "ICCID"
    }]
}

# Internal Server Error
{
    "errors": [{
        "message": "Something is Wrong at our end. Our Engineers are being Notified."
    }]
}

Get Plan

# Request
# Retrieve plan information using planId
$ curl \
    -X GET 'https://api-prod.spenza.com/api/v1/plan-by-id' \
    -H 'Authorization: Bearer YOUR_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
    "planId": "AT0001"
}'
# Response
{
    "message": "Success.",
    "plan": {
        "stripe": {
            "price": "price_1LgUXYAq7JKAyJICWrRooGBI"
        },
        "productName": "AT&T100",
        "category": "PLANS",
        "Operator": "AT&T",
        "network": "AT&T",
        "zone": "US",
        "validityDays": 30,
        "dataMB": 10,
        "speed": "4G",
        "price": 10,
        "inStock": 0,
        "durationType": "Monthly",
        "isDeleted": false,
        "_id": "63184df1203d4994052869f7",
        "description": "AT&T Plan 1",
        "productId": "AT0001",
        "feature": []
    }
}
# Error Responses
# Missing field planId
{
    "errors": [{
        "message": "Plan Id is Required.",
        "field": "planId"
    }]
}

# Plan Not Found
{
    "errors": [{
        "message": "Plan Not Found."
    }]
}

# Internal Server Error
{
    "errors": [{
        "message": "Something is Wrong at our end. Our Engineers are being Notified."
    }]
}

Purchase Plan

# Request
# Purchase a plan using ICCID, productId and schedule info
$ curl \
    -X POST 'https://api-prod.spenza.com/api/v1/purchase-plan' \
    -H 'Authorization: Bearer YOUR_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
    "ICCID": "8901260853182965429",
    "productId": "product1",
    "activateNow": true,
    "scheduledDate": "2024-04-20"
}'
# Response
{
    "message": "Success.",
    "subscription": {
        "stripe": {
            "subId": "sub_1LkKThAq7JKDyJICNJ3l1WWv",
            "scheduleId": null,
            "price": null
        },
        "operatorRes": {
            "MDN": null,
            "description": null
        },
        "planName": "EXPLAN_100",
        "planCode": "EXPLAN_001",
        "status": "ACTIVE",
        "initiatedDate": "2022-08-21T04:24:06.473Z",
        "startDate": "2022-08-21T04:24:05.000Z",
        "endDate": "2022-11-21T04:24:05.000Z",
        "scheduledDate": "2022-09-20T00:00:00.000Z",
        "cancelledDate": null,
        "autoTopUp": true,
        "subId": "1663734246474",
        "Operator": "T-Mobile",
        "network": "T-Mobile",
        "orderId": "1739-597673-2114"
    }
}
# Error Responses
# Missing field ICCID
{
    "errors": [{
        "message": "ICCID Key is Required Field.",
        "fields": ["ICCID"]
    }]
}

# Missing field productId
{
    "errors": [{
        "message": "productId is Required Field.",
        "fields": ["productId"]
    }]
}

# Missing field activateNow
{
    "errors": [{
        "message": "activateNow is Required Field.",
        "fields": ["activateNow"]
    }]
}

# Missing field scheduledDate
{
    "errors": [{
        "message": "scheduledDate is Required Field. Format:(YYYY-MM-DD)",
        "fields": ["scheduledDate"]
    }]
}

# Plan Not Found
{
    "errors": [{
        "message": "Plan Not Found."
    }]
}

# Operator Mismatch
{
    "errors": [{
        "message": "Sim Is Not Compatible. Please check Plan Operator and Sim Operator."
    }]
}

# Active Subscription Exists
{
    "errors": [{
        "message": "Sim Already has an Active Subscription."
    }]
}

# Internal Server Error
{
    "errors": [{
        "message": "Something Went Wrong at our End. Our Engineers are being Notified about this Error."
    }]
}

Activate Subscription

# Request
# Activate a purchased plan
$ curl \
    -X PUT 'https://api-prod.spenza.com/api/v1/activate-subscription' \
    -H 'Authorization: Bearer YOUR_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
    "ICCID": "8901260853182965429",
    "productId": "product1"
}'
# Response
{
    "message": "Success",
    "subscription": {
        "stripe": {
            "subId": "sub_1LkKThAq7JKDyJICNJ3l1WWv",
            "scheduleId": null,
            "price": null
        },
        "operatorRes": {
            "MDN": null,
            "description": null
        },
        "planName": "EXPLAN_101",
        "planCode": "EXPLAN_003",
        "status": "ACTIVE",
        "initiatedDate": "2022-08-21T04:24:06.473Z",
        "startDate": "2022-08-21T04:24:05.000Z",
        "endDate": "2022-11-21T04:24:05.000Z",
        "scheduledDate": "2022-09-20T00:00:00.000Z",
        "autoTopUp": true,
        "subId": "1663734246474",
        "Operator": "T-Mobile",
        "network": "T-Mobile",
        "orderId": "1739-597673-2114"
    }
}
# Error Responses
# Missing field ICCID
{
    "errors": [{
        "message": "ICCID is Required Field.",
        "fields": ["ICCID"]
    }]
}

# Missing field productId
{
    "errors": [{
        "message": "productId is Required Field.",
        "fields": ["productId"]
    }]
}

# Plan Not Found
{
    "errors": [{
        "message": "Plan Not Found."
    }]
}

# Sim Not Found
{
    "errors": [{
        "message": "Sim Not Found."
    }]
}

# Subscription Not Found
{
    "errors": [{
        "message": "Subscription Not Found."
    }]
}

# Internal Server Error
{
    "errors": [{
        "message": "Something is Wrong at our end. Our Engineers are being Notified."
    }]
}

Cancel Subscription

# Request
# Cancel an active subscription
$ curl \
    -X POST 'https://api-prod.spenza.com/api/v1/cancel-subscription' \
    -H 'Authorization: Bearer YOUR_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
    "ICCID": "9999967890123456789"
}'
# Response
{
    "message": "Subscription Cancelled.",
    "subscription": {
        "stripe": {
            "subId": "sub_1LkKThAq7JKDyJICNJ3l1WWv",
            "scheduleId": null,
            "price": null
        },
        "operatorRes": {
            "MDN": null,
            "description": null
        },
        "planName": "EXPLAN_100",
        "planCode": "EXPLAN_001",
        "status": "CANCELLED",
        "initiatedDate": "2022-08-21T04:24:06.473Z",
        "startDate": "2022-08-21T04:24:05.000Z",
        "endDate": "2022-11-21T04:24:05.000Z",
        "scheduledDate": "2022-09-20T00:00:00.000Z",
        "autoTopUp": true,
        "subId": "1663734246474",
        "Operator": "T-Mobile",
        "network": "T-Mobile",
        "orderId": "1739-597673-2114"
    }
}
# Error Responses
# Missing field ICCID
{
    "errors": [{
        "message": "ICCID is required field.",
        "fields": ["ICCID"]
    }]
}

# Sim Not Found
{
    "errors": [{
        "message": "Sim Not Found."
    }]
}

# Subscription Not Found
{
    "errors": [{
        "message": "Subscription Not Found."
    }]
}

# Internal Server Error
{
    "errors": [{
        "message": "Something is Wrong at our end. Our Engineers are being Notified."
    }]
}

Get Subscription

# Request
# Retrieve subscription by ICCID
$ curl \
    -X GET 'https://api-prod.spenza.com/api/v1/subscription-by-iccid' \
    -H 'Authorization: Bearer YOUR_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
    "ICCID": "9999967890123456789"
}'
# Response
{
    "message": "Success.",
    "subscription": {
        "stripe": {
            "subId": "sub_1LkKThAq7JKAyJICNJ3l1WWv",
            "scheduleId": null,
            "price": null
        },
        "operatorRes": {
            "MDN": null,
            "description": null
        },
        "planName": "T-Mobile100",
        "planCode": "T-Mobile0001",
        "status": "Pending",
        "initiatedDate": "2022-09-21T04:24:06.473Z",
        "startDate": "2022-09-21T04:24:05.000Z",
        "endDate": "2022-10-21T04:24:05.000Z",
        "scheduledDate": "2022-09-20T00:00:00.000Z",
        "cancelledDate": null,
        "autoTopUp": true,
        "subId": "1663734246473",
        "Operator": "T-Mobile",
        "network": "T-Mobile",
        "sim": "632a916b961e53002e1298d7",
        "account": "63280446e85110002e3ae54e",
        "orderId": "1339-597673-2114"
    }
}
# Error Responses
# Missing field ICCID
{
    "errors": [{
        "message": "ICCID is required field.",
        "field": "ICCID"
    }]
}

# Sim Not Found
{
    "errors": [{
        "message": "Sim Not Found."
    }]
}

# Subscription Not Found
{
    "errors": [{
        "message": "Subscription Not Found."
    }]
}

# Internal Server Error
{
    "errors": [{
        "message": "Something Went Wrong at our End. Our Engineers are being Notified about this Error."
    }]
}

Get Usage

# Request
# Retrieve the usage of a Sim
$ curl \
    -X POST 'https://api-prod.spenza.com/api/v1/telco/getUsage' \
    -H 'Authorization: Bearer YOUR_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
    "subId": "1745863440782"
}'
# Response
{
    "result": {
        "usageData": 0,
        "usageCalls": 0,
        "usageSms": 0,
        "usageUpdatedAt": 1747223324287,
        "telcoStatus": "ACTIVATED",
        "usageDataConverted": {
            "format": "B",
            "value": 0
        },
        "network": "Verizon",
        "networkCode": "Verizon"
    }
}
# Error Responses
# Missing field subId
{
    "errors": [{
        "message": "Subscription Id is a Required Field.",
        "fields": ["subId"]
    }]
}

# No Active Invoice
{
    "errors": [{
        "message": "Subscription Don't have any Active Invoices."
    }]
}

# Internal Server Error
{
    "errors": [{
        "message": "Something is Wrong at our end. Our Engineers are being Notified."
    }]
}

Renew Phone Number

# Request
# Request a new phone number for a SIM card
$ curl \
    -X POST 'https://api-prod.spenza.com/api/v1/renew-phone-number' \
    -H 'Authorization: Bearer YOUR_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
    "ICCID": "9999967890123456789"
}'
# Response
{
    "message": "Request placed successfully, new number will be assigned soon"
}
# Error Responses
# Missing field ICCID
{
    "errors": [{
        "message": "ICCID is required field.",
        "fields": ["ICCID"]
    }]
}

# Subscription Not Found
{
    "errors": [{
        "message": "Subscription Not Found. Please Create an Active Subscription First."
    }]
}

# Rate Limit Exceeded
{
    "errors": [{
        "message": "Monthly rate limit exceeded for renew phone number requests."
    }]
}

# Internal Server Error
{
    "error": "Something went wrong"
}

Validate ICCID

# Request
# Validate ICCID format (no auth required)
$ curl \
    -X POST 'https://api-prod.spenza.com/api/v1/public/validate-iccid' \
    -H 'Content-Type: application/json' \
    -d '{
    "ICCID": "8901260853182965429"
}'
# Response
{
    "isValid": true,
    "message": "Valid ICCID."
}

Validate IMEI

# Request
# Validate IMEI format (no auth required)
$ curl \
    -X POST 'https://api-prod.spenza.com/api/v1/public/validate-imei' \
    -H 'Content-Type: application/json' \
    -d '{
    "IMEI": "356938035643809"
}'
# Response
{
    "isValid": true,
    "message": "Valid IMEI."
}

Purchase eSIM (Async)

# Request - Basic
# Purchase eSIM with IMEI and SIM ID only
$ curl \
    -X POST 'https://api-prod.spenza.com/api/v1/purchase-esim' \
    -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
    "imei": "451014850281267",
    "simId": "TEST_SPENZA"
}'
# Request - With User Info
# Purchase eSIM with user information for number provisioning
$ curl \
    -X POST 'https://api-prod.spenza.com/api/v1/purchase-esim' \
    -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
    "imei": "451014850281267",
    "simId": "SpenzaJ_ESIM",
    "user": {
        "name": "Testung",
        "email": "test+8@spenza.com",
        "phoneNumber": "2031134322",
        "address": "47 W 13th St",
        "city": "New York",
        "state": "New York",
        "zipcode": "10011"
    }
}'
# Response
{
    "requestId": "64dfa874-f157-431a-a6db-d8e5a915ea12",
    "status": "PENDING",
    "message": "eSIM purchase initiated successfully"
}
# Response Flow
# After successful eSIM purchase:
# 1. Subscription is created with PENDING status
# 2. Phone number is provisioned based on the user's ZIP code location
# 3. eSIM QR code is generated and sent to customer via email
# 4. Customer can activate eSIM by scanning the QR code

Get eSIM Purchase Status

# Request
# Poll for purchase status 
$ curl \
    -X GET 'https://api-prod.spenza.com/api/v1/purchase-esim/550e8400-e29b-41d4-a716-446655440000' \
    -H 'Authorization: Bearer YOUR_JWT_TOKEN'
# Response (Success)
{
    "requestId": "550e8400-e29b-41d4-a716-446655440000",
    "status": "SUCCESS",
    "result": {
        "mdn": "1234567890",
        "iccid": "89012345678901234567",
        "qrCode": "https://s3.amazonaws.com/esim-activation-code/customer-id/iccid-esim-activation-qrcode.png"
    },
    "createdAt": "2024-12-05T10:00:00.000Z",
    "updatedAt": "2024-12-05T10:00:45.000Z"
}

Register Webhook

# Request
# Register webhook for SMS and Voice events
$ curl \
    -X POST 'https://api-prod.spenza.com/api/v1/webhooks/register' \
    -H 'Authorization: Bearer YOUR_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
    "operator": "SpenzaJ",
    "cdpType": "SpenzaJ",
    "eventType": "both",
    "authentication": {
        "type": "basic",
        "username": "webhook-user",
        "password": "super-secret"
    },
    "webhookUrls": {
        "messageUrl": "https://webhook.site/dcc5393e-cb1e-4005-9e80-1c80b7a011af",
        "callbackMessageUrl": "https://webhook.site/dcc5393e-cb1e-4005-9e80-1c80b7a011af",
        "voiceUrl": "https://webhook.site/dcc5393e-cb1e-4005-9e80-1c80b7a011af",
        "callbackVoiceUrl": "https://webhook.site/dcc5393e-cb1e-4005-9e80-1c80b7a011af"
    },
    "retryPolicy": {
        "maxAttempts": 5,
        "backoffStrategy": "fixed",
        "initialDelaySeconds": 2,
        "maxDelaySeconds": 300
    },
    "status": "active",
    "description": "Combined SMS and Voice webhooks for production"
}'
# Response
{
    "message": "Webhook registered",
    "registration": {
        "_id": "69146d70ed68e3890a45c228",
        "operator": "SpenzaJ",
        "cdpType": "SpenzaJ",
        "eventType": "both",
        "authentication": {
            "type": "basic",
            "username": "webhook-user"
        },
        "webhookUrls": {
            "messageUrl": "https://webhook.site/dcc5393e-cb1e-4005-9e80-1c80b7a011af",
            "callbackMessageUrl": "https://webhook.site/dcc5393e-cb1e-4005-9e80-1c80b7a011af",
            "voiceUrl": "https://webhook.site/dcc5393e-cb1e-4005-9e80-1c80b7a011af",
            "callbackVoiceUrl": "https://webhook.site/dcc5393e-cb1e-4005-9e80-1c80b7a011af"
        },
        "retryPolicy": {
            "maxAttempts": 5,
            "backoffStrategy": "fixed",
            "initialDelaySeconds": 2,
            "maxDelaySeconds": 300
        },
        "status": "active",
        "description": "Combined SMS and Voice webhooks for production",
        "createdAt": "2025-01-10T09:15:01.000Z",
        "updatedAt": "2025-01-10T09:15:01.000Z"
    }
}

Response objects always include both SMS and voice URL fields; unused fields are returned as null. Save the _id field to delete this registration later.

List Webhooks

# Request
# List registered webhooks
$ curl \
    -X GET 'https://api-prod.spenza.com/api/v1/webhooks/registrations?eventType=both' \
    -H 'Authorization: Bearer YOUR_TOKEN'
# Response
{
    "registrations": [
        {
            "_id": "69146d70ed68e3890a45c228",
            "operator": "SpenzaJ",
            "cdpType": "SpenzaJ",
            "eventType": "both",
            "authentication": {
                "type": "basic",
                "username": "webhook-user"
            },
            "webhookUrls": {
                "messageUrl": "https://webhook.site/dcc5393e-cb1e-4005-9e80-1c80b7a011af",
                "callbackMessageUrl": "https://webhook.site/dcc5393e-cb1e-4005-9e80-1c80b7a011af",
                "voiceUrl": "https://webhook.site/dcc5393e-cb1e-4005-9e80-1c80b7a011af",
                "callbackVoiceUrl": "https://webhook.site/dcc5393e-cb1e-4005-9e80-1c80b7a011af"
            },
            "retryPolicy": {
                "maxAttempts": 5,
                "backoffStrategy": "fixed",
                "initialDelaySeconds": 2,
                "maxDelaySeconds": 300
            },
            "status": "active",
            "description": "Combined SMS and Voice webhooks",
            "createdAt": "2025-01-10T09:15:01.000Z",
            "updatedAt": "2025-01-10T09:15:01.000Z"
        }
    ]
}

Each entry includes both SMS and voice URL fields. Unused fields are returned as null.

Delete Webhook

# Request
# Delete webhook registration by ID
$ curl \
    -X DELETE 'https://api-prod.spenza.com/api/v1/webhooks/registrations/69146d70ed68e3890a45c228' \
    -H 'Authorization: Bearer YOUR_TOKEN'
# Response
{
    "message": "Webhook registration deleted successfully"
}
# Error Responses
# Registration Not Found
{
    "error": "Webhook registration not found"
}

# Unauthorized
{
    "error": "Unauthorized"
}

Register SIP Config

# Request
# Register or update SIP configuration
$ curl \
    -X POST 'https://api-prod.spenza.com/api/v1/customer/register-sip' \
    -H 'Authorization: Bearer YOUR_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
    "sipIngressIp": "203.0.113.10",
    "sipPort": 5060,
    "transport": "udp",
    "codecs": ["ulaw", "alaw"],
    "didRanges": ["+1415000xxxx", "+1818444xxxx"],
    "allowedSourceIps": ["203.0.113.10", "203.0.113.11"]
}'
# Response
{
    "message": "SIP configuration registered successfully",
    "sipConfig": {
        "sipIngressIp": "203.0.113.10",
        "sipPort": 5060,
        "transport": "udp",
        "codecs": ["ulaw", "alaw"],
        "didRanges": ["+1415000xxxx", "+1818444xxxx"],
        "allowedSourceIps": ["203.0.113.10", "203.0.113.11"]
    },
    "isSIPEnabled": true
}

Get SIP Config

# Request
# Retrieve your SIP configuration
$ curl \
    -X GET 'https://api-prod.spenza.com/api/v1/customer/register-sip' \
    -H 'Authorization: Bearer YOUR_TOKEN'
# Response
{
    "isSIPEnabled": true,
    "sipConfig": {
        "sipIngressIp": "203.0.113.10",
        "sipPort": 5060,
        "transport": "udp",
        "codecs": ["ulaw", "alaw"],
        "didRanges": ["+1415000xxxx", "+1818444xxxx"],
        "allowedSourceIps": ["203.0.113.10", "203.0.113.11"]
    }
}

Delete SIP Config

# Request
# Delete SIP configuration and disable SIP
$ curl \
    -X DELETE 'https://api-prod.spenza.com/api/v1/customer/register-sip' \
    -H 'Authorization: Bearer YOUR_TOKEN'
# Response
{
    "message": "SIP configuration deleted successfully"
}

V3 Async APIs

# Async Workflow Example
# Step 1: Initiate async operation
$ curl \
    -X POST 'https://api-prod.spenza.com/api/v3/purchase-plan' \
    -H 'Authorization: Bearer YOUR_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
    "iccid": "8901260853182965429",
    "productName": "Premium_Plan",
    "activateNow": true
}'
# Response (Immediate)
{
    "success": true,
    "transactionId": "v3_1698765432100_abc123",
    "statusEndpoint": "/api/v3/transaction/v3_1698765432100_abc123/status",
    "message": "Transaction initiated",
    "timestamp": "2024-01-15T10:30:00.000Z"
}
# Step 2: Poll for status (recommended: every 5 seconds)
$ curl \
    'https://api-prod.spenza.com/api/v3/transaction/v3_1698765432100_abc123/status'
# Status Response (COMPLETED)
{
    "success": true,
    "transactionId": "v3_1698765432100_abc123",
    "status": "COMPLETED",
    "result": {
        "subscription": {
            "subscriptionId": "sub_12345",
            "status": "ACTIVE"
        }
    },
    "timestamp": "2024-01-15T10:32:00.000Z"
}

Get Transaction Status

# Request
# Check transaction status (no auth required)
$ curl \
    'https://api-prod.spenza.com/api/v3/transaction/v3_1698765432100_abc123/status'
# Response (PENDING)
{
    "success": true,
    "transactionId": "v3_1698765432100_abc123",
    "status": "PENDING",
    "message": "Transaction is pending",
    "timestamp": "2024-01-15T10:30:00.000Z"
}
# Response (COMPLETED)
{
    "success": true,
    "transactionId": "v3_1698765432100_abc123",
    "status": "COMPLETED",
    "result": {
        "subscription": {
            "subscriptionId": "sub_12345",
            "status": "ACTIVE"
        }
    },
    "processingStartedAt": "2024-01-15T10:30:01.000Z",
    "processingCompletedAt": "2024-01-15T10:32:00.000Z"
}
# Response (FAILED)
{
    "success": true,
    "transactionId": "v3_1698765432100_abc123",
    "status": "FAILED",
    "error": "Insufficient balance",
    "timestamp": "2024-01-15T10:31:00.000Z"
}

Purchase Plan (V3 Async)

# Request
# Initiate async plan purchase
$ curl \
    -X POST 'https://api-prod.spenza.com/api/v3/purchase-plan' \
    -H 'Authorization: Bearer YOUR_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
    "iccid": "8901260853182965429",
    "productName": "Premium_Plan_100GB",
    "activateNow": true
}'
# Response
{
    "success": true,
    "transactionId": "v3_1698765432100_abc123",
    "statusEndpoint": "/api/v3/transaction/v3_1698765432100_abc123/status",
    "message": "Transaction initiated",
    "timestamp": "2024-01-15T10:30:00.000Z"
}
# With Schedule Date
$ curl \
    -X POST 'https://api-prod.spenza.com/api/v3/purchase-plan' \
    -H 'Authorization: Bearer YOUR_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
    "iccid": "8901260853182965429",
    "productName": "Premium_Plan",
    "scheduleDate": "2024-04-20"
}'

Activate Subscription (V3 Async)

# Request
# Activate subscription asynchronously
$ curl \
    -X PUT 'https://api-prod.spenza.com/api/v3/activate-subscription' \
    -H 'Authorization: Bearer YOUR_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
    "iccid": "8901260853182965429",
    "productId": "product_12345"
}'
# Response
{
    "success": true,
    "transactionId": "v3_1698765432101_def456",
    "statusEndpoint": "/api/v3/transaction/v3_1698765432101_def456/status",
    "message": "Activation initiated",
    "timestamp": "2024-01-15T10:30:00.000Z"
}

Cancel Subscription (V3 Async)

# Request
# Cancel subscription asynchronously
$ curl \
    -X POST 'https://api-prod.spenza.com/api/v3/cancel-subscription' \
    -H 'Authorization: Bearer YOUR_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
    "iccid": "8901260853182965429"
}'
# Response
{
    "success": true,
    "transactionId": "v3_1698765432102_ghi789",
    "statusEndpoint": "/api/v3/transaction/v3_1698765432102_ghi789/status",
    "message": "Cancellation initiated",
    "timestamp": "2024-01-15T10:30:00.000Z"
}

eSIM Purchase (V3 Async)

# Request
# Initiate eSIM purchase (can take up to 45 minutes)
$ curl \
    -X POST 'https://api-prod.spenza.com/api/v3/esim-purchase' \
    -H 'Authorization: Bearer YOUR_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
    "deviceId": "device_12345",
    "planId": "plan_67890",
    "imei": "356938035643809",
    "activateNow": true
}'
# Response
{
    "success": true,
    "transactionId": "v3_1698765432103_jkl012",
    "statusEndpoint": "/api/v3/transaction/v3_1698765432103_jkl012/status",
    "message": "eSIM purchase initiated",
    "timestamp": "2024-01-15T10:30:00.000Z"
}
# Completed Result
{
    "success": true,
    "transactionId": "v3_1698765432103_jkl012",
    "status": "COMPLETED",
    "result": {
        "mdn": "1234567890",
        "iccid": "89012345678901234567",
        "qrCode": "https://s3.amazonaws.com/esim-qr-codes/abc123.png"
    }
}

Renew Phone Number (V3 Async)

# Request
# Request new phone number asynchronously
$ curl \
    -X POST 'https://api-prod.spenza.com/api/v3/renew-phone-number' \
    -H 'Authorization: Bearer YOUR_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
    "iccid": "8901260853182965429"
}'
# Response
{
    "success": true,
    "transactionId": "v3_1698765432104_mno345",
    "statusEndpoint": "/api/v3/transaction/v3_1698765432104_mno345/status",
    "message": "Phone number renewal initiated",
    "timestamp": "2024-01-15T10:30:00.000Z"
}
# Completed Result
{
    "success": true,
    "transactionId": "v3_1698765432104_mno345",
    "status": "COMPLETED",
    "result": {
        "newPhoneNumber": "+15551234567",
        "iccid": "8901260853182965429"
    }
}

Error Examples

# 400 Bad Request
{
    "errors": [{
        "message": "Missing required field: ICCID",
        "fields": ["ICCID"]
    }]
}
# 401 Unauthorized
{
    "statusCode": 401,
    "message": "Invalid or expired authentication token",
    "error": "Unauthorized"
}
# 403 Forbidden
{
    "statusCode": 403,
    "message": "Access denied. Insufficient permissions.",
    "error": "Forbidden"
}
# 404 Not Found
{
    "statusCode": 404,
    "message": "Resource not found",
    "error": "Not Found"
}
# 429 Rate Limit Exceeded
{
    "statusCode": 429,
    "message": "Monthly rate limit exceeded for this operation",
    "error": "Too Many Requests",
    "retryAfter": "2024-02-01T00:00:00.000Z"
}
# 500 Internal Server Error
{
    "statusCode": 500,
    "message": "Something went wrong at our end. Our engineers are being notified.",
    "error": "Internal Server Error"
}
# Example: Handling Errors
# Always check the HTTP status code
$ curl \
    -w '\nHTTP Status: %{http_code}\n' \
    -X GET 'https://api-prod.spenza.com/api/v1/sim-by-iccid' \
    -H 'Authorization: Bearer YOUR_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
    "ICCID": "8901260853182965429"
}'