> ## Documentation Index
> Fetch the complete documentation index at: https://docs.moduluslabs.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Create Account

> Create a new user account with contact information and credentials

## Overview

Creates a new user account with associated contact information and credentials. This is the first step in the merchant onboarding process.

<Note>
  **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.
</Note>

<Warning>
  **Password security:** Passwords expire after 90 days from creation. The password is hashed using bcrypt, and the PIN is encrypted using Rijndael encryption.
</Warning>

## Rate Limiting

<Info>
  This endpoint is rate limited to **10 requests per 60 seconds** per client.
</Info>

## Authentication

This endpoint requires JWT Bearer Token authentication.

```bash theme={null}
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```

See the [Authentication Guide](/docs/onboarding/authentication) for details on generating JWT tokens.

## Request Parameters

### Contact Information

<ParamField body="contact" type="object" required>
  User's contact information

  <Expandable title="Contact Properties">
    <ParamField body="email" type="string" required>
      User's email address

      **Format:** Valid email format

      **Example:** `"john.doe@example.com"`
    </ParamField>

    <ParamField body="phone" type="string" required>
      User's phone number

      **Length:** 1-30 characters

      **Format:** Digits only (no spaces, dashes, or special characters)

      **Example:** `"09171234567"`
    </ParamField>
  </Expandable>
</ParamField>

### User Information

<ParamField body="user" type="object" required>
  User's personal information and credentials

  <Expandable title="User Properties">
    <ParamField body="firstName" type="string" required>
      User's first name

      **Length:** 1-100 characters

      **Pattern:** Unicode letters (e.g., é, ñ), spaces, hyphens, and apostrophes only

      **Example:** `"John"`
    </ParamField>

    <ParamField body="middleName" type="string" optional>
      User's middle name

      **Length:** 1-100 characters

      **Pattern:** Unicode letters (e.g., é, ñ), spaces, hyphens, and apostrophes only

      **Example:** `"Michael"`
    </ParamField>

    <ParamField body="lastName" type="string" required>
      User's last name

      **Length:** 1-100 characters

      **Pattern:** Unicode letters (e.g., é, ñ), spaces, hyphens, and apostrophes only

      **Example:** `"Doe"`
    </ParamField>

    <ParamField body="username" type="string" required>
      Unique username for the account

      **Length:** 1-50 characters

      **Pattern:** Letters, numbers, dots (.), underscores (\_), and hyphens (-) only

      **Example:** `"john.doe"`

      <Warning>
        Username must be unique across the system. If the username is already taken, the request will fail with a 400 error.
      </Warning>
    </ParamField>

    <ParamField body="password" type="string" required>
      User's password

      **Length:** 12-64 characters

      **Requirements:**

      * At least 1 uppercase letter
      * At least 1 lowercase letter
      * At least 1 number
      * At least 1 special character from: `#?!@$%^&*-`

      **Example:** `"SecurePass123!"`

      <Note>
        Passwords expire after 90 days and must be changed.
      </Note>
    </ParamField>

    <ParamField body="pin" type="string" required>
      4-digit numeric PIN for additional security

      **Length:** Exactly 4 digits

      **Pattern:** Digits only

      **Example:** `"1234"`
    </ParamField>
  </Expandable>
</ParamField>

## Response

### Success Response

**Status Code:** `200 OK`

<ResponseField name="id" type="integer">
  The unique account ID of the newly created account

  **Usage:** Use this ID for subsequent onboarding steps (JWT token generation, merchant onboarding)

  **Example:** `12345`
</ResponseField>

```json theme={null}
{
  "id": 12345
}
```

<ResponseExample>
  ```json 200 Success theme={null}
  {
    "id": 12345
  }
  ```

  ```json 400 Bad Request theme={null}
  {
    "statusCode": 400,
    "message": "user.password must have at least 1 uppercase and lowercase character, a number, and special character.",
    "error": "Bad Request"
  }
  ```

  ```json 401 Unauthorized theme={null}
  {
    "statusCode": 401,
    "message": "Unauthorized",
    "error": "Unauthorized"
  }
  ```

  ```json 429 Too Many Requests theme={null}
  {
    "statusCode": 429,
    "message": "ThrottlerException: Too Many Requests",
    "error": "Too Many Requests"
  }
  ```

  ```json 500 Internal Server Error theme={null}
  {
    "statusCode": 500,
    "message": "Internal server error",
    "error": "Internal Server Error"
  }
  ```
</ResponseExample>

### Error Responses

<AccordionGroup>
  <Accordion title="401 Unauthorized">
    **Status Code:** `401`

    ```json theme={null}
    {
      "statusCode": 401,
      "message": "Unauthorized",
      "error": "Unauthorized"
    }
    ```

    **Cause:** Missing or invalid JWT Bearer token

    **Solution:**

    * 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
  </Accordion>

  <Accordion title="400 Bad Request - Validation Error">
    **Status Code:** `400`

    **Cause:** Request parameters don't meet validation rules

    **Examples:**

    ```json Invalid Password theme={null}
    {
      "statusCode": 400,
      "message": "user.password must have at least 1 uppercase and lowercase character, a number, and special character.",
      "error": "Bad Request"
    }
    ```

    ```json Invalid Email theme={null}
    {
      "statusCode": 400,
      "message": "contact.email is invalid.",
      "error": "Bad Request"
    }
    ```

    ```json Invalid Phone theme={null}
    {
      "statusCode": 400,
      "message": "contact.phone is invalid.",
      "error": "Bad Request"
    }
    ```

    **Solution:** Ensure all parameters meet the validation requirements listed above
  </Accordion>

  <Accordion title="400 Bad Request - Username Not Available">
    **Status Code:** `400`

    ```json theme={null}
    {
      "statusCode": 400,
      "message": "Username is not available",
      "error": "Bad Request"
    }
    ```

    **Cause:** The requested username is already taken by another user

    **Solution:** Choose a different username
  </Accordion>

  <Accordion title="400 Bad Request - Database Error">
    **Status Code:** `400`

    ```json theme={null}
    {
      "statusCode": 400,
      "message": "Failed to save account: [error details]",
      "error": "Bad Request"
    }
    ```

    **Cause:** Failed to save the account to the database

    **Solution:** Retry the request. If the issue persists, contact support
  </Accordion>

  <Accordion title="429 Too Many Requests">
    **Status Code:** `429`

    ```json theme={null}
    {
      "statusCode": 429,
      "message": "ThrottlerException: Too Many Requests",
      "error": "Too Many Requests"
    }
    ```

    **Cause:** Rate limit exceeded (more than 10 requests in 60 seconds)

    **Solution:** Wait before retrying. Implement exponential backoff in your application
  </Accordion>

  <Accordion title="500 Internal Server Error">
    **Status Code:** `500`

    ```json theme={null}
    {
      "statusCode": 500,
      "message": "Internal server error",
      "error": "Internal Server Error"
    }
    ```

    **Cause:** Unexpected server error

    **Solution:** Retry the request. If the issue persists, contact support
  </Accordion>
</AccordionGroup>

<RequestExample>
  ```bash cURL theme={null}
  # 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"
      }
    }'
  ```

  ```javascript Node.js theme={null}
  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();
  ```

  ```python Python theme={null}
  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 PHP theme={null}
  <?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();
  ?>
  ```

  ```java Java theme={null}
  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();
          }
      }
  }
  ```

  ```go Go theme={null}
  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)
  	}
  }
  ```

  ```csharp C# / .NET theme={null}
  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; }
  }
  ```
</RequestExample>

## Validation Requirements

<Steps>
  <Step title="Email Validation">
    ✓ Valid email format
    ✓ Contains @ symbol
    ✓ Valid domain
  </Step>

  <Step title="Phone Validation">
    ✓ 1-30 digits
    ✓ Numbers only (no spaces, dashes, or special characters)
  </Step>

  <Step title="Name Validation">
    ✓ 1-100 characters
    ✓ Unicode letters, spaces, hyphens, and apostrophes only
    ✓ No numbers or special characters (except hyphens and apostrophes)
  </Step>

  <Step title="Username Validation">
    ✓ 1-50 characters
    ✓ Letters, numbers, dots, underscores, and hyphens only
    ✓ Must be unique across the system
  </Step>

  <Step title="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: #?!@\$%^&\*-
  </Step>

  <Step title="PIN Validation">
    ✓ Exactly 4 digits
    ✓ Numbers only
  </Step>
</Steps>

## Best Practices

<CardGroup cols={2}>
  <Card title="Client-Side Validation" icon="shield-check">
    Validate all fields before submitting to avoid 400 errors
  </Card>

  <Card title="Handle Rate Limiting" icon="gauge">
    Implement retry logic with exponential backoff for 429 errors
  </Card>

  <Card title="Secure Password Handling" icon="lock">
    Never log or store passwords in plain text
  </Card>

  <Card title="Save Account ID" icon="database">
    Store the returned account ID for subsequent onboarding steps
  </Card>

  <Card title="Username Availability" icon="user">
    Check username uniqueness before submission if possible
  </Card>

  <Card title="Error Feedback" icon="message">
    Provide clear error messages to users when validation fails
  </Card>
</CardGroup>

## Account Lifecycle

<Steps>
  <Step title="Create Account">
    Call this endpoint to create a new user account
  </Step>

  <Step title="Pending Approval">
    Account is registered under Packworks Merchant parent and awaits admin approval
  </Step>

  <Step title="Admin Review">
    Modulus Labs admin reviews and approves the account
  </Step>

  <Step title="Account Activation">
    Upon approval, a new merchant branch is created and assigned to the account
  </Step>

  <Step title="Begin Onboarding">
    Use the account ID to generate JWT token and proceed with merchant onboarding
  </Step>
</Steps>

## Security Notes

<AccordionGroup>
  <Accordion title="Password Security" icon="lock">
    * 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
  </Accordion>

  <Accordion title="PIN Security" icon="key">
    * PINs are encrypted using Rijndael encryption
    * PINs provide additional security for sensitive operations
    * Consider implementing PIN rate limiting in your application
  </Accordion>

  <Accordion title="Rate Limiting" icon="gauge">
    * Maximum 10 requests per 60 seconds per client
    * Implement exponential backoff when encountering 429 errors
    * Consider caching account creation attempts to prevent abuse
  </Accordion>
</AccordionGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Username Already Taken" icon="user">
    **Error:** `Username is not available`

    **Solution:**

    * Try a different username
    * Add numbers or special characters (dots, underscores, hyphens)
    * Consider using email prefix or unique identifiers
  </Accordion>

  <Accordion title="Password Validation Failed" icon="triangle-exclamation">
    **Error:** `user.password must have at least 1 uppercase and lowercase character, a number, and special character`

    **Solution:**

    * 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: #?!@\$%^&\*-

    **Example valid password:** `SecurePass123!`
  </Accordion>

  <Accordion title="Rate Limit Exceeded" icon="clock">
    **Error:** `ThrottlerException: Too Many Requests`

    **Solution:**

    * Wait 60 seconds before retrying
    * Implement exponential backoff
    * Don't retry immediately on 429 errors
    * Consider queueing account creation requests
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Generate JWT Token" icon="key" href="/docs/onboarding/authentication">
    Create a JWT Bearer Token for authentication
  </Card>

  <Card title="Onboard Merchant" icon="plus" href="/api-reference/onboarding/onboard-merchant">
    Complete merchant onboarding with the account ID
  </Card>

  <Card title="Upload Documents" icon="upload" href="/docs/onboarding/introduction">
    Upload required KYC documents
  </Card>

  <Card title="Error Handling" icon="triangle-exclamation" href="/docs/errors">
    Learn about error codes and handling
  </Card>
</CardGroup>


## OpenAPI

````yaml POST /v2/account
openapi: 3.1.0
info:
  title: Modulus Labs Onboarding API
  description: API for merchant onboarding, KYC verification, and account management
  version: 2.0.0
servers:
  - url: https://kyc.sbx.moduluslabs.io
    description: Sandbox
security: []
paths:
  /v2/account:
    post:
      tags:
        - Account
      summary: Create Account
      description: >-
        Create a new merchant account. This is the first step in the onboarding
        process before submitting business details.
      operationId: createAccount
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateAccountRequest'
            example:
              contact:
                email: john.doe@example.com
                phone: '9876543212'
              user:
                firstName: John
                middleName: Michael
                lastName: Doe
                username: johndoe123
                password: SecureP@ssw0rd!
                pin: '1234'
      responses:
        '201':
          description: Account created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateAccountResponse'
        '400':
          description: Bad Request - Validation error or email already exists
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized - Invalid API Key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearerAuth: []
components:
  schemas:
    CreateAccountRequest:
      type: object
      required:
        - contact
        - user
      properties:
        contact:
          type: object
          required:
            - email
            - phone
          description: Contact information for the account
          properties:
            email:
              type: string
              format: email
              maxLength: 100
              description: Email address for the account (must be unique)
            phone:
              type: string
              pattern: ^\d+$
              maxLength: 30
              description: Phone number (digits only)
        user:
          type: object
          required:
            - firstName
            - lastName
            - username
            - password
            - pin
          description: User credentials and personal information
          properties:
            firstName:
              type: string
              minLength: 1
              maxLength: 100
              description: First name of the account holder
            middleName:
              type: string
              maxLength: 100
              description: Middle name (optional)
            lastName:
              type: string
              minLength: 1
              maxLength: 100
              description: Last name of the account holder
            username:
              type: string
              minLength: 1
              maxLength: 100
              description: Unique username for the account
            password:
              type: string
              minLength: 8
              maxLength: 128
              description: >-
                Account password (min 8 characters, must contain uppercase,
                lowercase, number, and special character)
            pin:
              type: string
              pattern: ^\d{4}$
              description: 4-digit PIN for account security
    CreateAccountResponse:
      type: object
      properties:
        id:
          type: string
          description: Unique account identifier
        emailAddress:
          type: string
          description: Registered email address
        firstName:
          type: string
          description: First name
        lastName:
          type: string
          description: Last name
        accessToken:
          type: string
          description: JWT access token for authentication
        refreshToken:
          type: string
          description: JWT refresh token for obtaining new access tokens
    ErrorResponse:
      type: object
      properties:
        statusCode:
          type: integer
          description: HTTP status code
        message:
          type: string
          description: Error message
        error:
          type: string
          description: Error type
        details:
          type: object
          description: Additional error details
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT Bearer token authentication

````