{"components":{"securitySchemes":{"apiKey":{"scheme":"bearer","type":"http"}}},"info":{"description":"\n## Authentication\n\nEvery request carries an API key created by an administrator of your organization in the console\n(**Integrations → API keys**). Keys start with `vdm_live_` and are shown once, at creation.\n\n```\nAuthorization: Bearer vdm_live_…\n```\n\nA key has **scopes** (what it may read or change), optional expiry, its own rate limits and, optionally, a list of\nclient addresses (IPs or CIDRs) it works from. `GET /v1/me` tells what a key can do.\n\n## Conventions\n\n* All times are UTC, ISO 8601. Dates in query parameters are `YYYY-MM-DD`.\n* Objects are identified by their `uuid`.\n* Lists answer `{data, total, offset, limit}`; page with `offset` and `limit` (at most 200). Use the exports for bigger pulls.\n* Dated reports take `dateFrom` and `dateTo` (`YYYY-MM-DD`, at most 92 days) and the same `client`, `location`, `instance` filters as the console.\n* Errors answer `{\"message\": \"\u003ccode\u003e\"}` with 400 (bad_request, with `detail`), 401 (unauthorized), 403 (scope_required, with `scope`),\n  404 (not_found), 409 (conflict), 429 (rate_limited).\n* Writes (agents, SupplyPoint records, withdrawal limits) are applied by the agent at its next synchronization: they answer\n  `202 {\"status\": \"accepted\", …}` and show up under `/v1/agents/{uuid}/pending-changes` until applied. The webhook events\n  `agent.change.applied` / `agent.change.failed` tell the outcome.\n\n## Rate limits\n\nDefault 120 requests per minute and 10 000 per day per key (an administrator can set other values). Every answer carries\n`X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` (unix time), `X-RateLimit-Limit-Day` and\n`X-RateLimit-Used-Day`. Over the limit: 429 with `Retry-After` seconds. Back off and retry; do not poll faster than once a minute.\n\n## Exports (big data sets)\n\n`POST /v1/exports` with `{\"report\": \"withdrawals\", \"format\": \"csv\", \"params\": {\"dateFrom\": …, \"dateTo\": …}}` queues a file (csv, jsonl or xlsx)\nand answers `202 {\"uuid\": …}`. Poll `GET /v1/jobs/{uuid}` (or subscribe to `export.completed`) until `status` is\n`completed`; `result.url` is a download link valid for one hour (ask again for a fresh one). Files are kept for 24 hours.\nAt most 2 exports run at a time and 50 a day per key.\n\n## Examples\n\n**curl**\n\n```bash\ncurl -H \"Authorization: Bearer $VDM_KEY\" \\\n  \"https://\u003capi host\u003e/v1/agents?limit=50\"\n```\n\n**Python**\n\n```python\nimport requests\n\ns = requests.Session()\ns.headers[\"Authorization\"] = \"Bearer \" + VDM_KEY\nr = s.get(API + \"/v1/reports/withdrawals\", params={\"dateFrom\": \"2026-01-01\", \"dateTo\": \"2026-01-31\", \"limit\": 200})\nr.raise_for_status()\nfor row in r.json()[\"data\"]:\n    print(row)\n```\n\n**Power Query (Excel / Power BI)** — a complete template with paging and every report is on the help center's API page (Power BI section).\n\n```\nlet\n  Source = Json.Document(Web.Contents(\"https://\u003capi host\u003e/v1/locations\",\n    [Headers = [Authorization = \"Bearer \" \u0026 Key], Query = [limit = \"200\"]])),\n  Rows = Table.FromRecords(Source[data])\nin\n  Rows\n```\n\n## Webhooks\n\nAn administrator subscribes an https URL to events (**Integrations → Webhooks**); each webhook has a signing secret shown once.\nDeliveries are `POST` requests with a JSON body `{id, event, createdAt, account, data}` and the headers\n`X-VDM-Event`, `X-VDM-Delivery` and `X-VDM-Signature: t=\u003cunix\u003e,v1=\u003chex\u003e`, where `v1` is\nHMAC-SHA256 over `\u003ct\u003e + \".\" + \u003craw body\u003e` with the secret. Answer 2xx within 10 seconds; anything else is retried\n(1 m, 5 m, 30 m, 2 h, 12 h), and after 20 failures in a row the webhook is switched off. Deliveries may repeat: use `id` to\ndeduplicate, and reject signatures whose `t` is older than 5 minutes.\n\n```python\nimport hmac, hashlib, time\n\ndef verify(secret: str, signature: str, body: bytes) -\u003e bool:\n    parts = dict(p.split(\"=\", 1) for p in signature.split(\",\"))\n    if abs(time.time() - int(parts[\"t\"])) \u003e 300:\n        return False\n    expected = hmac.new(secret.encode(), parts[\"t\"].encode() + b\".\" + body, hashlib.sha256).hexdigest()\n    return hmac.compare_digest(expected, parts[\"v1\"])\n```\n\nEvents: `agent.online`, `agent.offline`, `agent.sync.completed`, `agent.sync.failed`, `agent.change.applied`,\n`agent.change.failed`, `stock.alert`, `limit.changed`, `route.published`, `export.completed`, `export.failed`, `ping`.\n","title":"VDMSync public API","version":"1"},"openapi":"3.0.3","paths":{"/v1/agents":{"get":{"description":"Needs the scope `agents:read`.","parameters":[{"in":"query","name":"client","schema":{"type":"string"}},{"in":"query","name":"location","schema":{"type":"string"}},{"in":"query","name":"instance","schema":{"type":"string"}},{"in":"query","name":"agentStatus","schema":{"type":"string"}},{"in":"query","name":"name","schema":{"type":"string"}},{"in":"query","name":"serialNumber","schema":{"type":"string"}},{"in":"query","name":"offset","schema":{"type":"string"}},{"in":"query","name":"limit","schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"403":{"description":"scope_required"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"Agents (SupplyPoint installations) with their state","tags":["agents"],"x-scope":"agents:read"},"post":{"description":"Needs the scope `agents:write`.","responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"403":{"description":"scope_required"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"Add an agent at a location (pair it with an access code from the console)","tags":["agents"],"x-scope":"agents:write"}},"/v1/agents/{uuid}":{"get":{"description":"Needs the scope `agents:read`.","parameters":[{"in":"path","name":"uuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"403":{"description":"scope_required"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"One agent","tags":["agents"],"x-scope":"agents:read"},"patch":{"description":"Needs the scope `agents:write`.","parameters":[{"in":"path","name":"uuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"403":{"description":"scope_required"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"Change an agent's name, model, serial number, description or address","tags":["agents"],"x-scope":"agents:write"}},"/v1/agents/{uuid}/actions/spstatus":{"post":{"description":"Needs the scope `agents:write`.","parameters":[{"in":"path","name":"uuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"403":{"description":"scope_required"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"Ask the agent whether SupplyPoint runs: 202 with a job","tags":["agents"],"x-scope":"agents:write"}},"/v1/agents/{uuid}/disable":{"post":{"description":"Needs the scope `agents:write`.","parameters":[{"in":"path","name":"uuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"403":{"description":"scope_required"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"Disable an agent (it stops synchronizing)","tags":["agents"],"x-scope":"agents:write"}},"/v1/agents/{uuid}/jobs/{job}":{"get":{"description":"Needs the scope `agents:read`.","parameters":[{"in":"path","name":"uuid","required":true,"schema":{"type":"string"}},{"in":"path","name":"job","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"403":{"description":"scope_required"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"State and result of an agent job","tags":["agents"],"x-scope":"agents:read"}},"/v1/agents/{uuid}/pending-changes":{"get":{"description":"Needs the scope `agents:read`.","parameters":[{"in":"path","name":"uuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"403":{"description":"scope_required"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"Record changes not yet applied by the agent","tags":["agents"],"x-scope":"agents:read"}},"/v1/agents/{uuid}/stop-sync":{"put":{"description":"Needs the scope `agents:write`.","parameters":[{"in":"path","name":"uuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"403":{"description":"scope_required"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"Stop the running synchronization","tags":["agents"],"x-scope":"agents:write"}},"/v1/agents/{uuid}/synchronizations":{"get":{"description":"Needs the scope `agents:read`.","parameters":[{"in":"path","name":"uuid","required":true,"schema":{"type":"string"}},{"in":"query","name":"offset","schema":{"type":"string"}},{"in":"query","name":"limit","schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"403":{"description":"scope_required"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"Synchronizations of an agent, newest first","tags":["agents"],"x-scope":"agents:read"}},"/v1/clients":{"get":{"description":"Needs the scope `network:read`.","parameters":[{"in":"query","name":"name","schema":{"type":"string"}},{"in":"query","name":"offset","schema":{"type":"string"}},{"in":"query","name":"limit","schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"403":{"description":"scope_required"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"Clients of the organization","tags":["clients"],"x-scope":"network:read"}},"/v1/delivery-plan":{"get":{"description":"Needs the scope `forecasts:read`.","parameters":[{"in":"query","name":"location","schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"403":{"description":"scope_required"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"Load list of a location's next delivery run","tags":["delivery-plan"],"x-scope":"forecasts:read"}},"/v1/exports":{"post":{"description":"Needs the scope `exports`.","responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"403":{"description":"scope_required"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"Export a whole report (csv, jsonl or xlsx) in the background: 202 with a job; needs the report's scope too","tags":["exports"],"x-scope":"exports"}},"/v1/forecasts":{"get":{"description":"Needs the scope `forecasts:read`.","parameters":[{"in":"query","name":"client","schema":{"type":"string"}},{"in":"query","name":"location","schema":{"type":"string"}},{"in":"query","name":"instance","schema":{"type":"string"}},{"in":"query","name":"status","schema":{"type":"string"}},{"in":"query","name":"within","schema":{"type":"string"}},{"in":"query","name":"q","schema":{"type":"string"}},{"in":"query","name":"offset","schema":{"type":"string"}},{"in":"query","name":"limit","schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"403":{"description":"scope_required"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"When each product runs out, soonest first","tags":["forecasts"],"x-scope":"forecasts:read"}},"/v1/jobs/{uuid}":{"get":{"description":"Needs the scope `exports`.","parameters":[{"in":"path","name":"uuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"403":{"description":"scope_required"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"State of an export and, once completed, a signed link to its file (1 h)","tags":["jobs"],"x-scope":"exports"}},"/v1/locations":{"get":{"description":"Needs the scope `network:read`.","parameters":[{"in":"query","name":"client","schema":{"type":"string"}},{"in":"query","name":"name","schema":{"type":"string"}},{"in":"query","name":"address","schema":{"type":"string"}},{"in":"query","name":"offset","schema":{"type":"string"}},{"in":"query","name":"limit","schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"403":{"description":"scope_required"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"Locations","tags":["locations"],"x-scope":"network:read"}},"/v1/locations/{uuid}":{"get":{"description":"Needs the scope `network:read`.","parameters":[{"in":"path","name":"uuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"403":{"description":"scope_required"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"One location","tags":["locations"],"x-scope":"network:read"}},"/v1/me":{"get":{"responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"The organization, the key, its scopes and limits","tags":["general"]}},"/v1/openapi.json":{"get":{"responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"This specification","tags":["general"]}},"/v1/reports/{type}":{"get":{"description":"Needs the scope `reports:read`.","parameters":[{"in":"path","name":"type","required":true,"schema":{"type":"string"}},{"in":"query","name":"client","schema":{"type":"string"}},{"in":"query","name":"location","schema":{"type":"string"}},{"in":"query","name":"instance","schema":{"type":"string"}},{"in":"query","name":"vendingMachine","schema":{"type":"string"}},{"in":"query","name":"dateFrom","schema":{"type":"string"}},{"in":"query","name":"dateTo","schema":{"type":"string"}},{"in":"query","name":"offset","schema":{"type":"string"}},{"in":"query","name":"limit","schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"403":{"description":"scope_required"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"Operational reports: stock, withdrawals, transactions, total-withdrawals, product-cost, user-cost, cost-center-cost, restock-history, return-history, hardware-events, inventory-adjustments, change-history. Dated ones need dateFrom and dateTo (YYYY-MM-DD) at most 92 days apart; hardware-events and inventory-adjustments need instance and vendingMachine","tags":["reports"],"x-scope":"reports:read"}},"/v1/route-plans":{"get":{"description":"Needs the scope `forecasts:read`.","parameters":[{"in":"query","name":"date","schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"403":{"description":"scope_required"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"Published delivery routes of a day","tags":["route-plans"],"x-scope":"forecasts:read"}},"/v1/supplypoint/{kind}/{record}":{"delete":{"description":"Needs the scope `supplypoint:write`.","parameters":[{"in":"path","name":"kind","required":true,"schema":{"type":"string"}},{"in":"path","name":"record","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"403":{"description":"scope_required"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"Delete a SupplyPoint record on its machine: 202, applied by the agent","tags":["supplypoint"],"x-scope":"supplypoint:write"},"patch":{"description":"Needs the scope `supplypoint:write`.","parameters":[{"in":"path","name":"kind","required":true,"schema":{"type":"string"}},{"in":"path","name":"record","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"403":{"description":"scope_required"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"Change a SupplyPoint item, item group, user or user group (kind: items, item-groups, users, user-groups; record: its uuid): 202, applied by the agent","tags":["supplypoint"],"x-scope":"supplypoint:write"}},"/v1/supplypoint/{type}":{"get":{"description":"Needs the scope `supplypoint:read`.","parameters":[{"in":"path","name":"type","required":true,"schema":{"type":"string"}},{"in":"query","name":"client","schema":{"type":"string"}},{"in":"query","name":"location","schema":{"type":"string"}},{"in":"query","name":"instance","schema":{"type":"string"}},{"in":"query","name":"offset","schema":{"type":"string"}},{"in":"query","name":"limit","schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"403":{"description":"scope_required"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"SupplyPoint master data (items, groups, users, machines, rationing)","tags":["supplypoint"],"x-scope":"supplypoint:read"}},"/v1/withdrawal-limits":{"get":{"description":"Needs the scope `limits:read`.","parameters":[{"in":"query","name":"client","schema":{"type":"string"}},{"in":"query","name":"location","schema":{"type":"string"}},{"in":"query","name":"instance","schema":{"type":"string"}},{"in":"query","name":"userNumber","schema":{"type":"string"}},{"in":"query","name":"userGroupNumber","schema":{"type":"string"}},{"in":"query","name":"itemNumber","schema":{"type":"string"}},{"in":"query","name":"itemGroupNumber","schema":{"type":"string"}},{"in":"query","name":"vmId","schema":{"type":"string"}},{"in":"query","name":"offset","schema":{"type":"string"}},{"in":"query","name":"limit","schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"403":{"description":"scope_required"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"Withdrawal limits","tags":["withdrawal-limits"],"x-scope":"limits:read"},"post":{"description":"Needs the scope `limits:write`.","responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"403":{"description":"scope_required"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"Add a withdrawal limit","tags":["withdrawal-limits"],"x-scope":"limits:write"}},"/v1/withdrawal-limits/{uuid}":{"delete":{"description":"Needs the scope `limits:write`.","parameters":[{"in":"path","name":"uuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"403":{"description":"scope_required"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"Delete a withdrawal limit","tags":["withdrawal-limits"],"x-scope":"limits:write"},"patch":{"description":"Needs the scope `limits:write`.","parameters":[{"in":"path","name":"uuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"unauthorized"},"403":{"description":"scope_required"},"429":{"description":"rate_limited"}},"security":[{"apiKey":[]}],"summary":"Change a withdrawal limit","tags":["withdrawal-limits"],"x-scope":"limits:write"}}},"servers":[{"url":"/"}]}