# Set your Bearer token
TOKEN="your_jwt_bearer_token_here"
curl -X POST https://kyc.sbx.moduluslabs.io/v2/onboard/signup \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"contact": {
"email": "john.doe@example.com",
"phone": "09171234567"
},
"user": {
"firstName": "John",
"middleName": "Michael",
"lastName": "Doe",
"username": "john.doe",
"password": "SecurePass123!",
"pin": "1234"
}
}'
const axios = require('axios');
const BEARER_TOKEN = process.env.BEARER_TOKEN; // Your JWT Bearer token
async function createAccount() {
const accountData = {
contact: {
email: 'john.doe@example.com',
phone: '09171234567'
},
user: {
firstName: 'John',
middleName: 'Michael',
lastName: 'Doe',
username: 'john.doe',
password: 'SecurePass123!',
pin: '1234'
}
};
try {
const response = await axios.post(
'https://kyc.sbx.moduluslabs.io/v2/onboard/signup',
accountData,
{
headers: {
'Authorization': `Bearer ${BEARER_TOKEN}`,
'Content-Type': 'application/json'
}
}
);
console.log(' Account created successfully');
console.log('Account ID:', response.data.id);
return response.data;
} catch (error) {
if (error.response?.status === 401) {
console.error(' Unauthorized. Check your Bearer token.');
} else if (error.response?.status === 429) {
console.error(' Rate limit exceeded. Please wait before retrying.');
} else {
console.error(' Account creation failed:', error.response?.data || error.message);
}
throw error;
}
}
createAccount();
import os
import requests
BEARER_TOKEN = os.getenv('BEARER_TOKEN') # Your JWT Bearer token
def create_account():
"""Create a new user account"""
account_data = {
'contact': {
'email': 'john.doe@example.com',
'phone': '09171234567'
},
'user': {
'firstName': 'John',
'middleName': 'Michael',
'lastName': 'Doe',
'username': 'john.doe',
'password': 'SecurePass123!',
'pin': '1234'
}
}
try:
response = requests.post(
'https://kyc.sbx.moduluslabs.io/v2/onboard/signup',
json=account_data,
headers={
'Authorization': f'Bearer {BEARER_TOKEN}',
'Content-Type': 'application/json'
}
)
response.raise_for_status()
print(' Account created successfully')
print(f"Account ID: {response.json()['id']}")
return response.json()
except requests.exceptions.HTTPError as e:
if e.response.status_code == 401:
print(' Unauthorized. Check your Bearer token.')
elif e.response.status_code == 429:
print(' Rate limit exceeded. Please wait before retrying.')
else:
print(f' Account creation failed: {e.response.text}')
raise
if __name__ == '__main__':
create_account()
<?php
require 'vendor/autoload.php'; // composer require guzzlehttp/guzzle
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
$bearerToken = getenv('BEARER_TOKEN'); // Your JWT Bearer token
/**
* Create a new user account
*/
function createAccount() {
global $bearerToken;
$accountData = [
'contact' => [
'email' => 'john.doe@example.com',
'phone' => '09171234567'
],
'user' => [
'firstName' => 'John',
'middleName' => 'Michael',
'lastName' => 'Doe',
'username' => 'john.doe',
'password' => 'SecurePass123!',
'pin' => '1234'
]
];
try {
$client = new Client();
$response = $client->post('https://kyc.sbx.moduluslabs.io/v2/onboard/signup', [
'headers' => [
'Authorization' => 'Bearer ' . $bearerToken,
'Content-Type' => 'application/json'
],
'json' => $accountData
]);
$body = json_decode($response->getBody(), true);
echo " Account created successfully\n";
echo "Account ID: " . $body['id'] . "\n";
return $body;
} catch (RequestException $e) {
if ($e->hasResponse()) {
$statusCode = $e->getResponse()->getStatusCode();
if ($statusCode === 401) {
echo " Unauthorized. Check your Bearer token.\n";
} elseif ($statusCode === 429) {
echo " Rate limit exceeded. Please wait before retrying.\n";
} else {
echo " Account creation failed: " . $e->getMessage() . "\n";
echo $e->getResponse()->getBody() . "\n";
}
} else {
echo " Account creation failed: " . $e->getMessage() . "\n";
}
throw $e;
}
}
createAccount();
?>
import com.google.gson.Gson;
import okhttp3.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class CreateAccount {
private static final String BEARER_TOKEN = System.getenv("BEARER_TOKEN"); // Your JWT Bearer token
private static final String API_URL = "https://kyc.sbx.moduluslabs.io/v2/onboard/signup";
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
/**
* Create a new user account
*/
public static void createAccount() throws IOException {
// Create account data
Map<String, Object> accountData = new HashMap<>();
Map<String, String> contact = new HashMap<>();
contact.put("email", "john.doe@example.com");
contact.put("phone", "09171234567");
accountData.put("contact", contact);
Map<String, String> user = new HashMap<>();
user.put("firstName", "John");
user.put("middleName", "Michael");
user.put("lastName", "Doe");
user.put("username", "john.doe");
user.put("password", "SecurePass123!");
user.put("pin", "1234");
accountData.put("user", user);
// Send request
Gson gson = new Gson();
String json = gson.toJson(accountData);
OkHttpClient client = new OkHttpClient();
RequestBody body = RequestBody.create(json, JSON);
Request request = new Request.Builder()
.url(API_URL)
.header("Authorization", "Bearer " + BEARER_TOKEN)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
String responseBody = response.body().string();
if (response.isSuccessful()) {
System.out.println(" Account created successfully");
System.out.println("Response: " + responseBody);
} else if (response.code() == 401) {
System.out.println(" Unauthorized. Check your Bearer token.");
} else if (response.code() == 429) {
System.out.println(" Rate limit exceeded. Please wait before retrying.");
} else {
System.out.println(" Account creation failed: HTTP " + response.code());
System.out.println("Details: " + responseBody);
}
}
}
public static void main(String[] args) {
try {
createAccount();
} catch (IOException e) {
System.err.println(" Error: " + e.getMessage());
e.printStackTrace();
}
}
}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
)
const apiURL = "https://kyc.sbx.moduluslabs.io/v2/onboard/signup"
// createAccount creates a new user account
func createAccount() error {
bearerToken := os.Getenv("BEARER_TOKEN") // Your JWT Bearer token
accountData := map[string]interface{}{
"contact": map[string]string{
"email": "john.doe@example.com",
"phone": "09171234567",
},
"user": map[string]string{
"firstName": "John",
"middleName": "Michael",
"lastName": "Doe",
"username": "john.doe",
"password": "SecurePass123!",
"pin": "1234",
},
}
jsonData, err := json.Marshal(accountData)
if err != nil {
return fmt.Errorf("failed to marshal data: %w", err)
}
req, err := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+bearerToken)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response: %w", err)
}
if resp.StatusCode == http.StatusOK {
var result map[string]interface{}
if err := json.Unmarshal(body, &result); err != nil {
return fmt.Errorf("failed to parse response: %w", err)
}
fmt.Println(" Account created successfully")
fmt.Printf("Account ID: %.0f\n", result["id"])
return nil
} else if resp.StatusCode == http.StatusUnauthorized {
fmt.Println(" Unauthorized. Check your Bearer token.")
return fmt.Errorf("unauthorized")
} else if resp.StatusCode == http.StatusTooManyRequests {
fmt.Println(" Rate limit exceeded. Please wait before retrying.")
return fmt.Errorf("rate limit exceeded")
}
fmt.Printf(" Account creation failed: HTTP %d\n", resp.StatusCode)
fmt.Printf("Details: %s\n", string(body))
return fmt.Errorf("account creation failed with status %d", resp.StatusCode)
}
func main() {
if err := createAccount(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
await CreateAccount();
}
static async Task CreateAccount()
{
// Get Bearer token from environment variable
var bearerToken = Environment.GetEnvironmentVariable("BEARER_TOKEN");
if (string.IsNullOrEmpty(bearerToken))
{
Console.WriteLine(" Error: BEARER_TOKEN environment variable not set");
return;
}
try
{
using var client = new HttpClient();
// Set Bearer token authorization
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", bearerToken);
// Prepare account data
var accountData = new
{
contact = new
{
email = "john.doe@example.com",
phone = "09171234567"
},
user = new
{
firstName = "John",
middleName = "Michael",
lastName = "Doe",
username = "john.doe",
password = "SecurePass123!",
pin = "1234"
}
};
var content = new StringContent(
JsonSerializer.Serialize(accountData),
Encoding.UTF8,
"application/json"
);
// Send POST request
var response = await client.PostAsync(
"https://kyc.sbx.moduluslabs.io/v2/onboard/signup",
content
);
var responseBody = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
var result = JsonSerializer.Deserialize<CreateAccountResponse>(
responseBody,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
);
Console.WriteLine(" Account created successfully");
Console.WriteLine($"Account ID: {result.Id}");
}
else if ((int)response.StatusCode == 401)
{
Console.WriteLine(" Unauthorized. Check your Bearer token.");
}
else if ((int)response.StatusCode == 429)
{
Console.WriteLine(" Rate limit exceeded. Please wait before retrying.");
}
else
{
Console.WriteLine($" Account creation failed: HTTP {(int)response.StatusCode}");
Console.WriteLine($"Details: {responseBody}");
}
}
catch (HttpRequestException e)
{
Console.WriteLine($" Request failed: {e.Message}");
}
catch (Exception e)
{
Console.WriteLine($" Error: {e.Message}");
}
}
}
// Response model
public class CreateAccountResponse
{
public int Id { get; set; }
}
{
"id": 12345
}
{
"statusCode": 400,
"message": "user.password must have at least 1 uppercase and lowercase character, a number, and special character.",
"error": "Bad Request"
}
{
"statusCode": 401,
"message": "Unauthorized",
"error": "Unauthorized"
}
{
"statusCode": 429,
"message": "ThrottlerException: Too Many Requests",
"error": "Too Many Requests"
}
{
"statusCode": 500,
"message": "Internal server error",
"error": "Internal Server Error"
}
Onboarding API
Create Account
Create a new user account with contact information and credentials
POST
/
v2
/
account
# Set your Bearer token
TOKEN="your_jwt_bearer_token_here"
curl -X POST https://kyc.sbx.moduluslabs.io/v2/onboard/signup \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"contact": {
"email": "john.doe@example.com",
"phone": "09171234567"
},
"user": {
"firstName": "John",
"middleName": "Michael",
"lastName": "Doe",
"username": "john.doe",
"password": "SecurePass123!",
"pin": "1234"
}
}'
const axios = require('axios');
const BEARER_TOKEN = process.env.BEARER_TOKEN; // Your JWT Bearer token
async function createAccount() {
const accountData = {
contact: {
email: 'john.doe@example.com',
phone: '09171234567'
},
user: {
firstName: 'John',
middleName: 'Michael',
lastName: 'Doe',
username: 'john.doe',
password: 'SecurePass123!',
pin: '1234'
}
};
try {
const response = await axios.post(
'https://kyc.sbx.moduluslabs.io/v2/onboard/signup',
accountData,
{
headers: {
'Authorization': `Bearer ${BEARER_TOKEN}`,
'Content-Type': 'application/json'
}
}
);
console.log(' Account created successfully');
console.log('Account ID:', response.data.id);
return response.data;
} catch (error) {
if (error.response?.status === 401) {
console.error(' Unauthorized. Check your Bearer token.');
} else if (error.response?.status === 429) {
console.error(' Rate limit exceeded. Please wait before retrying.');
} else {
console.error(' Account creation failed:', error.response?.data || error.message);
}
throw error;
}
}
createAccount();
import os
import requests
BEARER_TOKEN = os.getenv('BEARER_TOKEN') # Your JWT Bearer token
def create_account():
"""Create a new user account"""
account_data = {
'contact': {
'email': 'john.doe@example.com',
'phone': '09171234567'
},
'user': {
'firstName': 'John',
'middleName': 'Michael',
'lastName': 'Doe',
'username': 'john.doe',
'password': 'SecurePass123!',
'pin': '1234'
}
}
try:
response = requests.post(
'https://kyc.sbx.moduluslabs.io/v2/onboard/signup',
json=account_data,
headers={
'Authorization': f'Bearer {BEARER_TOKEN}',
'Content-Type': 'application/json'
}
)
response.raise_for_status()
print(' Account created successfully')
print(f"Account ID: {response.json()['id']}")
return response.json()
except requests.exceptions.HTTPError as e:
if e.response.status_code == 401:
print(' Unauthorized. Check your Bearer token.')
elif e.response.status_code == 429:
print(' Rate limit exceeded. Please wait before retrying.')
else:
print(f' Account creation failed: {e.response.text}')
raise
if __name__ == '__main__':
create_account()
<?php
require 'vendor/autoload.php'; // composer require guzzlehttp/guzzle
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
$bearerToken = getenv('BEARER_TOKEN'); // Your JWT Bearer token
/**
* Create a new user account
*/
function createAccount() {
global $bearerToken;
$accountData = [
'contact' => [
'email' => 'john.doe@example.com',
'phone' => '09171234567'
],
'user' => [
'firstName' => 'John',
'middleName' => 'Michael',
'lastName' => 'Doe',
'username' => 'john.doe',
'password' => 'SecurePass123!',
'pin' => '1234'
]
];
try {
$client = new Client();
$response = $client->post('https://kyc.sbx.moduluslabs.io/v2/onboard/signup', [
'headers' => [
'Authorization' => 'Bearer ' . $bearerToken,
'Content-Type' => 'application/json'
],
'json' => $accountData
]);
$body = json_decode($response->getBody(), true);
echo " Account created successfully\n";
echo "Account ID: " . $body['id'] . "\n";
return $body;
} catch (RequestException $e) {
if ($e->hasResponse()) {
$statusCode = $e->getResponse()->getStatusCode();
if ($statusCode === 401) {
echo " Unauthorized. Check your Bearer token.\n";
} elseif ($statusCode === 429) {
echo " Rate limit exceeded. Please wait before retrying.\n";
} else {
echo " Account creation failed: " . $e->getMessage() . "\n";
echo $e->getResponse()->getBody() . "\n";
}
} else {
echo " Account creation failed: " . $e->getMessage() . "\n";
}
throw $e;
}
}
createAccount();
?>
import com.google.gson.Gson;
import okhttp3.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class CreateAccount {
private static final String BEARER_TOKEN = System.getenv("BEARER_TOKEN"); // Your JWT Bearer token
private static final String API_URL = "https://kyc.sbx.moduluslabs.io/v2/onboard/signup";
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
/**
* Create a new user account
*/
public static void createAccount() throws IOException {
// Create account data
Map<String, Object> accountData = new HashMap<>();
Map<String, String> contact = new HashMap<>();
contact.put("email", "john.doe@example.com");
contact.put("phone", "09171234567");
accountData.put("contact", contact);
Map<String, String> user = new HashMap<>();
user.put("firstName", "John");
user.put("middleName", "Michael");
user.put("lastName", "Doe");
user.put("username", "john.doe");
user.put("password", "SecurePass123!");
user.put("pin", "1234");
accountData.put("user", user);
// Send request
Gson gson = new Gson();
String json = gson.toJson(accountData);
OkHttpClient client = new OkHttpClient();
RequestBody body = RequestBody.create(json, JSON);
Request request = new Request.Builder()
.url(API_URL)
.header("Authorization", "Bearer " + BEARER_TOKEN)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
String responseBody = response.body().string();
if (response.isSuccessful()) {
System.out.println(" Account created successfully");
System.out.println("Response: " + responseBody);
} else if (response.code() == 401) {
System.out.println(" Unauthorized. Check your Bearer token.");
} else if (response.code() == 429) {
System.out.println(" Rate limit exceeded. Please wait before retrying.");
} else {
System.out.println(" Account creation failed: HTTP " + response.code());
System.out.println("Details: " + responseBody);
}
}
}
public static void main(String[] args) {
try {
createAccount();
} catch (IOException e) {
System.err.println(" Error: " + e.getMessage());
e.printStackTrace();
}
}
}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
)
const apiURL = "https://kyc.sbx.moduluslabs.io/v2/onboard/signup"
// createAccount creates a new user account
func createAccount() error {
bearerToken := os.Getenv("BEARER_TOKEN") // Your JWT Bearer token
accountData := map[string]interface{}{
"contact": map[string]string{
"email": "john.doe@example.com",
"phone": "09171234567",
},
"user": map[string]string{
"firstName": "John",
"middleName": "Michael",
"lastName": "Doe",
"username": "john.doe",
"password": "SecurePass123!",
"pin": "1234",
},
}
jsonData, err := json.Marshal(accountData)
if err != nil {
return fmt.Errorf("failed to marshal data: %w", err)
}
req, err := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+bearerToken)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response: %w", err)
}
if resp.StatusCode == http.StatusOK {
var result map[string]interface{}
if err := json.Unmarshal(body, &result); err != nil {
return fmt.Errorf("failed to parse response: %w", err)
}
fmt.Println(" Account created successfully")
fmt.Printf("Account ID: %.0f\n", result["id"])
return nil
} else if resp.StatusCode == http.StatusUnauthorized {
fmt.Println(" Unauthorized. Check your Bearer token.")
return fmt.Errorf("unauthorized")
} else if resp.StatusCode == http.StatusTooManyRequests {
fmt.Println(" Rate limit exceeded. Please wait before retrying.")
return fmt.Errorf("rate limit exceeded")
}
fmt.Printf(" Account creation failed: HTTP %d\n", resp.StatusCode)
fmt.Printf("Details: %s\n", string(body))
return fmt.Errorf("account creation failed with status %d", resp.StatusCode)
}
func main() {
if err := createAccount(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
await CreateAccount();
}
static async Task CreateAccount()
{
// Get Bearer token from environment variable
var bearerToken = Environment.GetEnvironmentVariable("BEARER_TOKEN");
if (string.IsNullOrEmpty(bearerToken))
{
Console.WriteLine(" Error: BEARER_TOKEN environment variable not set");
return;
}
try
{
using var client = new HttpClient();
// Set Bearer token authorization
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", bearerToken);
// Prepare account data
var accountData = new
{
contact = new
{
email = "john.doe@example.com",
phone = "09171234567"
},
user = new
{
firstName = "John",
middleName = "Michael",
lastName = "Doe",
username = "john.doe",
password = "SecurePass123!",
pin = "1234"
}
};
var content = new StringContent(
JsonSerializer.Serialize(accountData),
Encoding.UTF8,
"application/json"
);
// Send POST request
var response = await client.PostAsync(
"https://kyc.sbx.moduluslabs.io/v2/onboard/signup",
content
);
var responseBody = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
var result = JsonSerializer.Deserialize<CreateAccountResponse>(
responseBody,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
);
Console.WriteLine(" Account created successfully");
Console.WriteLine($"Account ID: {result.Id}");
}
else if ((int)response.StatusCode == 401)
{
Console.WriteLine(" Unauthorized. Check your Bearer token.");
}
else if ((int)response.StatusCode == 429)
{
Console.WriteLine(" Rate limit exceeded. Please wait before retrying.");
}
else
{
Console.WriteLine($" Account creation failed: HTTP {(int)response.StatusCode}");
Console.WriteLine($"Details: {responseBody}");
}
}
catch (HttpRequestException e)
{
Console.WriteLine($" Request failed: {e.Message}");
}
catch (Exception e)
{
Console.WriteLine($" Error: {e.Message}");
}
}
}
// Response model
public class CreateAccountResponse
{
public int Id { get; set; }
}
{
"id": 12345
}
{
"statusCode": 400,
"message": "user.password must have at least 1 uppercase and lowercase character, a number, and special character.",
"error": "Bad Request"
}
{
"statusCode": 401,
"message": "Unauthorized",
"error": "Unauthorized"
}
{
"statusCode": 429,
"message": "ThrottlerException: Too Many Requests",
"error": "Too Many Requests"
}
{
"statusCode": 500,
"message": "Internal server error",
"error": "Internal Server Error"
}
Overview
Creates a new user account with associated contact information and credentials. This is the first step in the merchant onboarding process.Account approval required: New accounts are initially registered under Packworks Merchant parent and become visible in the backoffice only after admin approval. Upon approval, a new merchant branch is created and assigned to the account.
Password security: Passwords expire after 90 days from creation. The password is hashed using bcrypt, and the PIN is encrypted using Rijndael encryption.
Rate Limiting
This endpoint is rate limited to 10 requests per 60 seconds per client.
Authentication
This endpoint requires JWT Bearer Token authentication.Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Request Parameters
Contact Information
object
required
User Information
object
required
User’s personal information and credentials
Show User Properties
Show User Properties
string
required
User’s first nameLength: 1-100 charactersPattern: Unicode letters (e.g., é, ñ), spaces, hyphens, and apostrophes onlyExample:
"John"string
User’s middle nameLength: 1-100 charactersPattern: Unicode letters (e.g., é, ñ), spaces, hyphens, and apostrophes onlyExample:
"Michael"string
required
User’s last nameLength: 1-100 charactersPattern: Unicode letters (e.g., é, ñ), spaces, hyphens, and apostrophes onlyExample:
"Doe"string
required
Unique username for the accountLength: 1-50 charactersPattern: Letters, numbers, dots (.), underscores (_), and hyphens (-) onlyExample:
"john.doe"Username must be unique across the system. If the username is already taken, the request will fail with a 400 error.
string
required
User’s passwordLength: 12-64 charactersRequirements:
- At least 1 uppercase letter
- At least 1 lowercase letter
- At least 1 number
- At least 1 special character from:
#?!@$%^&*-
"SecurePass123!"Passwords expire after 90 days and must be changed.
string
required
4-digit numeric PIN for additional securityLength: Exactly 4 digitsPattern: Digits onlyExample:
"1234"Response
Success Response
Status Code:200 OK
integer
The unique account ID of the newly created accountUsage: Use this ID for subsequent onboarding steps (JWT token generation, merchant onboarding)Example:
12345{
"id": 12345
}
{
"id": 12345
}
{
"statusCode": 400,
"message": "user.password must have at least 1 uppercase and lowercase character, a number, and special character.",
"error": "Bad Request"
}
{
"statusCode": 401,
"message": "Unauthorized",
"error": "Unauthorized"
}
{
"statusCode": 429,
"message": "ThrottlerException: Too Many Requests",
"error": "Too Many Requests"
}
{
"statusCode": 500,
"message": "Internal server error",
"error": "Internal Server Error"
}
Error Responses
401 Unauthorized
401 Unauthorized
Status Code: Cause: Missing or invalid JWT Bearer tokenSolution:
401{
"statusCode": 401,
"message": "Unauthorized",
"error": "Unauthorized"
}
- Ensure you’re sending the Authorization header with a valid JWT token
- Verify the JWT token is generated correctly with the proper secret key
- Check that the token hasn’t expired
400 Bad Request - Validation Error
400 Bad Request - Validation Error
Status Code: Solution: Ensure all parameters meet the validation requirements listed above
400Cause: Request parameters don’t meet validation rulesExamples:Invalid Password
{
"statusCode": 400,
"message": "user.password must have at least 1 uppercase and lowercase character, a number, and special character.",
"error": "Bad Request"
}
Invalid Email
{
"statusCode": 400,
"message": "contact.email is invalid.",
"error": "Bad Request"
}
Invalid Phone
{
"statusCode": 400,
"message": "contact.phone is invalid.",
"error": "Bad Request"
}
400 Bad Request - Username Not Available
400 Bad Request - Username Not Available
Status Code: Cause: The requested username is already taken by another userSolution: Choose a different username
400{
"statusCode": 400,
"message": "Username is not available",
"error": "Bad Request"
}
400 Bad Request - Database Error
400 Bad Request - Database Error
Status Code: Cause: Failed to save the account to the databaseSolution: Retry the request. If the issue persists, contact support
400{
"statusCode": 400,
"message": "Failed to save account: [error details]",
"error": "Bad Request"
}
429 Too Many Requests
429 Too Many Requests
Status Code: Cause: Rate limit exceeded (more than 10 requests in 60 seconds)Solution: Wait before retrying. Implement exponential backoff in your application
429{
"statusCode": 429,
"message": "ThrottlerException: Too Many Requests",
"error": "Too Many Requests"
}
500 Internal Server Error
500 Internal Server Error
Status Code: Cause: Unexpected server errorSolution: Retry the request. If the issue persists, contact support
500{
"statusCode": 500,
"message": "Internal server error",
"error": "Internal Server Error"
}
# Set your Bearer token
TOKEN="your_jwt_bearer_token_here"
curl -X POST https://kyc.sbx.moduluslabs.io/v2/onboard/signup \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"contact": {
"email": "john.doe@example.com",
"phone": "09171234567"
},
"user": {
"firstName": "John",
"middleName": "Michael",
"lastName": "Doe",
"username": "john.doe",
"password": "SecurePass123!",
"pin": "1234"
}
}'
const axios = require('axios');
const BEARER_TOKEN = process.env.BEARER_TOKEN; // Your JWT Bearer token
async function createAccount() {
const accountData = {
contact: {
email: 'john.doe@example.com',
phone: '09171234567'
},
user: {
firstName: 'John',
middleName: 'Michael',
lastName: 'Doe',
username: 'john.doe',
password: 'SecurePass123!',
pin: '1234'
}
};
try {
const response = await axios.post(
'https://kyc.sbx.moduluslabs.io/v2/onboard/signup',
accountData,
{
headers: {
'Authorization': `Bearer ${BEARER_TOKEN}`,
'Content-Type': 'application/json'
}
}
);
console.log(' Account created successfully');
console.log('Account ID:', response.data.id);
return response.data;
} catch (error) {
if (error.response?.status === 401) {
console.error(' Unauthorized. Check your Bearer token.');
} else if (error.response?.status === 429) {
console.error(' Rate limit exceeded. Please wait before retrying.');
} else {
console.error(' Account creation failed:', error.response?.data || error.message);
}
throw error;
}
}
createAccount();
import os
import requests
BEARER_TOKEN = os.getenv('BEARER_TOKEN') # Your JWT Bearer token
def create_account():
"""Create a new user account"""
account_data = {
'contact': {
'email': 'john.doe@example.com',
'phone': '09171234567'
},
'user': {
'firstName': 'John',
'middleName': 'Michael',
'lastName': 'Doe',
'username': 'john.doe',
'password': 'SecurePass123!',
'pin': '1234'
}
}
try:
response = requests.post(
'https://kyc.sbx.moduluslabs.io/v2/onboard/signup',
json=account_data,
headers={
'Authorization': f'Bearer {BEARER_TOKEN}',
'Content-Type': 'application/json'
}
)
response.raise_for_status()
print(' Account created successfully')
print(f"Account ID: {response.json()['id']}")
return response.json()
except requests.exceptions.HTTPError as e:
if e.response.status_code == 401:
print(' Unauthorized. Check your Bearer token.')
elif e.response.status_code == 429:
print(' Rate limit exceeded. Please wait before retrying.')
else:
print(f' Account creation failed: {e.response.text}')
raise
if __name__ == '__main__':
create_account()
<?php
require 'vendor/autoload.php'; // composer require guzzlehttp/guzzle
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
$bearerToken = getenv('BEARER_TOKEN'); // Your JWT Bearer token
/**
* Create a new user account
*/
function createAccount() {
global $bearerToken;
$accountData = [
'contact' => [
'email' => 'john.doe@example.com',
'phone' => '09171234567'
],
'user' => [
'firstName' => 'John',
'middleName' => 'Michael',
'lastName' => 'Doe',
'username' => 'john.doe',
'password' => 'SecurePass123!',
'pin' => '1234'
]
];
try {
$client = new Client();
$response = $client->post('https://kyc.sbx.moduluslabs.io/v2/onboard/signup', [
'headers' => [
'Authorization' => 'Bearer ' . $bearerToken,
'Content-Type' => 'application/json'
],
'json' => $accountData
]);
$body = json_decode($response->getBody(), true);
echo " Account created successfully\n";
echo "Account ID: " . $body['id'] . "\n";
return $body;
} catch (RequestException $e) {
if ($e->hasResponse()) {
$statusCode = $e->getResponse()->getStatusCode();
if ($statusCode === 401) {
echo " Unauthorized. Check your Bearer token.\n";
} elseif ($statusCode === 429) {
echo " Rate limit exceeded. Please wait before retrying.\n";
} else {
echo " Account creation failed: " . $e->getMessage() . "\n";
echo $e->getResponse()->getBody() . "\n";
}
} else {
echo " Account creation failed: " . $e->getMessage() . "\n";
}
throw $e;
}
}
createAccount();
?>
import com.google.gson.Gson;
import okhttp3.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class CreateAccount {
private static final String BEARER_TOKEN = System.getenv("BEARER_TOKEN"); // Your JWT Bearer token
private static final String API_URL = "https://kyc.sbx.moduluslabs.io/v2/onboard/signup";
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
/**
* Create a new user account
*/
public static void createAccount() throws IOException {
// Create account data
Map<String, Object> accountData = new HashMap<>();
Map<String, String> contact = new HashMap<>();
contact.put("email", "john.doe@example.com");
contact.put("phone", "09171234567");
accountData.put("contact", contact);
Map<String, String> user = new HashMap<>();
user.put("firstName", "John");
user.put("middleName", "Michael");
user.put("lastName", "Doe");
user.put("username", "john.doe");
user.put("password", "SecurePass123!");
user.put("pin", "1234");
accountData.put("user", user);
// Send request
Gson gson = new Gson();
String json = gson.toJson(accountData);
OkHttpClient client = new OkHttpClient();
RequestBody body = RequestBody.create(json, JSON);
Request request = new Request.Builder()
.url(API_URL)
.header("Authorization", "Bearer " + BEARER_TOKEN)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
String responseBody = response.body().string();
if (response.isSuccessful()) {
System.out.println(" Account created successfully");
System.out.println("Response: " + responseBody);
} else if (response.code() == 401) {
System.out.println(" Unauthorized. Check your Bearer token.");
} else if (response.code() == 429) {
System.out.println(" Rate limit exceeded. Please wait before retrying.");
} else {
System.out.println(" Account creation failed: HTTP " + response.code());
System.out.println("Details: " + responseBody);
}
}
}
public static void main(String[] args) {
try {
createAccount();
} catch (IOException e) {
System.err.println(" Error: " + e.getMessage());
e.printStackTrace();
}
}
}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
)
const apiURL = "https://kyc.sbx.moduluslabs.io/v2/onboard/signup"
// createAccount creates a new user account
func createAccount() error {
bearerToken := os.Getenv("BEARER_TOKEN") // Your JWT Bearer token
accountData := map[string]interface{}{
"contact": map[string]string{
"email": "john.doe@example.com",
"phone": "09171234567",
},
"user": map[string]string{
"firstName": "John",
"middleName": "Michael",
"lastName": "Doe",
"username": "john.doe",
"password": "SecurePass123!",
"pin": "1234",
},
}
jsonData, err := json.Marshal(accountData)
if err != nil {
return fmt.Errorf("failed to marshal data: %w", err)
}
req, err := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+bearerToken)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response: %w", err)
}
if resp.StatusCode == http.StatusOK {
var result map[string]interface{}
if err := json.Unmarshal(body, &result); err != nil {
return fmt.Errorf("failed to parse response: %w", err)
}
fmt.Println(" Account created successfully")
fmt.Printf("Account ID: %.0f\n", result["id"])
return nil
} else if resp.StatusCode == http.StatusUnauthorized {
fmt.Println(" Unauthorized. Check your Bearer token.")
return fmt.Errorf("unauthorized")
} else if resp.StatusCode == http.StatusTooManyRequests {
fmt.Println(" Rate limit exceeded. Please wait before retrying.")
return fmt.Errorf("rate limit exceeded")
}
fmt.Printf(" Account creation failed: HTTP %d\n", resp.StatusCode)
fmt.Printf("Details: %s\n", string(body))
return fmt.Errorf("account creation failed with status %d", resp.StatusCode)
}
func main() {
if err := createAccount(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
await CreateAccount();
}
static async Task CreateAccount()
{
// Get Bearer token from environment variable
var bearerToken = Environment.GetEnvironmentVariable("BEARER_TOKEN");
if (string.IsNullOrEmpty(bearerToken))
{
Console.WriteLine(" Error: BEARER_TOKEN environment variable not set");
return;
}
try
{
using var client = new HttpClient();
// Set Bearer token authorization
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", bearerToken);
// Prepare account data
var accountData = new
{
contact = new
{
email = "john.doe@example.com",
phone = "09171234567"
},
user = new
{
firstName = "John",
middleName = "Michael",
lastName = "Doe",
username = "john.doe",
password = "SecurePass123!",
pin = "1234"
}
};
var content = new StringContent(
JsonSerializer.Serialize(accountData),
Encoding.UTF8,
"application/json"
);
// Send POST request
var response = await client.PostAsync(
"https://kyc.sbx.moduluslabs.io/v2/onboard/signup",
content
);
var responseBody = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
var result = JsonSerializer.Deserialize<CreateAccountResponse>(
responseBody,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
);
Console.WriteLine(" Account created successfully");
Console.WriteLine($"Account ID: {result.Id}");
}
else if ((int)response.StatusCode == 401)
{
Console.WriteLine(" Unauthorized. Check your Bearer token.");
}
else if ((int)response.StatusCode == 429)
{
Console.WriteLine(" Rate limit exceeded. Please wait before retrying.");
}
else
{
Console.WriteLine($" Account creation failed: HTTP {(int)response.StatusCode}");
Console.WriteLine($"Details: {responseBody}");
}
}
catch (HttpRequestException e)
{
Console.WriteLine($" Request failed: {e.Message}");
}
catch (Exception e)
{
Console.WriteLine($" Error: {e.Message}");
}
}
}
// Response model
public class CreateAccountResponse
{
public int Id { get; set; }
}
Validation Requirements
1
Email Validation
✓ Valid email format
✓ Contains @ symbol
✓ Valid domain
2
Phone Validation
✓ 1-30 digits
✓ Numbers only (no spaces, dashes, or special characters)
3
Name Validation
✓ 1-100 characters
✓ Unicode letters, spaces, hyphens, and apostrophes only
✓ No numbers or special characters (except hyphens and apostrophes)
4
Username Validation
✓ 1-50 characters
✓ Letters, numbers, dots, underscores, and hyphens only
✓ Must be unique across the system
5
Password Validation
✓ 12-64 characters
✓ At least 1 uppercase letter
✓ At least 1 lowercase letter
✓ At least 1 number
✓ At least 1 special character from: #?!@$%^&*-
6
PIN Validation
✓ Exactly 4 digits
✓ Numbers only
Best Practices
Client-Side Validation
Validate all fields before submitting to avoid 400 errors
Handle Rate Limiting
Implement retry logic with exponential backoff for 429 errors
Secure Password Handling
Never log or store passwords in plain text
Save Account ID
Store the returned account ID for subsequent onboarding steps
Username Availability
Check username uniqueness before submission if possible
Error Feedback
Provide clear error messages to users when validation fails
Account Lifecycle
1
Create Account
Call this endpoint to create a new user account
2
Pending Approval
Account is registered under Packworks Merchant parent and awaits admin approval
3
Admin Review
Modulus Labs admin reviews and approves the account
4
Account Activation
Upon approval, a new merchant branch is created and assigned to the account
5
Begin Onboarding
Use the account ID to generate JWT token and proceed with merchant onboarding
Security Notes
Password Security
Password Security
- Passwords are hashed using bcrypt before storage
- Passwords expire after 90 days and must be changed
- Never send passwords over unencrypted connections
- Implement strong password requirements on the client side
PIN Security
PIN Security
- PINs are encrypted using Rijndael encryption
- PINs provide additional security for sensitive operations
- Consider implementing PIN rate limiting in your application
Rate Limiting
Rate Limiting
- Maximum 10 requests per 60 seconds per client
- Implement exponential backoff when encountering 429 errors
- Consider caching account creation attempts to prevent abuse
Troubleshooting
Username Already Taken
Username Already Taken
Error:
Username is not availableSolution:- Try a different username
- Add numbers or special characters (dots, underscores, hyphens)
- Consider using email prefix or unique identifiers
Password Validation Failed
Password Validation Failed
Error:
user.password must have at least 1 uppercase and lowercase character, a number, and special characterSolution:- Ensure password is 12-64 characters long
- Include at least one uppercase letter (A-Z)
- Include at least one lowercase letter (a-z)
- Include at least one number (0-9)
- Include at least one special character from: #?!@$%^&*-
SecurePass123!Rate Limit Exceeded
Rate Limit Exceeded
Error:
ThrottlerException: Too Many RequestsSolution:- Wait 60 seconds before retrying
- Implement exponential backoff
- Don’t retry immediately on 429 errors
- Consider queueing account creation requests
Next Steps
Generate JWT Token
Create a JWT Bearer Token for authentication
Onboard Merchant
Complete merchant onboarding with the account ID
Upload Documents
Upload required KYC documents
Error Handling
Learn about error codes and handling
Authorizations
JWT Bearer token authentication
Body
application/json
Was this page helpful?
⌘I