# 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: ```json { "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: ```bash 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:** ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "friendlyId": "ORD-45678", "key": "ERP-ORDER-123", "status": "draft", ... } ``` ### On Update Add or change `key` on existing resources: ```bash 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: ```bash curl "https://api.mvmnt.io/v1/orders?key=ERP-ORDER-123" \ -H "Authorization: Bearer $TOKEN" ``` **Response:** ```json { "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 ```yaml 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 ```javascript 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 ```javascript 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 ```python 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 ```python 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 ### Without Client Keys (Not Recommended) ```javascript // 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'); }); ``` ### With Client Keys (Recommended) ```javascript // 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: ```json { "key": "TENANT-abc123:ORDER-789" } ``` Parse in webhook handler: ```javascript const [tenantId, orderId] = webhook.data.key.split(':'); ``` ### Use Case 2: Idempotency Use client key for idempotent creates: ```javascript 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: ```json { "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 - [Partial Updates](/getting-started/partial-updates) - Update client keys on existing resources - [Webhooks](/webhooks/overview) - Use client keys in webhook handlers - [API Reference](/apis/openapi) - See which resources support client keys (all of them!)