The same records the CRM shows, reachable with a key. Read leads, contacts, companies, deals and tasks; create leads; receive every event as a signed webhook. Built for Zapier, Make, Google Sheets and your own code.
Create a key under CRM → Integrations → Website & API. It is shown once. Send it as a bearer token on every request.
curl https://vendraone.com/api/api.php?r=me \
-H "Authorization: Bearer vk_live_..."
Keys are per company and carry a scope: read or read,write. Limit: 600 requests per 10 minutes per key. A revoked key stops working immediately.
One resource per call. Filters are optional and combine.
| Request | Returns |
|---|---|
GET ?r=leads | id, name, company, email, phone, status, source, value, owner, score, next, tags, created, updated |
GET ?r=contacts | id, first, last, title, email, phone, mobile, companyId, tags, created, updated |
GET ?r=companies | id, name, domain, industry, size, phone, email, address, city, country, vat, tags |
GET ?r=deals | id, name, pipeline, stage, value, companyId, contactId, owner, close, prob, wonAt, lostAt |
GET ?r=tasks | id, title, type, due, time, prio, status, assignee, linkType, linkId, doneAt |
GET ?r=export&entity=leads | The same list as CSV (semicolon separated, UTF-8 with BOM, opens correctly in Excel) |
Filters: q (free text), status, since (milliseconds since 1970, on the updated timestamp), limit (max 200), offset.
GET https://vendraone.com/api/api.php?r=leads&status=New&limit=50
{
"ok": true, "entity": "leads", "total": 12, "limit": 50, "offset": 0,
"items": [
{ "id": "l_8f2a", "name": "Anders Holm", "company": "Nordvest Solar A/S",
"email": "anders@nordvestsolar.dk", "phone": "+45 71 99 88 77",
"status": "New", "source": "Website", "value": 24000, "score": 72,
"created": 1757145600000, "updated": 1757145600000 }
]
}
A POST goes into the CRM inbox, exactly like the website form. The CRM checks it against existing phone numbers and email addresses and assigns an owner, so the answer is 202 queued, not 201 created.
POST https://vendraone.com/api/api.php?r=leads
Authorization: Bearer vk_live_...
Content-Type: application/json
{ "name": "Anders Holm", "email": "anders@nordvestsolar.dk", "phone": "+45 71 99 88 77",
"company": "Nordvest Solar A/S", "note": "Asked for a call about the CRM", "source": "Zapier" }
Paste an https URL under Integrations → Chat & alerts or connect Outgoing webhooks, Zapier or Make on your account page. Every CRM event is POSTed as JSON from the Vendra server, retried after 2 minutes, 15 minutes and 2 hours if your endpoint does not answer 2xx, and logged on the same screen.
POST https://example.com/vendra-webhook
X-Vendra-Event: deal.won
X-Vendra-Delivery: 8123
X-Vendra-Timestamp: 1757145600
X-Vendra-Signature: sha256=3f1c...
{ "event": "deal.won", "ts": 1757145600123, "company_id": 42,
"data": { "name": "CRM for 14 seats", "value": 41000, "company": "Nordvest Solar A/S" } }
Events: lead.created, deal.created, deal.stage, deal.won, ticket.created, invoice.paid, meeting.booked, and test from the Send test button.
The signature is an HMAC-SHA256 of timestamp + "." + rawBody with your signing secret (Integrations → Website & API). Reject anything older than five minutes.
// Node
const crypto = require("crypto");
function verify(req, rawBody, secret) {
const ts = req.headers["x-vendra-timestamp"];
const sig = (req.headers["x-vendra-signature"] || "").replace("sha256=", "");
const mine = crypto.createHmac("sha256", secret).update(ts + "." + rawBody).digest("hex");
return Math.abs(Date.now() / 1000 - ts) < 300 && crypto.timingSafeEqual(Buffer.from(mine), Buffer.from(sig));
}
// PHP
$raw = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_VENDRA_TIMESTAMP'] ?? '';
$sig = str_replace('sha256=', '', $_SERVER['HTTP_X_VENDRA_SIGNATURE'] ?? '');
$mine = hash_hmac('sha256', $ts . '.' . $raw, $secret);
$ok = abs(time() - (int)$ts) < 300 && hash_equals($mine, $sig);
Out: trigger Webhooks by Zapier → Catch Hook, copy the hook URL, paste it as your Zapier connection. In: action Webhooks by Zapier → POST to /api/api.php?r=leads with the Authorization header and a JSON body.
Out: a Webhooks → Custom webhook module gives you a URL to paste. In: an HTTP → Make a request module that POSTs to /api/api.php?r=leads.
Extensions → Apps Script, then a time-driven trigger on this function:
function pullVendraLeads() {
const res = UrlFetchApp.fetch("https://vendraone.com/api/api.php?r=export&entity=leads",
{ headers: { Authorization: "Bearer vk_live_..." } });
const rows = Utilities.parseCsv(res.getContentText("UTF-8"), ";");
const sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Leads") || SpreadsheetApp.getActiveSpreadsheet().insertSheet("Leads");
sh.clearContents();
sh.getRange(1, 1, rows.length, rows[0].length).setValues(rows);
}
The website form endpoint needs your site key (Integrations → Website & API) instead of an API key, because it is called from a browser. Post the fields key, name, email, phone, company, note, source to:
POST https://vendraone.com/api/public.php?a=web_lead
Content-Type: application/json
{ "key": "YOUR_SITE_KEY", "name": "…", "email": "…", "phone": "…", "company": "…", "note": "…", "source": "Website" }
Create a webhook for Order creation (Shopify: Settings → Notifications → Webhooks; WooCommerce: Settings → Advanced → Webhooks) pointing at:
https://vendraone.com/api/public.php?a=shop_order&key=YOUR_SITE_KEY
Orders land on the customer card with lifetime value; Shopify checkout webhooks show up as abandoned carts in the CRM inbox.
workspace_updated_at tells you when that was.ok:false and a sentence that says what to fix. 401 is the key, 402 the plan, 403 the scope, 422 the input, 429 the rate limit.