Sales Order API (2.0)

Download OpenAPI specification:

Secure REST API for authorized third-party applications to create and manage sales orders. Integrations are authenticated via JWT bearer tokens and RSA request signatures.

Getting Started

Overview

The Sales Order API provides a secure integration interface for authorized third-party applications to create and manage sales orders programmatically.

This API is intended for external partner systems that need to submit sales orders into our platform. All requests are authenticated and validated to ensure data integrity and secure communication.

Integration Overview

The API acts as a secure gateway between external partner applications and our order management system. Partner credentials are validated on every request, and payload integrity is enforced through JWT authentication and RSA signature verification.

Quick Start

Follow these steps to begin integration:

  1. Obtain credentials — Request a client_id and client_secret from our integration team
  2. Register your public key — Generate an RSA key pair and submit your public key in X.509 PEM format
  3. Authenticate — Call /api/v1/auth/login to obtain a JWT access token
  4. Submit orders — Send sales order requests to /api/v1/sales-orders or /api/v2/sales-orders with the required authentication headers
  5. Test with Postman — Use our Postman documentation and run the E2E Flow folder for end-to-end testing

Authentication

Authentication Overview

This API uses a two-layer authentication model to protect all integration endpoints:

  1. JWT bearer token — Grants API access for the authenticated partner session
  2. RSA request signature — Validates request authenticity via the X-SIGNATURE header

Step 1: Obtain a JWT Access Token

Before calling protected endpoints, obtain a JWT access token by sending your client_id and client_secret to /api/v1/auth/login.

Request Example:

POST /api/v1/auth/login
Content-Type: application/json

{
  "client_id": "your-client-id",
  "client_secret": "your-client-secret"
}

Response:

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expired_at": 1704067200
}

Access tokens are valid for 12 hours. When a token expires, renew it using /api/v1/auth/refresh without re-submitting your client credentials.

Step 2: Generate the X-SIGNATURE Header

Sales order requests require an X-SIGNATURE header in addition to the JWT bearer token. The signature confirms that the request originated from the registered partner and has not been tampered with.

Signature Algorithm

X-SIGNATURE = SHA256withRSA(PrivateKey, StringToSign)
StringToSign = client_id + "|" + X-TIMESTAMP

Key Requirements

  • Private key — Your RSA private key; store securely and never share it
  • Public key — Submit your public key in X.509 PEM format to our integration team for registration

Public Key Format (X.509 PEM)

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAomV+Vm1xlRXanmh108Ku
sls7SSKec0oCejtc9QGObpd4RnQ+7gihm2k6etnSNP7b+XrpY+fBkiQNaBInii9M
10kW9Bhf/M9GH/edL3IqnzDNSi7tcoQgnO7h8xmzLNWHTjtR6bkrsdBS5dry6hto
taF5KXomuoYgztCdGDOa0W20aeLzYSXIoW7s/Ay5yIXt0xaXTll3/bmezleguFPn
wQZq5EqZFWlUZvutDi+f2l9rTRY0Fb64y+VAf+mnIbEovGqsPEeF/p97YWxcY7CW
m8NsT0lwBVOtkmEl967Brz5yvEObF5bJgVodi6mNVsN1ki0MCitIhYO8shcE7eUi
lQIDAQAB
-----END PUBLIC KEY-----

Step 3: Send Authenticated Requests

Include the following headers when creating a sales order:

Required headers

Authorization: Bearer <JWT_TOKEN>
X-CLIENT-ID: <your-client-id>
X-TIMESTAMP: <current-unix-timestamp>
X-SIGNATURE: <generated-signature>
Content-Type: application/json

Validation Rules

  • The client_id embedded in the JWT must match the X-CLIENT-ID header value
  • The X-SIGNATURE is verified against your registered public key
  • The X-TIMESTAMP must match the timestamp used when generating the signature

Step 4: Refresh an Expired Token

To renew an expired access token without re-authenticating:

Request Example:

POST /api/v1/auth/refresh
Content-Type: application/json

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Response:

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expired_at": 1704110400
}

Security Best Practices

  • Never expose your client_secret or RSA private key in source code or logs
  • Store credentials in environment variables or a dedicated secrets management service
  • Rotate API credentials and signing keys on a regular schedule
  • Monitor API activity and report any unauthorized access promptly

Generate JWT token with client credentials

Request Body schema: application/json
required
client_id
required
string

Client ID

client_secret
required
string

Client Secret

Responses

Request samples

Content type
application/json
{
  • "client_id": "partner-client-001",
  • "client_secret": "secret-key-123"
}

Response samples

Content type
application/json
{
  • "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  • "expired_at": 1704067200
}

Refresh JWT token

Request Body schema: application/json
required
token
required
string

JWT Token

Responses

Request samples

Content type
application/json
{
  • "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Response samples

Content type
application/json
{
  • "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  • "expired_at": 1704067200
}

Testing

Postman Collection

We provide a Postman collection for integration testing. View the interactive documentation at:

PMPS Sales Order API — Postman Documentation

End-to-End Testing

Use the E2E Flow folder in the Postman collection to run a full integration test:

  1. Login — Obtain a JWT access token
  2. Generate Signature — Generate X-SIGNATURE and X-TIMESTAMP headers (testing only)
  3. Create Sales Order V1 or Create Sales Order V2 — Submit a sales order with authentication headers

Generate Signature (Testing Only)

The Generate Signature request in Postman calls POST /api/v1/auth/signature/generate, a helper endpoint for development and testing environments.

Important: This endpoint is not available in production. When NODE_ENV=production, the API returns 404 Not Found. In production, you must generate X-SIGNATURE locally using your RSA private key and the algorithm described in the Authentication section.

Generate X-SIGNATURE (testing only)

Generates an RSA-SHA256 signature for API requests. Intended for development and testing only.

This endpoint is not served in production — it returns 404 Not Found when NODE_ENV=production. In production, generate signatures locally using your RSA private key.

Use the Generate Signature request in the Postman E2E Flow folder, or see the Postman documentation.

Request Body schema: application/json
required
client_id
required
string

Client ID

private_key
required
string

RSA private key in PEM format

timestamp
string

Unix timestamp in milliseconds (defaults to current time)

Responses

Request samples

Content type
application/json
{
  • "client_id": "versuni-client-001",
  • "private_key": "-----BEGIN PRIVATE KEY-----\\n...\\n-----END PRIVATE KEY-----",
  • "timestamp": "1704067200000"
}

Response samples

Content type
application/json
{
  • "signature": "abc123...",
  • "timestamp": "1704067200000",
  • "string_to_sign": "versuni-client-001|1704067200000"
}

Sales Orders

Endpoints for creating and managing sales orders submitted by authorized partner applications.

Product splitting

A single request may include more than 15 products. The API automatically splits the product list into multiple sales orders, with a maximum of 15 products per sales order. Each resulting order receives its own unique sales order code.

For example, a request with 34 products creates 3 sales orders containing 15, 15, and 4 products respectively.

On v1, the response shape is unchanged: data.code contains a comma-separated list of all created codes (e.g. SOP12500001,SOP12500002,SOP12500003). data.status and data.created_at reflect the first created order.

For a structured list of all created orders, use the v2 endpoint instead.

Create a new sales order

Creates one or more sales orders from the submitted product list. When the request contains more than 15 products, the API splits them into multiple sales orders (15 products each, with any remainder in the final order). Each order receives a unique code.

The fields customer_code, site_code, sales_name, and address are optional. If a field is omitted or sent with a null value, the API uses the partner's fallback value configured by an admin in the database.

The response always uses the same shape. When multiple orders are created, data.code is a comma-separated string of all generated codes. data.status and data.created_at are taken from the first created order.

Authorizations:
X-CLIENT-IDX-TIMESTAMPX-SIGNATUREJWT-auth
Request Body schema: application/json
required
customer_code
string or null

Customer code. Optional; when omitted or null, the partner fallback value set by an admin in the database is used.

site_code
string or null

Site code. Optional; when omitted or null, the partner fallback value set by an admin in the database is used.

sales_name
string or null

Sales person name. Optional; when omitted or null, the partner fallback value set by an admin in the database is used.

address
string or null

Delivery address. Optional; when omitted or null, the partner fallback value set by an admin in the database is used.

purchase_order_no
string or null

Purchase order number from the external partner system

required
Array of objects (ProductDto) non-empty

List of products. Must contain at least one item. There is no maximum array size; when more than 15 products are submitted, the API automatically creates multiple sales orders with up to 15 products each.

Responses

Request samples

Content type
application/json
{
  • "customer_code": "CUST-001",
  • "site_code": "SITE-001",
  • "sales_name": "John Doe",
  • "address": "Jl. Sudirman No. 1, Jakarta",
  • "purchase_order_no": "PO-2025-00001",
  • "products": [
    ]
}

Response samples

Content type
application/json
{
  • "message": "Sales order created successfully",
  • "data": {
    }
}

Sales Orders V2

Version 2 sales order endpoints. Use v2 when you need the full list of created sales orders returned as an array — for example, when a large product list is split into multiple orders.

Create a new sales order

Creates one or more sales orders from the submitted product list. When the request contains more than 15 products, the API splits them into multiple sales orders (15 products each, with any remainder in the final order). Each order receives a unique code.

The fields customer_code, site_code, sales_name, and address are optional. If a field is omitted or sent with a null value, the API uses the partner's fallback value configured by an admin in the database.

Unlike v1, the response returns every created order in data.orders as an array of objects, each with its own code, status, and created_at.

Authorizations:
X-CLIENT-IDX-TIMESTAMPX-SIGNATUREJWT-auth
Request Body schema: application/json
required
customer_code
string or null

Customer code. Optional; when omitted or null, the partner fallback value set by an admin in the database is used.

site_code
string or null

Site code. Optional; when omitted or null, the partner fallback value set by an admin in the database is used.

sales_name
string or null

Sales person name. Optional; when omitted or null, the partner fallback value set by an admin in the database is used.

address
string or null

Delivery address. Optional; when omitted or null, the partner fallback value set by an admin in the database is used.

purchase_order_no
string or null

Purchase order number from the external partner system

required
Array of objects (ProductDto) non-empty

List of products. Must contain at least one item. There is no maximum array size; when more than 15 products are submitted, the API automatically creates multiple sales orders with up to 15 products each.

Responses

Request samples

Content type
application/json
{
  • "customer_code": "CUST-001",
  • "site_code": "SITE-001",
  • "sales_name": "John Doe",
  • "address": "Jl. Sudirman No. 1, Jakarta",
  • "purchase_order_no": "PO-2025-00001",
  • "products": [
    ]
}

Response samples

Content type
application/json
{
  • "message": "Sales order created successfully",
  • "data": {
    }
}