Skip to content
Last updated

Using Client Keys

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.

What is a Client Key?

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.

Key Characteristics

  • 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

Why Use Client Keys?

Problem Without Client Keys

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-45680

This requires:

  • Extra database table
  • Synchronization logic
  • Risk of mapping mismatches

Solution With Client Keys

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"

Setting Client Keys

On Create

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",
  ...
}

On Update

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"
  }'

Querying by Client Key

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).

Best Practices

✅ Do

  • 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 key instead of maintaining separate mappings
  • Validate format before setting (your own business rules)

❌ Don't

  • 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)

Field Details

Schema

key:
  type: string
  maxLength: 512
  nullable: true
  description: Client-defined reference identifier
  example: 'ERP-ORDER-123'

Validation

  • 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)

Read-Only?

No - You can set and update key at any time via POST, PATCH, or PUT.

Examples

JavaScript: Create Order with Client Key

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"

JavaScript: Find Order by Client Key

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"

Python: Bidirectional Mapping

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'],
        })

Python: Query with Client Key

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']}")

Webhook Integration Pattern

// 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');
});

Advanced Use Cases

Use Case 1: Multi-Tenant SaaS

Include tenant ID in client key:

{
  "key": "TENANT-abc123:ORDER-789"
}

Parse in webhook handler:

const [tenantId, orderId] = webhook.data.key.split(':');

Use Case 2: Idempotency

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;
  }
}

Use Case 3: Migration Tracking

Track which orders were migrated from legacy system:

{
  "key": "LEGACY-SYSTEM:ORDER-456"
}

Troubleshooting

Client Key Not Being Set

Problem: key is null after creation

Solutions:

  1. Verify you included key in request body
  2. Check JSON is valid
  3. Ensure key value is a string (not number/object)

Can't Find Resource by Client Key

Problem: Query returns 404 or empty results

Solutions:

  1. Verify client key was set (use GET by ID to check)
  2. Check for typos or case sensitivity
  3. Ensure you're searching the correct resource type
  4. Try URL-encoding special characters: ?key=${encodeURIComponent(key)}

Duplicate Key Error

Problem: Create/update fails with duplicate key error

Solutions:

  1. Check if resource with that key already exists
  2. Use a different, unique key value
  3. If updating, ensure you're not conflicting with another resource's key
  4. Consider adding a prefix or suffix to make the key unique

Next Steps