{"openapi":"3.1.0","info":{"title":"Movement OMS Public API","version":"1.2.0","description":"Org-scoped API for dealer storefronts and integrations (Wix, custom sites, Zapier, Make, server backends). Every response is limited to the Organization on your API key.\n\n## Auth\n\n`Authorization: Bearer mov_live_…`. Create and revoke keys in the OMS under **Settings → Integrations → Movement API**. Org is inferred from the key. Do not send an organization id.\n\n**Server-side only.** Do not put the API key in browser JavaScript, Wix site code visible to visitors, or a public Git repo. Call from a Wix backend (Velo HTTP functions / web methods), your own server, or an automation tool that stores secrets.\n\n## Typical storefront wiring\n\n1. `GET /api/v1/inventory`: list public available stock for your catalog page.\n2. `GET /api/v1/inventory/{idOrSlug}`: product detail.\n3. Manual buy (invoice / wire): `POST /api/v1/purchase-requests`. Creates a Deal in the OMS, emails the buyer a confirmation, and leaves invoice and payment instructions to your team. Use when Stripe is not connected.\n4. Card buy: `POST /api/v1/checkout/sessions`. Returns a Stripe Checkout URL. Requires `commerceCheckout` and Stripe connected.\n5. Inquire / sell / source / access: `POST /api/v1/leads`."},"servers":[{"url":"https://app.movementoms.com","description":"Movement OMS app host"},{"url":"http://localhost:3000","description":"Local OMS"}],"tags":[{"name":"Inventory","description":"Public catalog for your organization only"},{"name":"Journal","description":"Published articles (entitlement: journalCms)"},{"name":"Leads","description":"Inbound leads into your CRM (inquire, sell, source, access, notify, waitlist). For purchase intent, use Purchase requests."},{"name":"Purchase requests","description":"Manual buy / request-to-purchase. Creates a sell Deal with no Stripe charge. Staff follow up with invoice and payment instructions."},{"name":"Subscribers","description":"Newsletter capture with double opt-in (entitlement: newsletters). Requires name and email. POST again to resend after cooldown."},{"name":"Checkout","description":"Stripe Checkout sessions (entitlement: commerceCheckout). Use Purchase requests when Stripe is not connected."}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"mov_live_","description":"Organization API key. Org is inferred from the key. Do not send an organization id."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"}}},"OrganizationRef":{"type":"object","properties":{"slug":{"type":"string","example":"example-dealer"},"name":{"type":"string","example":"Example Dealer"}}},"InventoryItem":{"type":"object","properties":{"id":{"type":"integer"},"slug":{"type":"string","nullable":true},"brand":{"type":"string","nullable":true},"model":{"type":"string","nullable":true},"reference":{"type":"string","nullable":true},"stockIdentifier":{"type":"string","nullable":true},"status":{"type":"string","description":"available | pending | sold | …"},"assetType":{"type":"string","enum":["watch","car","art"]},"condition":{"type":"string","nullable":true},"priceCents":{"type":"integer","nullable":true},"priceOrInquire":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"seoTitle":{"type":"string","nullable":true},"seoDescription":{"type":"string","nullable":true},"soldAt":{"type":"string","format":"date-time","nullable":true},"primaryImageUrl":{"type":"string","nullable":true},"href":{"type":"string","description":"Storefront path, e.g. /watches/{id}/{slug}"},"images":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"caption":{"type":"string","nullable":true}}}},"related":{"type":"array","items":{"$ref":"#/components/schemas/InventoryItem"}}}},"InventoryCounts":{"type":"object","description":"Available counts only. Sold totals are not included. Use counts.available for storefront AVAILABLE readouts.","properties":{"available":{"type":"integer","description":"Count of status=available matching current filters (status query ignored for this count)"},"byAssetType":{"type":"object","properties":{"watch":{"type":"integer"},"car":{"type":"integer"},"art":{"type":"integer"}}}}},"JournalPostSummary":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"slug":{"type":"string"},"excerpt":{"type":"string","nullable":true},"imageUrl":{"type":"string","nullable":true},"imageAlt":{"type":"string","nullable":true},"publishedAt":{"type":"string","format":"date-time"}}},"LeadCreate":{"type":"object","required":["name","email","phone"],"properties":{"name":{"type":"string"},"email":{"type":"string","format":"email"},"phone":{"type":"string"},"message":{"type":"string"},"type":{"type":"string","enum":["inquire","sell","source","reservation","consign","request_access","notify","waitlist","queue","request_photos"],"default":"inquire","description":"notify = sold-listing alert; waitlist/queue = pending second buyer; request_photos = gallery failure CTA. inventoryItemId required for notify, waitlist, queue, request_photos."},"inventoryItemId":{"type":"integer","description":"Optional public inventory item in this org; required for notify / waitlist / queue / request_photos"},"photoRefs":{"type":"array","items":{"type":"string"},"description":"File refs from POST /api/v1/leads/attachments"},"meta":{"type":"object","additionalProperties":true,"description":"Free-form form fields (brands, budget, condition, tradeInterest, …)"},"utm":{"type":"object","additionalProperties":true}}},"PurchaseRequestCreate":{"type":"object","required":["inventoryItemId","name","email","phone"],"properties":{"inventoryItemId":{"type":"integer","description":"Public inventory item id from GET /api/v1/inventory (must be available and belong to your org)"},"name":{"type":"string","example":"Alex Collector"},"email":{"type":"string","format":"email","example":"alex@example.com"},"phone":{"type":"string","example":"+1 415 555 0100"},"message":{"type":"string","description":"Optional note from the buyer (shown on the Deal)"},"paymentMethod":{"type":"string","enum":["wire","zelle","trade","invoice"],"description":"Preferred settlement. Omit if unknown. Manual path only; for card use POST /api/v1/checkout/sessions."}}},"PurchaseRequestResponse":{"type":"object","properties":{"organization":{"$ref":"#/components/schemas/OrganizationRef"},"dealId":{"type":"integer","description":"OMS Deal id (type sell, stage new). Open /internal/deals/{dealId} in the OMS to send the invoice."},"customerId":{"type":"integer"},"inventoryItemId":{"type":"integer"},"stage":{"type":"string","example":"new"},"preferredPaymentMethod":{"type":"string","nullable":true,"example":"wire"},"settlement":{"type":"string","enum":["manual"],"description":"Always manual for this endpoint. For card settlement, use checkout sessions."}}},"CheckoutCreate":{"type":"object","required":["inventoryItemId","name","email","phone"],"properties":{"inventoryItemId":{"type":"integer"},"name":{"type":"string"},"email":{"type":"string","format":"email"},"phone":{"type":"string"},"message":{"type":"string"},"successUrl":{"type":"string","description":"Must include the literal placeholder {CHECKOUT_SESSION_ID}"},"cancelUrl":{"type":"string"}}},"SubscriberCreate":{"type":"object","required":["name","email"],"properties":{"name":{"type":"string"},"email":{"type":"string","format":"email"},"source":{"type":"string","description":"Optional free-form source label (footer, journal, Zapier, …)"}}}}},"security":[{"bearerAuth":[]}],"paths":{"/api/v1/inventory":{"get":{"tags":["Inventory"],"summary":"List public inventory","parameters":[{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"limit","in":"query","schema":{"type":"integer","default":24,"maximum":100}},{"name":"sort","in":"query","schema":{"type":"string","enum":["price_desc","price_asc","newest","oldest","updated","brand"],"default":"updated"}},{"name":"assetType","in":"query","schema":{"type":"string","enum":["watch","car","art"]}},{"name":"brand","in":"query","schema":{"type":"string"}},{"name":"model","in":"query","schema":{"type":"string"}},{"name":"reference","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","description":"Filter list rows (e.g. available for hide-sold). Does not change counts.available.","schema":{"type":"string"}},{"name":"q","in":"query","description":"Search brand, model, reference, stock id, slug","schema":{"type":"string"}}],"responses":{"200":{"description":"Paged inventory for the API key's organization only","content":{"application/json":{"schema":{"type":"object","properties":{"organization":{"$ref":"#/components/schemas/OrganizationRef"},"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer","description":"Total matching rows (including sold). Use counts.available for storefront AVAILABLE counts. Do not publish sold or total-listing figures to collectors."},"hasMore":{"type":"boolean"},"sort":{"type":"string"},"counts":{"$ref":"#/components/schemas/InventoryCounts"},"filters":{"type":"object","properties":{"brands":{"type":"array","items":{"type":"string"}}}},"items":{"type":"array","items":{"$ref":"#/components/schemas/InventoryItem"}}}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/inventory/{idOrSlug}":{"get":{"tags":["Inventory"],"summary":"Get one public inventory item","parameters":[{"name":"idOrSlug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Item detail","content":{"application/json":{"schema":{"type":"object","properties":{"organization":{"$ref":"#/components/schemas/OrganizationRef"},"item":{"$ref":"#/components/schemas/InventoryItem"}}}}}},"404":{"description":"Not found in this organization","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/journal":{"get":{"tags":["Journal"],"summary":"List published journal posts","parameters":[{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"limit","in":"query","schema":{"type":"integer","default":20,"maximum":50}}],"responses":{"200":{"description":"Paged journal","content":{"application/json":{"schema":{"type":"object","properties":{"organization":{"$ref":"#/components/schemas/OrganizationRef"},"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"items":{"type":"array","items":{"$ref":"#/components/schemas/JournalPostSummary"}}}}}}},"403":{"description":"journalCms entitlement required"}}}},"/api/v1/journal/{slug}":{"get":{"tags":["Journal"],"summary":"Get one published journal post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Post with HTML body"},"403":{"description":"journalCms entitlement required"},"404":{"description":"Not found"}}}},"/api/v1/leads":{"post":{"tags":["Leads"],"summary":"Create a lead","description":"Creates a CRM Submission, or an Application when type is request_access. Other types write a Submission.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadCreate"}}}},"responses":{"201":{"description":"Lead created in the org CRM","content":{"application/json":{"schema":{"type":"object","properties":{"organization":{"$ref":"#/components/schemas/OrganizationRef"},"id":{"type":"integer"},"type":{"type":"string","example":"inquire"},"customerId":{"type":"integer"},"inventoryItemId":{"type":"integer","nullable":true},"applicationId":{"type":"integer","description":"Present when type=request_access"}}}}}},"400":{"description":"Validation error"}}}},"/api/v1/leads/attachments":{"post":{"tags":["Leads"],"summary":"Upload lead photos","description":"Multipart upload (`photos` or `photo` fields). Returns fileRefs to include on POST /api/v1/leads. Local disk in development; GCS in production when configured.","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"photos":{"type":"array","items":{"type":"string","format":"binary"}},"photo":{"type":"string","format":"binary"}}}}}},"responses":{"200":{"description":"Uploaded refs","content":{"application/json":{"schema":{"type":"object","properties":{"organization":{"$ref":"#/components/schemas/OrganizationRef"},"fileRefs":{"type":"array","items":{"type":"string"}}}}}}},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/api/v1/subscribers":{"post":{"tags":["Subscribers"],"summary":"Start newsletter double opt-in","description":"Creates or finds a Customer, sets newsletter pending, and emails a confirmation link. Marketing opt-in is set only after they confirm at `/subscribe/confirm?token=…`. Name and email required. Server-side only (Bearer key).\n\nTo resend: POST the same payload again after the cooldown (~15 minutes). While on cooldown, the response is still `pending_confirmation` with `retryAfterSeconds`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriberCreate"}}}},"responses":{"200":{"description":"Already subscribed","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["already_subscribed"]},"customerId":{"type":"integer"}}}}}},"202":{"description":"Confirmation email sent (or cooldown active)","content":{"application/json":{"schema":{"type":"object","properties":{"organization":{"$ref":"#/components/schemas/OrganizationRef"},"status":{"type":"string","enum":["pending_confirmation"]},"customerId":{"type":"integer"},"retryAfterSeconds":{"type":"integer"}}}}}},"400":{"description":"Validation error"},"403":{"description":"newsletters entitlement required"}}}},"/api/v1/purchase-requests":{"post":{"tags":["Purchase requests"],"summary":"Create a manual purchase request","description":"Creates a sell Deal for an available public inventory item (request to buy). No Stripe charge.\n\nFlow:\n1. Find or create the Customer in your org CRM.\n2. Create Deal `type=sell`, `stage=new`, optional product line item from website price.\n3. Email and push your team with a link to the Deal.\n4. Email the buyer a confirmation (\"We got your purchase request\").\n5. Your team sends the invoice and payment instructions from the OMS (wire / Zelle / trade).\n\nUse for offline settlement (invoice, wire, Zelle, trade), or when Stripe is not connected. Storefront Buy / Request to buy forms should call this so a Deal lands in the OMS.\n\nFor card checkout, call `POST /api/v1/checkout/sessions`. For questions about a listing (not purchase intent), call `POST /api/v1/leads` with `type=inquire`.\n\nWix (server-side): call from a Velo web method or HTTP function with the key in a secret. Browser form posts to your backend; your backend posts here. On `201`, read `dealId` and show a thank-you state.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseRequestCreate"},"examples":{"wireBuy":{"summary":"Wire purchase request","value":{"inventoryItemId":42,"name":"Alex Collector","email":"alex@example.com","phone":"+1 415 555 0100","paymentMethod":"wire","message":"Please send wire instructions."}}}}}},"responses":{"201":{"description":"Purchase request created (Deal in OMS)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseRequestResponse"}}}},"400":{"description":"Validation error (missing fields, item not available, bad paymentMethod)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key"},"404":{"description":"Inventory item not found for this organization"}}}},"/api/v1/checkout/sessions":{"post":{"tags":["Checkout"],"summary":"Create a Stripe Checkout session","description":"Card path only. Requires `commerceCheckout` and Stripe connected. For invoice, wire, or other manual settlement, use `POST /api/v1/purchase-requests`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutCreate"}}}},"responses":{"200":{"description":"Checkout URL","content":{"application/json":{"schema":{"type":"object","properties":{"organization":{"$ref":"#/components/schemas/OrganizationRef"},"url":{"type":"string","format":"uri"},"dealId":{"type":"integer"},"inventoryItemId":{"type":"integer"}}}}}},"403":{"description":"commerceCheckout entitlement required or Stripe not connected"}}}}}}