marius
How it worksWhat it bringsFree auditCustomisePricingFAQClient areaTry itFR/EN
Try it
How it worksWhat it bringsFree auditCustomisePricingFAQClient areaFrançais

APIs and webhooks

To start upKeys and authenticationLists and synchronizationProspectsConversationsAccount, sites and figuresWebhooksVerify signatureZapier, Make, n8nErrors and limitationsPersonal dataHistorique

Dernière mise à jour : 15 octobre 2026

Translation provided for convenience; the French version prevails.

OpenAPI specification: openapi.json
A question: bonjour@marius.qd.je

01

To start up

Marius qualifies visitors to your site and turns them into prospects. The API and webhooks send them to your tools: your CRM (HubSpot, Pipedrive, Salesforce, Axonaut, etc.), a spreadsheet, Zapier, Make, n8n or your own software.

  • Webhooks send each new lead to an address of your choice, as soon as it arrives. It's the simplest way to populate a CRM.
  • The API allows your tool to reread everything whenever it wants: prospects, conversations, sites, figures of the month. It also allows you to mark a prospect as processed and add notes, which appear in your Marius space.

The API is open to customers whose site is online, all plans combined. THE owner of the space creates the keys and webhooks in its space: My account → API and webhooks.

All addresses start with https://marius.qd.je/api/v1. They respond in JSON (UTF-8). Dates follow ISO 8601, in UTC.

curl https://marius.qd.je/api/v1/me \
  -H "Authorization: Bearer mr_live_votre_cle"
02

Keys and authentication

Each call has a key in the header Authorization: Bearer mr_live_…. A key starts with mr_live_ and is displayed only once, at its creation: Marius only keeps a print, no one can find it. If it is lost, delete it and create another one.

  • Scope. A key opens all the data in your space (all your sites), never that of another client.
  • Read-only or edits. By default, a key only reads. Check "Allow changes" when it is created so that it can mark a prospect as processed and add notes.
  • Expiry. Each key has an expiration date, chosen when it is created (two years at most). The owner receives an email seven days in advance.
  • Deletion. A deleted key stops working immediately. It belongs to your company, not to a person: it remains valid if the owner of the space changes.
  • Server side only. A key is kept like a password. Never put it in the code of a web page or a mobile application: the API also refuses calls made from a browser.
03

Lists and synchronization

Lists (/leads, /conversations) return one page at a time:

{
  "object": "list",
  "data": [ … ],
  "has_more": true,
  "next_cursor": "MjAyNi0xMC0xNVQwOToxMzowMS40ODIrMDA6MDB8NmQxYzRiMGUt…"
}
SettingRole
limitElements per page, from 1 to 100 (default 25).
cursorNext page: value next_cursor from the previous page, as is.
orderdesc (default): most recent first. asc : the oldest first, for synchronization.
updated_sinceOnly what changed after this date (ISO 8601 with time zone, for example 2026-10-15T09:00:00Z).

To keep a CRM up to date without a webhook, keep the largest updated_at received and go from there to the following passage:

# 1st time: all the history, from oldest to most recent
curl "https://marius.qd.je/api/v1/leads?order=asc&limit=100" -H "Authorization: Bearer $MARIUS_KEY"

# next page: resume next_cursor as long as has_more is true
curl "https://marius.qd.je/api/v1/leads?order=asc&limit=100&cursor=MjAyNi0x…" -H "Authorization: Bearer $MARIUS_KEY"

# then, every 15 minutes: only what has changed since then
# largest updated_at already received
curl "https://marius.qd.je/api/v1/leads?order=asc&updated_since=2026-10-15T09:13:01.482Z" -H "Authorization: Bearer $MARIUS_KEY"

An unknown parameter is refused (error 400): a typing error never goes unnoticed.

04

Prospects

A prospect is a visitor who has left their email or number. Tests made with "Tester Marius" never appear.

GET/leadsList. Filters: site (site identifier), status (new, done), score (hot, warm, cold), plus those of the paging. Sorted by updated_at.
GET/leads/{id}A prospect, with your team's notes (notes).
PATCH/leads/{id}Change the status: {"status": "done"} (processed) or {"status": "new"} (to be processed). Key with modifications.
GET/leads/{id}/notesTeam notes on this prospect.
POST/leads/{id}/notesAdd a note ({"text": "..."}, 2,000 characters at most), signed "API · key name". Key with modifications.
{
  "id": "6d1c4b0e-2f7a-4c55-9a1e-3b8d2c7f0a19",
  "object": "lead",
  "site": { "id": "290583f4-e9d2-4528-b615-83b8e23fc7e1", "name": "Atelier Martin", "url": "https://www.atelier-martin.fr" },
  "conversation_id": "a3b9e1c2-5d4f-4e8a-b7c6-1f2e3d4c5b6a",
  "status": "new",
  "done_at": null,
  "done_by": null,
  "contact": { "name": "Claire Dubois", "email": "claire.dubois@example.fr", "phone": "06 12 34 56 78" },
  "answers": [
    { "key": "project", "label": "What is your project?", "value": "Renovate the kitchen", "answered_at": "2026-10-15T09:12:44Z" },
    { "key": "delay", "label": "When?", "value": "Before Christmas", "answered_at": "2026-10-15T09:13:01Z" }
  ],
  "score": {
    "level": "hot",
    "points": 80,
    "criteria": [ { "id": "budget", "label": "Indicated budget", "points": 25 } ]
  },
  "estimated_value_eur": 8500,
  "summary": {
    "text": "Claire wants to renovate her kitchen before Christmas and has a set budget.",
    "need": "Complete kitchen renovation",
    "next_step": "Call her back this week to schedule a visit.",
    "score_reason": "Contact details left, budget and deadline given."
  },
  "qualified_at": "2026-10-15T09:45:02.118+00:00",
  "created_at": "2026-10-15T09:11:20.405+00:00",
  "updated_at": "2026-10-15T09:45:02.118+00:00",
  "last_message_at": "2026-10-15T09:14:37.902+00:00",
  "espace_url": "https://marius.qd.je/espace/prospects?p=6d1c4b0e-2f7a-4c55-9a1e-3b8d2c7f0a19"
}
status
new (to be processed) or done (processed, with done_at And done_by).
contact
Name, email and telephone number left by the visitor; null for what he did not give.
answers
Answers to the questions in your questionnaire, in their order.
score
Level (hot, warm, cold), points out of 100 and criteria met. null until the form is completed.
estimated_value_eur
Estimated value, if you have provided your prices; Otherwise null.
summary
Summary, need, recommended next step and reason for score. Done once the conversation has died down (30 minutes without a message), done again if it resumes.
space_url
The file in your Marius space, to be linked in your CRM.
curl -X PATCH https://marius.qd.je/api/v1/leads/6d1c4b0e-2f7a-4c55-9a1e-3b8d2c7f0a19 \
  -H "Authorization: Bearer $MARIUS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "done"}'
curl -X POST https://marius.qd.je/api/v1/leads/6d1c4b0e-2f7a-4c55-9a1e-3b8d2c7f0a19/notes \
  -H "Authorization: Bearer $MARIUS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Quote sent on 10/16."}'

# answer (201)
{ "id": "f0c1…", "object": "note", "author": "API · HubSpot", "text": "Quote sent on 10/16.",
  "created_at": "2026-10-16T08:02:11+00:00", "edited_at": null }
05

Conversations

GET/conversationsList, sorted by date of last message. Filters: site, has_lead (true : only those who gave a lead), updated_since (those who have a message after this date), plus the paging.
GET/conversations/{id}A conversation with its messages (500 at most), in order.

In messages, role worth visitor Or assistant ; unanswered points out a response from Marius who did not have the information.

{
  "id": "a3b9e1c2-5d4f-4e8a-b7c6-1f2e3d4c5b6a",
  "object": "conversation",
  "site": { "id": "290583f4-…", "name": "Atelier Martin", "url": "https://www.atelier-martin.fr" },
  "started_at": "2026-10-15T09:11:02+00:00",
  "last_message_at": "2026-10-15T09:14:37+00:00",
  "messages_count": 8,
  "first_message": "Hello, do you make custom kitchens?",
  "lead_id": "6d1c4b0e-2f7a-4c55-9a1e-3b8d2c7f0a19",
  "messages": [
    { "id": "…", "role": "visitor", "content": "Hello, do you do custom kitchens?", "unanswered": null, "created_at": "…" },
    { "id": "…", "role": "assistant", "content": "Yes:…", "unanswered": false, "created_at": "…" }
  ]
}
06

Account, sites and figures

GET/meYour company, its formula and its sites. Convenient for checking a key.
GET/sitesYour sites: identifier (for the filter site), name, address, state.
GET/statsFigures for one month, Paris time, tests excluded. Settings: month (YYYY-MM, the current month by default), site.
{
  "object": "account",
  "client": { "id": "8c1f…", "name": "Atelier Martin", "plan": "pro", "live_since": "2026-10-02T08:14:00+00:00" },
  "sites": [
    { "object": "site", "id": "290583f4-e9d2-4528-b615-83b8e23fc7e1", "name": "Atelier Martin",
      "url": "https://www.atelier-martin.fr", "status": "active", "created_at": "2026-09-30T09:42:12+00:00" }
  ]
}
{
  "object": "stats",
  "month": "2026-10",
  "site_id": null,
  "conversations": 412,
  "visitor_messages": 1386,
  "leads": 37,
  "hot_leads": 9,
  "done_leads": 21,
  "unanswered_questions": 14
}
07

Webhooks

In My account → API and webhooks, the owner indicates an address in https:// and chooses the events. Marius sends a request there POST in JSON for each event, in less than a minute.

EventWhen
lead.createdA visitor has just left their email or number: a new prospect.
lead.qualifiedHis form is ready: summary, need, score, estimated value. May return if the conversation resumes.
lead.updatedIts status has changed (processed, to be processed), in space or by the API, or its contact details have been completed.
pingThe "Send a test" button in My Account.

data contains the prospect, in the same form as GET /leads/{id} (without notes), as it was at the time of the event.

POST https://votre-outil.agence.fr/marius
Content-Type: application/json; charset=utf-8
User-Agent: marius-Webhooks/1.0 (+https://marius.qd.je/developpeurs) …
Marius-Event: lead.created
Marius-Event-Id: b7e2c9d0-6a1f-4f3e-8c2d-9e0f1a2b3c4d
Marius-Delivery: 0e9d8c7b-6a5f-4e3d-2c1b-0a9f8e7d6c5b
Marius-Signature: t=1760519581,v1=5f2b0c…

{
  "id": "b7e2c9d0-6a1f-4f3e-8c2d-9e0f1a2b3c4d",
  "object": "event",
  "type": "lead.created",
  "created_at": "2026-10-15T09:13:01.482+00:00",
  "data": { "id": "6d1c4b0e-…", "object": "lead", "contact": { … }, … }
}
  • Respond quickly with a 2xx code, in less than 10 seconds, then process the event. A redirection (3xx) counts as a failure: indicate the final address.
  • New tests. If unsuccessful, Marius tries again after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours. After 7 failed attempts (around 21 hours), the event is abandoned; If your address has not received anything during this time, the webhook is disabled and the owner receives an email. He reactivates it in My Account.
  • Duplicates and order. The same event can happen twice, and two events can happen out of order: duplicate with id (or the header Marius-Event-Id) and rely on data.updated_at.
  • Missed events (webhook paused or disabled): they are not returned. Retrieve them through the API with updated_since.
  • Addresses accepted : https://, a public domain name (no IP address, no private network), port 443 or 8443, 5 webhooks per space at most.
  • Site no longer online : no more events are sent, and the keys are refused.
08

Verify signature

Each shipment is signed with the webhook secret (whsec_…, shown only once, when adding the webhook). Verifying the signature ensures that the submission comes from Marius and has not been modified.

The header Marius-Signature worth t=<timestamp>,v1=<signature>. The signature is HMAC-SHA256, in hexadecimal, of the string <timestamp>.<raw body>, with the entire secret (whsec_ included) as a key. Calculate it on the body received as is, before decoding it, and refuse a timestamp of more than 5 minutes.

Node.js

import crypto from 'node:crypto'

// rawBody : le corps reçu tel quel (texte), AVANT JSON.parse
export function mariusSignatureOk(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.trim().split('=')))
  const t = Number(parts.t)
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false // plus de 5 minutes : refusé
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
  const given = String(parts.v1 ?? '')
  return given.length === expected.length && crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected))
}

// Express : app.post('/marius', express.raw({ type: 'application/json' }), (req, res) => {
// if (!mariusSignatureOk(req.body.toString('utf8'), req.get('Marius-Signature') ?? '', process.env.MARIUS_WEBHOOK_SECRET)) return res.sendStatus(401)
// const event = JSON.parse(req.body)
// res.sendStatus(204) // respond quickly, process later
// })

PHP

function marius_signature_ok(string $rawBody, string $header, string $secret): bool {
    parse_str(str_replace(',', '&', $header), $p);
    $t = (int) ($p['t'] ?? 0);
    if (!$t || abs(time() - $t) > 300) return false;
    $expected = hash_hmac('sha256', $t . '.' . $rawBody, $secret);
    return hash_equals($expected, (string) ($p['v1'] ?? ''));
}

$raw = file_get_contents('php://input');
if (!marius_signature_ok($raw, $_SERVER['HTTP_MARIUS_SIGNATURE'] ?? '', getenv('MARIUS_WEBHOOK_SECRET'))) {
    http_response_code(401);
    exit;
}
$event = json_decode($raw, true);

Python

import hashlib, hmac, time

def marius_signature_ok(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.strip().split("=", 1) for p in header.split(",") if "=" in p)
    t = int(parts.get("t", "0") or 0)
    if not t or abs(time.time() - t) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))
09

Zapier, Make, n8n

No need to write code to send your prospects to a CRM:

  • Zapier : "Webhooks by Zapier" trigger, "Catch Hook" event. Paste the address given by Zapier into My account, send a test, then add the action of your CRM (create a contact, a deal, etc.).
  • Make : "Webhooks", "Custom webhook" module. Same principle: Make's address in My account, a test so that Make discovers the fields.
  • n8n : "Webhook" node in POST method; the production address in My Account.

To read the API from these tools, use their HTTP module with the header Authorization: Bearer mr_live_…. Useful fields can be found in data.contact (name, email, telephone), data.summary.need (the need) and data.espace_url (link to the file).

10

Errors and limitations

An error returns an HTTP code and an object error : code is stable (to be tested in your program), message is a phrase in French. Each response has a header Marius-Request-Id : give it to support if necessary.

HTTP/1.1 404 Not Found
Marius-Request-Id: 1f0e2d3c-…

{
  "error": { "code": "not_found", "message": "Lead not found." },
  "request_id": "1f0e2d3c-…"
}
HTTPcodeCause
400invalid_parameter, invalid_jsonUnknown or invalid parameter, body that is not a JSON object.
401missing_key, invalid_key, key_expiredKey missing, malformed, unknown, deleted or expired.
403read_only_keyChange requested with a read-only key.
403not_liveThe site is no longer online.
404not_foundUnknown address, or item not found in your space.
405method_not_allowedMethod not accepted (the header Allow give the correct ones).
413payload_too_largeBody of more than 10,000 characters.
429rate_limitedMore than 60 calls in a minute with the same key: wait for header delay Retry-After.
500, 502server_error, unavailableError on our side: try again a little later. Another 5xx error, sometimes without a JSON body, comes from the network: same behavior.

Limits: 60 calls per minute per key, 10 keys and 5 webhooks per space. The last 15 calls and sendings are displayed in My Account.

11

Personal data

Prospects are personal data (name, email, telephone number, comments made). Marius processes them on your behalf, as a subcontractor. When you send them to another tool via the API or a webhook, this transfer and what this tool does with it are your responsibility: choose tools that respect the GDPR, only keep what you need, and delete a key as soon as it is no longer needed.

Marius keeps the API call log for 90 days and webhooks events for 30 days. A prospect deleted from your space (or at the end of its retention period) also disappears from the API and pending shipments.

12

Historical

Current version: v1. In the same version, we add fields and events, we never remove them and we never change their meaning: simply ignore the fields that you do not know. An incompatible change would be the subject of a v2, announced to the space owners.

October 15, 2026
First version: prospects (reading, status, notes), conversations, sites, figures of the month; webhooks lead.created, lead.qualified, lead.updated.
marius

The assistant that knows your site by heart - and brings you customers.

Try Marius on my site
ProductHow it worksWhat it bringsCustomisePricingFAQ
CustomersSign inRequest a demoAPI and webhooksSupport
Contactbonjour@marius.qd.jeWe reply within 48 h, Monday to FridayNantes, France
© 2026 Marius · A French company, based in Nantes
Legal noticePrivacyTerms of useTerms of sale