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.

The Ondes PRO teamUpdated on
.ondes.pro/mcp/

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.

  1. Create a dedicated key

    Under API & Developers → API keys, create a key named “Logistics back office” with billing:read and billing:write, and a quota sized to your volume. The value is shown once: store it in your server’s secret manager.

  2. 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 with page_size.

    bash
    curl "https://your-workspace.ondes.pro/api/v1/billing/factures/?fields=id,numero,statut,date_echeance&page_size=100" \
      -H "Authorization: ApiKey opk_…"
  3. Walk a whole collection with a cursor

    For a full sync, add pagination=curseur and follow the next link to the end. Unlike page numbers, the cursor neither skips nor repeats rows when invoices are created mid-walk.

  4. Write with an idempotency key

    Any POST, PUT or PATCH can carry Idempotency-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.

    bash
    curl -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"}'
  5. 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 instance billing.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"]
    }
  6. Verify each delivery, then fetch the object

    The Ondes-Signature header reads t=<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.

  7. Test, then catch up on anything missed

    POST …/abonnements/<id>/tester/ sends a signed ondes.test event. 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

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.

ResponseMeaningRecommended reaction
400Invalid body or parameterRead ondes_erreur.champs, fix it, do not retry unchanged
403Missing scope or permissionAdd the scope to the key, or ask the admin for the module
409Same Idempotency-Key, different bodyFix key generation on the client side
429Per-minute quota reachedWait for Retry-After, then resume
503 instance_occupeeWorkspace briefly busyRetry 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.

Claude Code · Example conversation · demo data (Atelier Lumen)
  1. You : Write our back office’s webhook handler: for every new invoice, store its number and due date in our ondes_invoices table.
  2. MCP tool : ondes_exemple “Subscribe to events” recipe: creating the subscription, checking Ondes-Signature, fetching the object.
  3. MCP tool : ondes_impossible Confirms a delivery never contains the object’s content, only its identity.
  4. Assistant : The webhook only carries the resource and the id, so I am adding a GET billing/factures/<id>/?fields=numero,date_echeance,statut right after the signature check. The write to ondes_invoices is an upsert on id, so a replay from rejouer/ never creates a duplicate.

Going further

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