Vigilae Connect

An API you can read before you sign.

The specification is public, the webhook signature can be verified in ten lines on your side. This page documents what the API exposes today — nothing more, and that is deliberate: what isn't shipped isn't documented.

  • OpenAPI specification readable without a key or an account
  • Never a suspicious activity report, never client verbatim
  • Signed webhooks, verifiable outside Vigilae

Served by the API itself: the contract you read is the one that runs.

The perimeter

The whole journey, and two exclusions that will not budge.

Your software can run a file end to end: create the client, open the file, upload the documents, launch the screening, declare the beneficial owners, read the risk rating and retrieve the evidence file. Two things will never pass through it, and it is not a plan limitation: no suspicious transaction report, and no verbatim from your client. The first is an act the law reserves for the person who files it; the second has no business in an integration pipeline.

The file, end to end

Clients, files, supporting documents, screening, beneficial owners, risk rating, evidence file, integrity check. Every resource has its scope: your key opens only what you gave it.

Journal and aggregates

The firm's pKYC events — expired document, change of beneficial owner, re-screening to review — and portfolio counters for your dashboard. Always a bounded response, a stable cursor.

What you write carries a name

Upload, screen, qualify: every write demands its scope and records the operator. An anonymous qualification is refused — the integration records a human decision, it never makes one.

The API is designed server-to-server: a key in code shipped to the browser is a published key. Have a server-side proxy hold the key, never the page.

Keys

Scoped keys, shown once, revocable immediately.

  • Format. Every key starts with vgk_ and is sent as Authorization: Bearer vgk_…. The secret is shown only at creation: we keep nothing but its SHA-256 digest.
  • Scopes. A key carries only the scopes ticked when it is created: the general lecture (read, the default) and ecriture (write), or fine-grained ones, resource by resource — clients:, dossiers:, pieces:, criblage: (in lecture or ecriture), origine-fonds:lecture, journal:lecture.
    Inheritance is suffix-based, and it runs one way only: lecture covers every *:lecture, ecriture every *:ecriture; a fine-grained scope opens nothing beyond itself.
    Six scopes fall outside that pair and are therefore inherited from no general key: alertes:qualifier, embed:emettre, adversemedia:consulter, conformite:consulter, dispositif:consulter, integrite:consulter. Each is delegated on its own, when the key is created.
    A key without the required scope receives a 403 — see #scopes.
  • Expiry. One year after creation (visible in the firm's console). An expired key receives an explicit 401 — see #cle-expiree.
    A key can also stop being accepted before that date, when the API is no longer part of the firm's subscription. The stop then falls at least 30 days after a first email to the firm, on the date given by a second email, sent at least 7 days ahead. Once set, that date can be read in the firm's console (the coupure_le field of GET /connect/cles), and every response to an accepted call announces it in the Sunset header until it falls. After that date, calls get the 401 described under #authentification, with "motif": "fin_option_api"; if the API comes back into the subscription, the key is accepted again, until its own expiry date.
    A revoked key does not come back with the API. And when the whole subscription is suspended or ends, keys stop being accepted immediately, without this delay.
  • Rate. 60 requests/minute per key (overridable per key). A 429 carries Retry-After and the X-RateLimit-Limit / -Remaining / -Reset headers.
  • Revocation. Immediate, from the console. Resolution is by digest: keys cannot be enumerated.
Quickstart

Three curl calls and you have seen it all.

# Health + key authentication curl -s https://vigilae.org/api/v1/sante \ -H "Authorization: Bearer vgk_your_key" # Monitoring journal, from the beginning, in pages of 100 curl -s "https://vigilae.org/api/v1/evenements?depuisSeq=0&limite=100" \ -H "Authorization: Bearer vgk_your_key" # Qualify event 42 (write scope) curl -s -X POST https://vigilae.org/api/v1/evenements/42/qualifier \ -H "Authorization: Bearer vgk_your_key" \ -H "Content-Type: application/json" \ -d '{"statut":"qualifie","disposition":"traite"}'

Pagination, in one rule

Pass depuisSeq (0 on the first call) and limite (capped at 500): the response is sorted by ascending seq and returns prochainSeq, to pass back as is on the next call. A page shorter than limite means the end of the journal — prochainSeq then stays stable and serves as a polling cursor. Without depuisSeq, you get the "most recent first" view, bounded to 500.

The portfolio is queried on /api/v1/portefeuille/agregats — counters, one response, no pagination.

Webhooks

Signed, timestamped, delivered at least once.

Rather than polling the journal, receive it: Vigilae delivers events to your URL, in order, in batches of at most 100. Delivery is at-least-once — the cursor only advances on your 2xx, batch by batch — and idempotency is by seq: process each sequence exactly once (#idempotence).

Verifying the signature

Each delivery carries the header x-vigilae-signature: t=<unix>,v1=HMAC-SHA256(secret, t + "." + body). Recompute v1 from the raw body received, and reject any timestamp t beyond 5 min: that is the anti-replay. During a secret rotation the header carries one v1 per still-valid secret (24 h overlap): accept if any of them matches.

// Node — signature verification (no dependency) import { createHmac, timingSafeEqual } from 'node:crypto'; export function signatureValid(header, rawBody, secrets, toleranceMs = 5 * 60 * 1000) { const t = Number((/(?:^|,)t=(\d+)/.exec(header) || [])[1]); if (!t || Math.abs(Date.now() - t * 1000) > toleranceMs) return false; // anti-replay const received = [...header.matchAll(/v1=([0-9a-f]{64})/g)].map((m) => m[1]); return [].concat(secrets).some((secret) => { const expected = createHmac('sha256', secret).update(t + '.' + rawBody).digest('hex'); return received.some((r) => r.length === expected.length && timingSafeEqual(Buffer.from(r), Buffer.from(expected))); }); }
# Python — same verification import hmac, hashlib, re, time def signature_valid(header: str, raw_body: bytes, secrets, tolerance_s: int = 300) -> bool: m = re.search(r"(?:^|,)t=(\d+)", header) if not m or abs(time.time() - int(m.group(1))) > tolerance_s: return False # anti-replay received = re.findall(r"v1=([0-9a-f]{64})", header) for secret in secrets: expected = hmac.new(secret.encode(), m.group(1).encode() + b"." + raw_body, hashlib.sha256).hexdigest() if any(hmac.compare_digest(expected, r) for r in received): return True return False

Transitional: the old sha256=HMAC(body) format is still emitted on x-vigilae-signature-legacy for receivers already in place; its removal is planned with webhooks v2. New receivers verify the timestamped scheme above, not the old one.

Rotation is triggered from the console (POST /connect/webhook/rotation on the application side): for 24 h the old secret still signs — time to deploy the new one on your side without a failure window.

Embedded widgets

Vigilance status, inside your screens.

Your software already creates the files through the API; widgets give it the surface that was missing: showing a file's vigilance status inside your client record, without rebuilding a compliance UI. The flow takes three steps — your server mints a short-lived token, the returned URL goes into an iframe, the widget displays. Displaying is all it does: no decision, no qualification, no write ever goes through a widget. Clicking opens the Vigilae application, where an identified human acts.

1. Mint the token — server-side, never in the page

The vgk_ key never touches the browser. Your server calls the mint with the dedicated embed:emettre scope (ticked at key creation, never inherited) and only the short token goes down to the page: fifteen minutes by default, bounded 5-60, tied to one file, one component, one declared origin. It travels in the URL fragment — it reaches neither our logs nor yours.

# A token for the Compliance Button, label picked from the catalogue curl -s -X POST https://vigilae.org/api/v1/embed/jetons \ -H "Authorization: Bearer vgk_your_key" \ -H "Content-Type: application/json" \ -d '{"dossier_id":"D123","composant":"bouton", "audience":"https://your-software.example","libelle":"controler_client"}' # 201 -> { "jeton": "…", "expire_le": "…", # "url": "https://vigilae.org/embarque/bouton?c=…#…" }

2. Place the iframe — the launcher, in ten lines

The vigilae-embed.js launcher creates the sandboxed iframe, performs the handshake and follows the widget's size (bounded — the iframe never dictates its height to your page). CSP-wise, your page opens a single directive: frame-src https://vigilae.org. No inline script required, no dependency. A hand-written iframe works just as well.

<script src="https://vigilae.org/embarque/vigilae-embed.js" defer></script> <div id="vigilance"></div>
// In your script: the url comes from YOUR server, which called the mint var widget = VigilaeEmbed.monter({ conteneur: 'vigilance', url: urlReturnedByTheMint, renouvelerJeton: function () { // on expiry: re-mint server-side return fetch('/my-server/vigilae-token').then(function (r) { return r.text(); }); }, }); // widget.rafraichir(token) pushes a fresh token; widget.demonter() removes everything.

3. The Compliance Button: your label, our state

The button label is picked from a closed catalogue — never free text on a surface that speaks about vigilance:

  • controler_client — "Check this client" (default)
  • lancer_vigilance — "Start due diligence"
  • verifier_be — "Check beneficial owners"
  • preparer_dossier — "Prepare the AML file"

The status dot, however, renders a state computed by Vigilae: a_jour (green — screening and rating up to date), a_revoir (amber — screening or rating outdated, or the source failed on the last run), alerte (red — at least one alert to review), inconnu (grey — file never screened). The label is configurable, the semantics never are: the colour is bound to the state inside the shell itself, which ignores any postMessage attempt to override it. A file in alert shows an alert, whatever the styling.

The postMessage contract, in full

The widget emits vigilae:pret, vigilae:taille (auto-resize), vigilae:expire (re-mint server-side, then post vigilae:jeton) and vigilae:ouvrir. Never a name, a risk or an alert inside a message: your software already has everything through the server-to-server API, and this channel will not become a second one.

Origin declaration: by your firm's administrator. An embed token is tied to a declared origin of your firm — it is what drives the shell's frame-ancestors CSP. The administrator declares it directly in Vigilae, on the "Integrations & security" screen: ten https:// origins at most, explicit host, never a wildcard; the components they enable; and a mass revocation that kills every token already issued in one gesture. http://localhost is only allowed on a sandbox firm — write to contact@vigilae.org (subject "Embedded widgets") to have one opened for the duration of your integration. No origin declared: the mint refuses with a 400. An instance whose origin store is not wired: an explicit 501 — see #embed-indisponible.
Test environment

Sandbox: on request.

There is no self-service sandbox yet — we would rather tell you here than let you discover it. Write to contact@vigilae.org (subject "API access"): we open a trial firm with fictitious data for the duration of your integration, and we stay reachable while it progresses.

Each API error class has a stable address on the error reference: #authentification, #scopes, #cloisonnement, #debit, #signature… The API's error responses will point to these anchors.

The API exposes neither suspicious activity reports nor client verbatim, by construction (art. L.561-18 of the French CMF). No vigilance decision is taken by the API: it exposes and qualifies events; the professional decides.