API Design Lessons from Building an OMS
Vectis has a public REST API for orders, products, inventory, and webhook subscriptions. Here’s what we learned designing it.
Lesson 1: Idempotency Is Non-Negotiable
Networks fail. Clients retry. If your POST endpoint creates a duplicate resource on retry, you’ve got a problem.
Order creation in Vectis (POST /api/orders) accepts an Idempotency-Key header:
POST /api/orders
Idempotency-Key: order-create-abc123
If we’ve seen that key before, we return the original response. No duplicate order created.
Implementation: Store idempotency keys with their responses in Redis, expire after 24 hours. Check before processing, store after success.
Today, tokens from API clients are read-only on orders, products, and inventory, so check the API docs for current write access.
Lesson 2: Pagination Done Right
Offset pagination (?page=2&per_page=50) is simple but breaks when data changes. If a new order is inserted while you’re paginating, you’ll either skip an item or see one twice.
Cursor pagination is more robust:
GET /orders?cursor=eyJpZCI6MTIzNH0&limit=50
The cursor encodes the last item’s position. New inserts don’t affect your traversal.
Vectis order and product lists use limit and offset today, and their responses include a total. The inventory list isn’t paged: it returns up to 100 rows, so narrow it with filters such as sku or warehouseId. If you page through data that is changing, filter by date or re-read the last page so you don’t miss records.
Lesson 3: Predictable Errors
Errors return a non-2xx status and a JSON body with an error field. Branch on the HTTP status code.
Lesson 4: Verb the Nouns
REST purists say use HTTP methods for actions. In practice, some actions don’t map cleanly:
POST /api/orders/123/cancel
POST /api/packages/456/ship
PATCH /api/inventory/789/adjust
These are clearer than trying to shoehorn everything into PATCH:
PATCH /api/orders/123
{ "status": "shipped" } // What about tracking number? Carrier? Ship date?
Use sub-resource endpoints for actions that have their own parameters or side effects.
Lesson 5: Expand Related Resources
Fetching an order, then fetching its items, then fetching each product is slow. Allow expansion.
Vectis doesn’t support an expand parameter today. The order detail endpoint (GET /api/orders/{id}) already returns its line items, so the common case is one request.
Lesson 6: Webhooks Need Signatures
Anyone can POST to your webhook URL. Without verification, you’re trusting random internet traffic.
Every Vectis webhook includes a timestamped HMAC-SHA256 signature:
X-Vectis-Signature: t=1705312800,v1=abc123...
Computed as:
const signature = crypto
.createHmac('sha256', webhookSecret)
.update(`${timestamp}.${requestBody}`)
.digest('hex');
Clients split the header into t and v1, recompute the HMAC over the timestamp, a period, and the raw body, compare in constant time, and reject timestamps older than five minutes. Each delivery also carries X-Vectis-Event and X-Vectis-Delivery-Id headers.
Lesson 7: Rate Limits with Feedback
Rate limiting protects your system. But silent failures frustrate developers.
Vectis doesn’t publish API rate limits today: the public order, product, inventory, and webhook routes run no rate limiter. Where a specific feature is rate limited (AI forecasting, for example), responses include:
X-RateLimit-Limit
X-RateLimit-Remaining
X-RateLimit-Reset
When you hit that limit, we return 429 with a Retry-After header and a JSON body with error (a message) and retryAfter (seconds).
Developers can build backoff logic. No guessing why requests are failing.
Lesson 8: Scoped API Keys
One API key with full access is a security incident waiting to happen.
Vectis API keys have scopes. (An API client made in the account portal gets six of them, the order, product and inventory read and write scopes, and the portal doesn’t let you pick others; request fewer when you get a token.)
orders:read
orders:write
inventory:read
inventory:write
products:read
webhooks:manage
A key for your reporting dashboard only needs orders:read. A key for your WMS integration needs orders:write and inventory:write. Principle of least privilege.
Lesson 9: Versioning Strategy
Vectis paths are unversioned today (/api/orders). Non-breaking changes (new optional fields, new endpoints) ship in place.
Lesson 10: Document Everything
Every endpoint should have:
- Description of what it does
- Request parameters with types and validation rules
- Response schema with examples
- Error codes it can return
We document the API in OpenAPI 3.1. The interactive API Reference is built from that spec and shows each public endpoint with its parameters and response schema.
The best API is one developers can figure out from the docs without contacting support.
Vectis has a public REST API for orders, products, inventory, and webhook subscriptions, documented in OpenAPI 3.1. Explore the API.
Disclaimer
This content is for informational purposes only. Individual results may vary based on your specific circumstances.