Developer guide
Build a back-office integration with the Ondes PRO API
Syncing an ERP, feeding a warehouse system, pushing customers from a line-of-business app: this guide walks through a complete server-to-server integration on the Ondes PRO REST API v1. A minimally scoped key, paginated reads, idempotent writes, signed webhooks so you react instead of polling, and the MCP server as a companion while you build.
Type your workspace name: the configurations on this page adapt. It is remembered in this browser only.
How do you authenticate against the Ondes PRO REST API?
A service running with nobody at the keyboard uses an API key, sent as Authorization: ApiKey opk_…. Every key carries scopes in module:action form — billing:read, shop:write, inventory:read — and those scopes are a ceiling: the key never reaches beyond the permissions of the person who created it. If that person loses access to invoicing, so does the key.
Give each integration its own key with only what it needs. The day a server is compromised, you revoke one key without breaking the others, and the call log in the Analytics tab shows what it did. Each key also has a per-minute quota, chosen at creation; the X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers tell you where you stand.
Set up a reliable integration, from key to webhook
The scenario: Atelier Lumen’s logistics back office needs every new invoice, and must create in Ondes PRO the customers its sales team enters.
Create a dedicated key
Under API & Developers → API keys, create a key named “Logistics back office” with
billing:readandbilling:write, and a quota sized to your volume. The value is shown once: store it in your server’s secret manager.Read only what you need
?fields=trims each row to the useful fields, and?expand=inlines a relation in the same response instead of issuing extra calls. Pages hold 50 rows by default, up to 200 withpage_size.bashcurl "https://your-workspace.ondes.pro/api/v1/billing/factures/?fields=id,numero,statut,date_echeance&page_size=100" \ -H "Authorization: ApiKey opk_…"Walk a whole collection with a cursor
For a full sync, add
pagination=curseurand follow thenextlink to the end. Unlike page numbers, the cursor neither skips nor repeats rows when invoices are created mid-walk.Write with an idempotency key
Any
POST,PUTorPATCHcan carryIdempotency-Key. Same key and same body: the stored response comes back and nothing runs again. Same key, different body:409, because that signals a client-side bug. Derive the key from your own system’s identifier, not from a random value drawn on every attempt.bashcurl -X POST https://your-workspace.ondes.pro/api/v1/billing/clients/ \ -H "Authorization: ApiKey opk_…" \ -H "Idempotency-Key: crm-customer-4812" \ -H "Content-Type: application/json" \ -d '{"nom": "Le Comptoir des Marées", "email": "purchasing@comptoir-marees.com", "ville": "Saint-Malo"}'Subscribe to events
POST /api/v1/developer/abonnements/creer/registers your server’s address and the events you follow, named<resource>.<cree|modifie|supprime>— for instancebilling.factures.cree. The signing secret is returned only at creation. The address must belong to a declared origin, and internal network addresses are refused.json{ "nom": "Logistics back office", "url": "https://logistics.atelier-lumen.com/hooks/ondes", "evenements": ["billing.factures.cree", "billing.factures.modifie"] }Verify each delivery, then fetch the object
The
Ondes-Signatureheader readst=<timestamp>,v1=<hmac>; recompute the HMAC-SHA256 of<t>.<body>with your secret and reject timestamps older than five minutes. The payload only carries the object’s identity (resource,id, company): fetch it through the API to get its current state.Test, then catch up on anything missed
POST …/abonnements/<id>/tester/sends a signedondes.testevent.GET …/<id>/livraisons/lists what went out and with which status code;POST …/<id>/rejouer/resends a delivery after an outage on your side.
Verify a webhook signature in Python
import hashlib, hmac, time
def signature_is_valid(body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
timestamp = int(parts.get("t", "0"))
if abs(time.time() - timestamp) > tolerance:
return False
expected = hmac.new(
secret.encode(), f"{timestamp}.".encode() + body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))Compute the signature over the RAW body you received, before any JSON parsing.
Which errors to handle, and how to respond?
Every API v1 error shares one JSON shape: an ondes_erreur object with a stable code, a readable message and, for validation failures, the offending champs.
| Response | Meaning | Recommended reaction |
|---|---|---|
400 | Invalid body or parameter | Read ondes_erreur.champs, fix it, do not retry unchanged |
403 | Missing scope or permission | Add the scope to the key, or ask the admin for the module |
409 | Same Idempotency-Key, different body | Fix key generation on the client side |
429 | Per-minute quota reached | Wait for Retry-After, then resume |
503 instance_occupee | Workspace briefly busy | Retry after Retry-After, with the same idempotency key |
Bulk writes without leaving a half-finished import
On any resource that accepts writes, POST /api/v1/<resource>/lot/ bundles up to 100 operations into a single transaction. The body holds an operations list, each with its methode (POST, PATCH, PUT, DELETE), its id for an existing object and its donnees.
A batch is all or nothing: one rejected operation rolls back the lot, and the response says which one and why. Every operation goes through the same checks as a single call — permissions, company, test mode — so a batch grants nothing new; it just saves a hundred round trips.
Test mode: build against real routes with no consequences
Some resources have a twin under /api/v1/test/: store orders, video meetings, email sends and invitations. The mode comes from the URL and nothing else — no header, no parameter — so you cannot write for real by accident. Test objects stay out of revenue figures, exports and dashboards.
Point your staging environment at the /test/ URLs and production at the plain ones: switching becomes a configuration variable rather than a code change.
The MCP server as a development companion
Once connected to your workspace, your coding assistant reads each resource sheet and the verified recipes instead of assuming.
Going further
- REST API v1Around 135 REST resources, scoped keys, an OpenAPI schema and signed webhooks.Read
- MCP vs REST APIWhat really sets them apart, when to pick which, and why at Ondes PRO they work together.Read
- InvoicingQuotes, invoices, reminders and credit notes prepared by your assistant, within invoicing rules.Read
- CursorOne `.cursor/mcp.json` file, one OAuth sign-in: Cursor’s agent codes against your real workspace.Read
Frequently asked questions
Which authentication should a server integration use?
An API key in the Authorization: ApiKey opk_… header, created under API & Developers → API keys with only the scopes it needs. Scopes are a ceiling: the key never exceeds its creator’s permissions.
How do I avoid duplicates when a request fails halfway?
Send an Idempotency-Key header on every write. Replayed with the same body, the request returns the stored response without running again; with a different body, it returns 409.
How do I get notified of changes without polling the API?
Create a subscription through developer/abonnements: Ondes PRO sends your server events such as billing.factures.cree, signed with the Ondes-Signature header. The delivery log and the rejouer/ action let you recover from downtime.
Why doesn’t the webhook include all of the object’s data?
By design: the payload only carries the object’s identity. You fetch it through the API with your key, which guarantees its current state and applies your permissions at that moment.
Is there an OpenAPI schema?
Yes, an OpenAPI 3.1 schema generated from your workspace’s real routes, downloadable from API & Developers once signed in. It is personalised: it only describes the workspace’s modules.
What happens when my quota runs out?
The API returns 429 with a Retry-After header. The X-RateLimit-* headers on every response let you plan ahead, and a workspace manager can raise the key’s per-minute quota.
Plug your AI into your business
The MCP server is included in every plan, with no add-on or extra fee.
Read next
- Connect your websiteShow the catalogue and take Stripe payments from a site that is already live, without rebuilding it.Read
- Migrate to Ondes PROBring over catalogue, customers and billing records in verified batches, with AI doing the column mapping.Read
- Automate with AIReminders, reports and automations described in one sentence, always reviewed before anything runs.Read