Integration Guide
This guide covers the public API for integrating external systems with Vectis OMS.
Support: support@stoalogistics.com
Terms of Service: stoalogistics.com/terms
Authentication
All API requests require a Bearer token in the Authorization header.
Obtaining a Token
Request an access token from the OAuth endpoint:
curl -X POST https://api.vectisoms.app/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET"
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "orders:read orders:write inventory:read inventory:write products:read products:write"
}
Making Requests
curl https://acme.vectisoms.app/api/orders \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Organization owners create API Clients in the Vectis account portal (the Control Plane link in the app sidebar), under Organization Settings → Developer → API Clients. (The same tab also lists API Keys; for the Vectis API, use an API Client.) The secret is shown once. Contact support@stoalogistics.com if you need assistance.
API client tokens currently have read access to orders, products, and inventory, and can manage webhook subscriptions. To create or change orders, products, or inventory through the API, contact support@stoalogistics.com.
Ecommerce Integration
Push orders from your ecommerce platform to Vectis for fulfillment.
Check Inventory Before Selling
GET /api/inventory?sku=WIDGET-001
Authorization: Bearer YOUR_ACCESS_TOKEN
Response:
{
"items": [{
"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"sku": "WIDGET-001",
"quantityAvailable": 150,
"quantityReserved": 10,
"warehouseId": "3f2b8c1e-9d4a-4b6e-8f1a-2c5d7e9b0a14"
}]
}
Create an Order
POST /api/orders
Content-Type: application/json
Authorization: Bearer YOUR_ACCESS_TOKEN
{
"externalId": "SHOPIFY-12345",
"customerId": "cust_jane123",
"customerEmail": "jane@example.com",
"items": [
{ "sku": "WIDGET-001", "quantity": 2 }
],
"shippingAddresses": [{
"name": "Jane Smith",
"line1": "123 Main St",
"city": "Austin",
"region": "TX",
"postalCode": "78701",
"country": "US"
}],
"billingAddress": {
"name": "Jane Smith",
"line1": "123 Main St",
"city": "Austin",
"region": "TX",
"postalCode": "78701",
"country": "US"
}
}
Line prices come from your product catalog.
Response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "pending",
"total": 59.98,
"itemCount": 2,
"createdAt": "2026-01-26T12:00:00Z"
}
Get Order Status
GET /api/orders/{orderId}
Authorization: Bearer YOUR_ACCESS_TOKEN
Response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "shipped",
"total": 59.98,
"trackingNumber": "1Z999AA10123456784",
"carrier": "UPS",
"items": [...],
"createdAt": "2026-01-26T12:00:00Z"
}
Cancel an Order
Cancel an order before it ships:
POST /api/orders/{orderId}/cancel
Content-Type: application/json
Authorization: Bearer YOUR_ACCESS_TOKEN
{
"reason": "Customer requested cancellation"
}
Bulk order import
When you need to load many orders at once (cutover, replatforming, or steady high volume), use POST /api/orders/bulk-import with a JSON body: {"format": "json", "data": [orders]} or {"format": "csv", "data": "<csv text>"}, up to 500 orders per request. Same Bearer token as the rest of the API.
Use POST /api/orders when you create one order at a time and want safe retries with an Idempotency-Key header. Use bulk import when the work is batch-shaped and you control file or payload layout.
CSV needs a header row with customerId, customerEmail, sku, quantity, shipName, shipLine1, shipCity, shipRegion, shipPostalCode, shipCountry, and optional externalId, billLine1, billCity, billRegion, billPostalCode, and billCountry columns (each billing field defaults to its shipping value; the billing name is always shipName); each row is one order with one line item. Every row is checked for required fields and formats before any order is created. A row can still fail when its order is created (for example, an unknown SKU) while the other rows succeed. The response lists created, failed, and per-row errors. Questions: support@stoalogistics.com.
Product Catalog Sync
Keep your product catalog in sync with Vectis.
Create a Product
POST /api/products
Content-Type: application/json
Authorization: Bearer YOUR_ACCESS_TOKEN
{
"sku": "WIDGET-001",
"name": "Premium Widget",
"price": 29.99,
"weight": 1.5,
"weightUnit": "lb",
"dimensions": {
"length": 10, "width": 8, "height": 4, "unit": "in"
}
}
Get Product by SKU
GET /api/products/sku/WIDGET-001
Authorization: Bearer YOUR_ACCESS_TOKEN
Update a Product
PATCH /api/products/{productId}
Content-Type: application/json
Authorization: Bearer YOUR_ACCESS_TOKEN
{
"name": "Premium Widget v2",
"weight": 1.2,
"weightUnit": "lb"
}
Inventory Adjustments
Adjust inventory levels when stock changes outside of Vectis.
PATCH /api/inventory/{id}/adjust
Content-Type: application/json
Authorization: Bearer YOUR_ACCESS_TOKEN
{
"adjustment": -5,
"reason": "Damaged goods removed"
}
Webhook Subscriptions
Subscribe to real-time events for order and inventory changes.
Create a Webhook Subscription
POST /api/webhooks
Content-Type: application/json
Authorization: Bearer YOUR_ACCESS_TOKEN
{
"url": "https://your-system.com/webhooks/vectis",
"events": ["OrderShipped", "OrderDelivered", "InventoryAdjusted"],
"secret": "your-webhook-secret"
}
Available Events
| Event | Description |
|---|---|
OrderCreated | New order received |
OrderConfirmed | Order confirmed for fulfillment |
OrderShipped | Order has shipped (includes tracking) |
OrderDelivered | Order delivered to customer |
OrderCancelled | Order was cancelled |
InventoryAdjusted | Inventory level changed |
PackageCreated | Package created for order |
PackageShipped | Package shipped with tracking |
PackageDelivered | Package delivered |
Use * to receive every event. See the Webhooks Guide for the full list.
Webhook Payload Example
{
"event": "OrderShipped",
"timestamp": "2026-01-27T14:30:00Z",
"data": {
"orderId": "550e8400-e29b-41d4-a716-446655440000",
"trackingNumber": "1Z999AA10123456784",
"carrier": "UPS"
}
}
data also carries organizationId, eventId, and other fields. Ignore fields you don’t use.
Verify each delivery with the HMAC-SHA256 signature in the X-Vectis-Signature header.
Pagination
The order and product lists return paginated results:
GET /api/orders?limit=50&offset=100
| Parameter | Type | Default | Max | Description |
|---|---|---|---|---|
limit | integer | 20 | 100 | Items per page |
offset | integer | 0 | - | Items to skip |
Response includes pagination metadata. The pagination object always includes total, limit, and offset.
{
"orders": [...],
"pagination": {
"limit": 50,
"offset": 100,
"total": 523
}
}
Idempotency
To retry order creation safely, send an Idempotency-Key header with a value unique to the order. A repeat request with the same key within 24 hours returns the original order (status 201) instead of creating a new one. externalId is stored for your reference but is not checked for duplicates.
# First request (creates order)
POST /api/orders
Idempotency-Key: SHOP-12345
{ "externalId": "SHOP-12345", ... }
# Response: 201 Created
# Retry with the same key (returns the original order)
POST /api/orders
Idempotency-Key: SHOP-12345
{ "externalId": "SHOP-12345", ... }
# Response: 201 Created (same order returned)
Webhook Signature Verification
Outbound webhooks include an HMAC-SHA256 signature for verification.
Header: X-Vectis-Signature: t=<timestamp>,v1=<signature> (Unix seconds and a hex HMAC-SHA256). Deliveries also carry X-Vectis-Event and X-Vectis-Delivery-Id.
Verification (Node.js):
const crypto = require('crypto');
function verifyWebhook(rawBody, header, secret) {
// Parse t (timestamp) and v1 (signature) from X-Vectis-Signature
const { t, v1 } = Object.fromEntries(header.split(',').map(p => p.split('=')));
// Reject old timestamps (replay protection)
const age = Date.now() / 1000 - parseInt(t);
if (age > 300) return false; // 5 minute window
// Compute expected signature
const signedPayload = `${t}.${rawBody}`;
const expected = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// Timing-safe comparison (lengths must match first, or timingSafeEqual throws)
const received = Buffer.from(v1 || '');
const computed = Buffer.from(expected);
return received.length === computed.length && crypto.timingSafeEqual(received, computed);
}
Rate Limits
Rate limits are not published for the tenant API. Some features, such as AI demand forecasting, have their own limits. A 429 whose body has "error": "LIMIT_EXCEEDED" means a plan limit (for example warehouses, carrier accounts or sales channels); retrying won’t clear it. Any other 429 is temporary: wait and retry with backoff.
Error Handling
Errors return a non-2xx status and a JSON body with an error field. Depending on the endpoint, error is a message string, an object with code and message, or, for request validation failures (HTTP 400), an object with an issues list. Branch on the HTTP status code.
| Status Code | Meaning |
|---|---|
| 400 | Bad request / validation error |
| 401 | Invalid or missing access token |
| 403 | Authenticated but not permitted |
| 404 | Resource not found |
| 409 | Conflict (e.g., order already cancelled) |
| 429 | Too many requests, or a plan limit reached (see Rate Limits) |
| 500 | Internal server error |
API Reference
Full OpenAPI specification available at the API Reference page.
Need Help?
- Technical support: support@stoalogistics.com