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.
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.
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.
Follow these steps to begin integration:
client_id and client_secret from our integration team/api/v1/auth/login to obtain a JWT access token/api/v1/sales-orders or /api/v2/sales-orders with the required authentication headersThis API uses a two-layer authentication model to protect all integration endpoints:
X-SIGNATURE headerBefore 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.
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.
X-SIGNATURE = SHA256withRSA(PrivateKey, StringToSign)
StringToSign = client_id + "|" + X-TIMESTAMP
-----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-----
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
client_id embedded in the JWT must match the X-CLIENT-ID header valueX-SIGNATURE is verified against your registered public keyX-TIMESTAMP must match the timestamp used when generating the signatureTo 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
}
client_secret or RSA private key in source code or logs| client_id required | string Client ID |
| client_secret required | string Client Secret |
{- "client_id": "partner-client-001",
- "client_secret": "secret-key-123"
}{- "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
- "expired_at": 1704067200
}| token required | string JWT Token |
{- "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}{- "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
- "expired_at": 1704067200
}We provide a Postman collection for integration testing. View the interactive documentation at:
Use the E2E Flow folder in the Postman collection to run a full integration test:
X-SIGNATURE and X-TIMESTAMP headers (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 returns404 Not Found. In production, you must generateX-SIGNATURElocally using your RSA private key and the algorithm described in the Authentication section.
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.
| 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) |
{- "client_id": "versuni-client-001",
- "private_key": "-----BEGIN PRIVATE KEY-----\\n...\\n-----END PRIVATE KEY-----",
- "timestamp": "1704067200000"
}{- "signature": "abc123...",
- "timestamp": "1704067200000",
- "string_to_sign": "versuni-client-001|1704067200000"
}Endpoints for creating and managing sales orders submitted by authorized partner applications.
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.
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.
| 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. |
{- "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": [
- {
- "product_code": "PROD-001",
- "quantity": 10,
- "price": 100000
}
]
}{- "message": "Sales order created successfully",
- "data": {
- "code": "SOP12500001,SOP12500002,SOP12500003",
- "status": "draft",
- "created_at": "2025-01-01T00:00:00.000Z"
}
}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.
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.
| 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. |
{- "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": [
- {
- "product_code": "PROD-001",
- "quantity": 10,
- "price": 100000
}
]
}{- "message": "Sales order created successfully",
- "data": {
- "orders": [
- {
- "code": "SOP12500001",
- "status": "draft",
- "created_at": "2025-01-01T00:00:00.000Z"
}, - {
- "code": "SOP12500002",
- "status": "draft",
- "created_at": "2025-01-01T00:00:01.000Z"
}, - {
- "code": "SOP12500003",
- "status": "draft",
- "created_at": "2025-01-01T00:00:02.000Z"
}
]
}
}