Every entity in the MVMNT API includes a key field - a string identifier that you control. This field enables bidirectional lookups between your system and MVMNT.
A client key is a custom string identifier that you can set on any MVMNT resource to reference your own system's ID, SKU, or external identifier.
- Type: String (up to 512 characters)
- Your Data: You define the value - MVMNT doesn't generate it
- Optional: Not required, but highly recommended for integrations
- Unique (Enforced): Must be unique per resource type - duplicate keys are rejected
- Queryable: Use it to find MVMNT resources by your system's IDs
- Always Returned: Included in all API responses and webhook payloads
Without client keys, you need to maintain a mapping table:
Your System MVMNT
=========== =====
ERP-ORDER-123 → ORD-45678
ERP-ORDER-124 → ORD-45679
ERP-ORDER-125 → ORD-45680This requires:
- Extra database table
- Synchronization logic
- Risk of mapping mismatches
Store your ID directly in MVMNT resources:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"friendlyId": "ORD-45678",
"key": "ERP-ORDER-123",
...
}Now you can query: "Give me the order with key=ERP-ORDER-123"
Include key when creating resources:
curl -X POST https://api.mvmnt.io/v1/orders \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"key": "ERP-ORDER-123",
"stops": [...],
"freight": {...}
}'Response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"friendlyId": "ORD-45678",
"key": "ERP-ORDER-123",
"status": "draft",
...
}Add or change key on existing resources:
curl -X PATCH https://api.mvmnt.io/v1/orders/ORD-45678 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"key": "ERP-ORDER-123"
}'Use the key filter parameter to find resources:
curl "https://api.mvmnt.io/v1/orders?key=ERP-ORDER-123" \
-H "Authorization: Bearer $TOKEN"Response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"friendlyId": "ORD-45678",
"key": "ERP-ORDER-123",
...
}Since keys are unique per resource type, querying by key returns a single resource (not an array).
- Set client keys on creation whenever possible
- Use consistent format across your integration (e.g., always
"ERP-ORD-{id}") - Include system prefix if integrating multiple systems
- Ensure keys are unique per resource type (enforced by MVMNT)
- Update webhook handlers to use
keyinstead of maintaining separate mappings - Validate format before setting (your own business rules)
- Don't store sensitive data (e.g., SSNs, credit cards) in client keys
- Don't exceed 512 characters (will be rejected)
- Don't use MVMNT IDs as client keys (redundant - MVMNT already has IDs)
- Don't include PII if you want to use client keys in logs/debugging
- Don't reuse keys across different resources of the same type (will be rejected)
key:
type: string
maxLength: 512
nullable: true
description: Client-defined reference identifier
example: 'ERP-ORDER-123'- Type: Must be a string
- Length: Maximum 512 characters
- Null: Allowed (field is optional)
- Uniqueness: Enforced per resource type (duplicate keys rejected)
- Format: No restrictions (any valid string)
No - You can set and update key at any time via POST, PATCH, or PUT.
async function createOrderWithClientKey(yourOrderId, orderData) {
const response = await fetch('https://api.mvmnt.io/v1/orders', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
...orderData,
key: `ERP-ORDER-${yourOrderId}`,
}),
});
return response.json();
}
// Usage
const mvmntOrder = await createOrderWithClientKey('12345', {
stops: [...],
freight: {...}
});
console.log(mvmntOrder.key); // "ERP-ORDER-12345"async function findOrderByClientKey(key) {
const response = await fetch(
`https://api.mvmnt.io/v1/orders?key=${encodeURIComponent(key)}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
},
);
return response.json(); // Returns single resource (keys are unique)
}
// Usage
const order = await findOrderByClientKey('ERP-ORDER-12345');
console.log(order.friendlyId); // "ORD-45678"class MVMNTOrderSync:
def __init__(self, client):
self.client = client # MVMNT API client
def create_order_from_erp(self, erp_order):
"""Create MVMNT order and store ERP order ID in key"""
mvmnt_order = self.client.post('/v1/orders', {
'key': f"ERP-{erp_order['id']}",
'stops': self.convert_stops(erp_order['stops']),
'freight': self.convert_freight(erp_order['freight']),
})
return mvmnt_order
def update_erp_from_webhook(self, webhook_payload):
"""Update ERP using key from webhook"""
key = webhook_payload['data']['key']
if not key or not key.startswith('ERP-'):
return # Not an ERP order
erp_order_id = key.replace('ERP-', '')
# Update ERP directly using the ERP order ID
erp_client.update_order(erp_order_id, {
'status': webhook_payload['data']['status'],
})import requests
def get_order_by_key(key: str) -> dict:
response = requests.get(
'https://api.mvmnt.io/v1/orders',
headers={'Authorization': f'Bearer {access_token}'},
params={'key': key}
)
response.raise_for_status()
return response.json() # Returns single resource (keys are unique)
# Usage
order = get_order_by_key('ERP-ORDER-12345')
print(f"MVMNT Order ID: {order['friendlyId']}")// Maintain mapping table
const orderMapping = new Map();
// On create
const mvmntOrder = await createOrder(orderData);
orderMapping.set(mvmntOrder.id, yourOrderId);
// On webhook
app.post('/webhooks/mvmnt', (req, res) => {
const mvmntOrderId = req.body.data.id;
const yourOrderId = orderMapping.get(mvmntOrderId); // ⚠️ Requires mapping
updateYourSystem(yourOrderId, req.body.data);
res.status(200).send('OK');
});// On create
const mvmntOrder = await createOrder({
...orderData,
key: yourOrderId, // ✅ Store your ID directly
});
// On webhook
app.post('/webhooks/mvmnt', (req, res) => {
const yourOrderId = req.body.data.key; // ✅ No mapping needed!
updateYourSystem(yourOrderId, req.body.data);
res.status(200).send('OK');
});Include tenant ID in client key:
{
"key": "TENANT-abc123:ORDER-789"
}Parse in webhook handler:
const [tenantId, orderId] = webhook.data.key.split(':');Use client key for idempotent creates:
async function createOrderIdempotent(key, orderData) {
try {
// Try to create with unique key
return await createOrder({
...orderData,
key,
});
} catch (error) {
if (error.code === 'DUPLICATE_KEY') {
// Order already exists, fetch it
return await findOrderByClientKey(key);
}
throw error;
}
}Track which orders were migrated from legacy system:
{
"key": "LEGACY-SYSTEM:ORDER-456"
}Problem: key is null after creation
Solutions:
- Verify you included
keyin request body - Check JSON is valid
- Ensure
keyvalue is a string (not number/object)
Problem: Query returns 404 or empty results
Solutions:
- Verify client key was set (use GET by ID to check)
- Check for typos or case sensitivity
- Ensure you're searching the correct resource type
- Try URL-encoding special characters:
?key=${encodeURIComponent(key)}
Problem: Create/update fails with duplicate key error
Solutions:
- Check if resource with that key already exists
- Use a different, unique key value
- If updating, ensure you're not conflicting with another resource's key
- Consider adding a prefix or suffix to make the key unique
- Partial Updates - Update client keys on existing resources
- Webhooks - Use client keys in webhook handlers
- API Reference - See which resources support client keys (all of them!)