Vertex Connect API
Bring your leads into your own systems, add the ones you collect elsewhere, and hear about every new one the moment it arrives.
Getting started
The API speaks JSON over HTTPS. Every address below starts with the base URL shown here.
Create a key in Integrations → API keys (owners and admins), choose only the scopes you need, and send it with every request.
https://api.vertexleatherco.com/apiAuthentication
Send the key as a Bearer token in the Authorization header. A key (vxk_live_…) belongs to one workspace and acts as the person who made it, or the workspace owner once they have left. Anything it creates is theirs.
A personal access token (vxp_…) acts as you, with your role. Send X-Organization-Id to choose the workspace when you are in more than one.
A key can only reach the endpoints its scopes allow. A key or token is shown once when it is made; if one leaks, revoke it from the same page.
curl https://api.vertexleatherco.com/api/leads \
-H "Authorization: Bearer vxk_live_…"Scopes
crm:read- Read leads, stages, fields and tasks.
crm:write- Add and change leads, their activity, and tasks.
cards:read- Read cards.
cards:write- Create cards.
nfc:read- Read NFC chips.
integration:read- Read webhook endpoints.
integration:write- Add webhook endpoints.
Errors
A failed request answers with an HTTP status and a JSON body with statusCode and message. The message says what to fix.
- 400
- Something sent is missing or not valid.
- 401
- The key is missing, wrong, revoked or expired.
- 403
- The key lacks the scope, or the role, for this.
- 404
- No such record in this workspace.
- 429
- Too many requests; wait and try again.
{
"statusCode": 403,
"message": "This token is missing the required scope(s): crm:write"
}Rate limits
Up to 600 requests a minute from one address. Past that the API answers 429 until the minute is over; slow down and retry.
Webhooks
Add an endpoint (here or in Integrations → Webhooks) and we POST each event you choose to it as JSON. Answer with any 2xx status within 10 seconds. Anything else is retried up to 5 times: after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours.
Every delivery is signed. Check the signature before trusting the body, and use the event id to skip one you already handled (a retry has the same id).
Events
lead.created- A new lead: from a card, a meeting request, an import or by hand.
lead.updated- A lead’s stage, temperature, value or details changed, or duplicates were merged into it.
contact.saved- A visitor saved a card to their phone’s contacts.
card.viewed- A card was opened (by link, QR code or chip).
nfc.tapped- A chip was tapped.
meeting.requested- A visitor asked to meet.
quote.requested- A visitor asked for a quote.
member.added- Someone joined the workspace.
Each delivery carries these headers
X-Vertex-Signature: t=1791286435,v1=5f2c…
X-Vertex-Event: lead.created
X-Vertex-Event-Id: evt_5b1d0c3e-8f7a-4f53-9f0e-2a6b1c9d7e41
X-Vertex-Delivery: cm3k9x2dlv001{
"id": "evt_5b1d0c3e-8f7a-4f53-9f0e-2a6b1c9d7e41",
"event": "lead.created",
"createdAt": "2026-10-06T09:14:00.000Z",
"data": {
"leadId": "cm3k9x2lead01",
"name": "Mona Adel",
"email": "mona@nile.example",
"phone": "+201001234567",
"company": "Nile Co",
"source": "manual"
}
}import { createHmac, timingSafeEqual } from 'node:crypto';
// rawBody: the request body exactly as received (a string, not parsed JSON).
export function isFromVertex(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map((kv) => kv.split('=')));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; // older than 5 minutes
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const given = Buffer.from(parts.v1 ?? '');
return given.length === expected.length && timingSafeEqual(given, Buffer.from(expected));
}Reference
Leads
People who left their details on a card, were imported, or were added by hand.
List leads
GET/leads
Every lead the caller can see, newest first. A key sees the whole workspace; a personal token sees what its owner sees.
Scope: crm:read
curl https://api.vertexleatherco.com/api/leads \
-H "Authorization: Bearer $VERTEX_API_KEY"[
{
"id": "cm3k9x2lead01",
"name": "Mona Adel",
"email": "mona@nile.example",
"phone": "+201001234567",
"company": "Nile Co",
"score": 40,
"value": 25000,
"temperature": "HOT",
"source": "card",
"stageId": "cm3k9x2stage2",
"assignedTo": null,
"firstContactedAt": null,
"lastContactedAt": null,
"createdAt": "2026-10-06T09:14:00.000Z",
"customFields": {
"cm3k9x2field1": "Real estate"
},
"card": {
"slug": "omar-saeed"
}
}
]Add a lead
POST/leads
Needs at least a name, an email or a phone number.
Scope: crm:write
Body
| Name | Type | Description |
|---|---|---|
nameoptional | string | Full name. |
emailoptional | string | Email address. |
phoneoptional | string | Phone number, ideally with its country code. |
companyoptional | string | Company. |
titleoptional | string | Job title. |
websiteoptional | string | Website. |
addressoptional | string | Address. |
noteoptional | string | A first note, kept in the lead’s activity. |
sourceoptional | string | Where it came from. Default: manual.One of: manualcard_scanin_person |
curl -X POST https://api.vertexleatherco.com/api/leads \
-H "Authorization: Bearer $VERTEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Mona Adel","email":"mona@nile.example","phone":"+201001234567","company":"Nile Co","note":"Met at Cairo ICT"}'{
"id": "cm3k9x2lead01",
"name": "Mona Adel",
"email": "mona@nile.example",
"phone": "+201001234567",
"company": "Nile Co",
"score": 0,
"value": 0,
"temperature": "COLD",
"source": "manual",
"stageId": "cm3k9x2stage1",
"createdAt": "2026-10-06T09:14:00.000Z",
"card": null
}Get a lead
GET/leads/{id}
The lead with its activity: notes, calls, messages, stage changes and meetings, newest first.
Scope: crm:read
Path
| Name | Type | Description |
|---|---|---|
idrequired | string | The record’s id. |
curl https://api.vertexleatherco.com/api/leads/$ID \
-H "Authorization: Bearer $VERTEX_API_KEY"{
"id": "cm3k9x2lead01",
"name": "Mona Adel",
"email": "mona@nile.example",
"phone": "+201001234567",
"company": "Nile Co",
"score": 40,
"value": 25000,
"temperature": "HOT",
"source": "card",
"stageId": "cm3k9x2stage2",
"assignedTo": null,
"firstContactedAt": null,
"lastContactedAt": null,
"createdAt": "2026-10-06T09:14:00.000Z",
"customFields": {
"cm3k9x2field1": "Real estate"
},
"card": {
"slug": "omar-saeed"
},
"activities": [
{
"id": "cm3k9x2act001",
"type": "NOTE",
"metadata": {
"note": "Wants a demo next week",
"by": "cm3k9x2user01"
},
"createdAt": "2026-10-06T10:00:00.000Z",
"author": {
"id": "cm3k9x2user01",
"name": "Omar Saeed"
}
}
]
}Activity across leads
GET/leads/activity
What happened across your leads, newest first: conversations, notes, pipeline moves, new leads, leads coming back to a card, and tasks done. Pass the returned `next` as `before` for the next page; an item may repeat at the edge of two pages, so drop ids you have.
Scope: crm:read
Query
| Name | Type | Description |
|---|---|---|
kindoptional | string | Only one kind of thing. Default all.One of: allconversationsnotesvisitstaskschanges |
beforeoptional | string | ISO date: things at or before it. |
limitoptional | number | 1–100, default 40. |
curl https://api.vertexleatherco.com/api/leads/activity \
-H "Authorization: Bearer $VERTEX_API_KEY"{
"items": [
{
"kind": "returned",
"id": "visit-cm3k9x2evt009-cm3k9x2lead1",
"at": "2026-10-07T09:12:00.000Z",
"lead": {
"id": "cm3k9x2lead01",
"name": "Mona Adel",
"company": "Nile Co"
},
"card": "Omar Saeed"
},
{
"kind": "activity",
"id": "cm3k9x2act001",
"at": "2026-10-06T10:00:00.000Z",
"lead": {
"id": "cm3k9x2lead01",
"name": "Mona Adel",
"company": "Nile Co"
},
"type": "CALL",
"metadata": {
"note": "Wants a demo"
},
"author": {
"id": "cm3k9x2user01",
"name": "Omar Saeed",
"avatarUrl": null
}
},
{
"kind": "captured",
"id": "created-cm3k9x2lead01",
"at": "2026-10-06T09:14:00.000Z",
"lead": {
"id": "cm3k9x2lead01",
"name": "Mona Adel",
"company": "Nile Co"
},
"source": "card_form",
"card": "Omar Saeed"
}
],
"next": "2026-10-06T09:14:00.000Z"
}Get a lead’s timeline
GET/leads/{id}/timeline
Everything that happened with the lead, newest first: their visits to your cards (before and after they left their details, with what they tapped), activity, tasks and when they were captured. With a summary.
Scope: crm:read
Path
| Name | Type | Description |
|---|---|---|
idrequired | string | The record’s id. |
curl https://api.vertexleatherco.com/api/leads/$ID/timeline \
-H "Authorization: Bearer $VERTEX_API_KEY"{
"items": [
{
"kind": "visit",
"id": "visit-cm3k9x2evt009",
"at": "2026-10-07T09:12:00.000Z",
"endedAt": "2026-10-07T09:15:30.000Z",
"card": {
"id": "cm3k9x2card01",
"name": "Omar Saeed"
},
"returning": true,
"tapped": false,
"actions": [
{
"type": "CLICK",
"at": "2026-10-07T09:14:00.000Z",
"action": "WHATSAPP"
}
]
},
{
"kind": "activity",
"id": "cm3k9x2act001",
"at": "2026-10-06T10:00:00.000Z",
"type": "CALL",
"metadata": {
"note": "Wants a demo",
"by": "cm3k9x2user01"
},
"author": {
"id": "cm3k9x2user01",
"name": "Omar Saeed",
"avatarUrl": null
}
},
{
"kind": "task",
"id": "task-cm3k9x2task1",
"at": "2026-10-06T10:05:00.000Z",
"event": "created",
"title": "Send the offer",
"dueDate": "2026-10-08T09:00:00.000Z",
"completed": false,
"assignee": null
},
{
"kind": "created",
"id": "created-cm3k9x2lead01",
"at": "2026-10-06T09:14:00.000Z",
"source": "card_form",
"card": {
"id": "cm3k9x2card01",
"name": "Omar Saeed"
},
"tag": null
}
],
"summary": {
"visits": 3,
"returns": 1,
"firstVisitAt": "2026-10-05T18:01:00.000Z",
"lastVisitAt": "2026-10-07T09:12:00.000Z",
"contacts": 1,
"lastContactAt": "2026-10-06T10:00:00.000Z",
"notes": 0,
"openTasks": 1,
"overdueTasks": 0
}
}Update a lead
PATCH/leads/{id}
Send only what changes. An empty string or null clears a text field.
Scope: crm:write
Path
| Name | Type | Description |
|---|---|---|
idrequired | string | The record’s id. |
Body
| Name | Type | Description |
|---|---|---|
stageIdoptional | string | null | A stage from GET /leads/stages. |
temperatureoptional | string | How interested they are.One of: COLDWARMHOT |
valueoptional | number | Expected deal value, in whole currency units. |
nameoptional | string | null | Full name. |
emailoptional | string | null | Email address. |
phoneoptional | string | null | Phone number. |
companyoptional | string | null | Company. |
customFieldsoptional | object | Your own fields by field id (GET /leads/fields); null clears one. |
curl -X PATCH https://api.vertexleatherco.com/api/leads/$ID \
-H "Authorization: Bearer $VERTEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"stageId":"cm3k9x2stage3","temperature":"HOT","value":40000}'{
"id": "cm3k9x2lead01",
"stageId": "cm3k9x2stage3",
"temperature": "HOT",
"value": 40000,
"name": "Mona Adel",
"email": "mona@nile.example",
"phone": "+201001234567",
"company": "Nile Co",
"customFields": {
"cm3k9x2field1": "Real estate"
}
}Log activity on a lead
POST/leads/{id}/activities
A note, or a call, email, WhatsApp message or meeting that happened.
Scope: crm:write
Path
| Name | Type | Description |
|---|---|---|
idrequired | string | The record’s id. |
Body
| Name | Type | Description |
|---|---|---|
typerequired | string | What happened.One of: NOTECALLEMAILWHATSAPPMEETING |
noteoptional | string | What was said or agreed (up to 2,000 characters). |
meetingAtoptional | date-time | For a MEETING: when. |
curl -X POST https://api.vertexleatherco.com/api/leads/$ID/activities \
-H "Authorization: Bearer $VERTEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"CALL","note":"Called, sending the offer tomorrow"}'{
"id": "cm3k9x2act002",
"type": "CALL",
"metadata": {
"note": "Called, sending the offer tomorrow",
"meetingAt": null,
"manual": true,
"by": "cm3k9x2user01"
},
"createdAt": "2026-10-06T11:00:00.000Z"
}List pipeline stages
GET/leads/stages
Scope: crm:read
curl https://api.vertexleatherco.com/api/leads/stages \
-H "Authorization: Bearer $VERTEX_API_KEY"[
{
"id": "cm3k9x2stage1",
"name": "New",
"order": 0,
"color": "#6366f1"
},
{
"id": "cm3k9x2stage2",
"name": "Contacted",
"order": 1,
"color": "#0ea5e9"
}
]List your lead fields
GET/leads/fields
The workspace’s own fields, in order. Use their ids in customFields.
Scope: crm:read
curl https://api.vertexleatherco.com/api/leads/fields \
-H "Authorization: Bearer $VERTEX_API_KEY"[
{
"id": "cm3k9x2field1",
"label": "Industry",
"type": "SELECT",
"options": [
"Retail",
"Real estate"
],
"order": 0
}
]Tasks
Follow-ups with a due date, on a lead or on their own.
List tasks
GET/tasks
Open tasks first, by due date.
Scope: crm:read
Query
| Name | Type | Description |
|---|---|---|
leadIdoptional | string | Only this lead’s tasks. |
curl https://api.vertexleatherco.com/api/tasks \
-H "Authorization: Bearer $VERTEX_API_KEY"[
{
"id": "cm3k9x2task01",
"title": "Send the price list",
"notes": null,
"priority": "HIGH",
"dueDate": "2026-10-08T09:00:00.000Z",
"completed": false,
"completedAt": null,
"leadId": "cm3k9x2lead01",
"createdAt": "2026-10-06T09:20:00.000Z",
"lead": {
"id": "cm3k9x2lead01",
"name": "Mona Adel"
}
}
]Add a task
POST/tasks
Scope: crm:write
Body
| Name | Type | Description |
|---|---|---|
titlerequired | string | What to do. |
leadIdoptional | string | The lead it is about. |
notesoptional | string | Details. |
priorityoptional | string | Default: MEDIUM.One of: LOWMEDIUMHIGH |
dueDateoptional | date-time | When it is due; a reminder goes out then. |
curl -X POST https://api.vertexleatherco.com/api/tasks \
-H "Authorization: Bearer $VERTEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"Send the price list","leadId":"cm3k9x2lead01","priority":"HIGH","dueDate":"2026-10-08T09:00:00.000Z"}'{
"id": "cm3k9x2task01",
"title": "Send the price list",
"notes": null,
"priority": "HIGH",
"dueDate": "2026-10-08T09:00:00.000Z",
"completed": false,
"completedAt": null,
"leadId": "cm3k9x2lead01",
"createdAt": "2026-10-06T09:20:00.000Z",
"lead": {
"id": "cm3k9x2lead01",
"name": "Mona Adel"
}
}Update or complete a task
PATCH/tasks/{id}
Scope: crm:write
Path
| Name | Type | Description |
|---|---|---|
idrequired | string | The record’s id. |
Body
| Name | Type | Description |
|---|---|---|
titleoptional | string | What to do. |
notesoptional | string | Details. |
priorityoptional | string | Priority.One of: LOWMEDIUMHIGH |
dueDateoptional | date-time | null | When it is due; null removes it. |
completedoptional | boolean | Done or not. |
curl -X PATCH https://api.vertexleatherco.com/api/tasks/$ID \
-H "Authorization: Bearer $VERTEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"completed":true}'{
"id": "cm3k9x2task01",
"title": "Send the price list",
"notes": null,
"priority": "HIGH",
"dueDate": "2026-10-08T09:00:00.000Z",
"completed": true,
"completedAt": "2026-10-07T12:00:00.000Z",
"leadId": "cm3k9x2lead01",
"createdAt": "2026-10-06T09:20:00.000Z",
"lead": {
"id": "cm3k9x2lead01",
"name": "Mona Adel"
}
}Delete a task
DELETE/tasks/{id}
Scope: crm:write
Path
| Name | Type | Description |
|---|---|---|
idrequired | string | The record’s id. |
curl -X DELETE https://api.vertexleatherco.com/api/tasks/$ID \
-H "Authorization: Bearer $VERTEX_API_KEY"{
"id": "cm3k9x2task01",
"deleted": true
}Cards
The digital business cards in the workspace.
List cards
GET/cards
Each card with its buttons and sections. Its public page is /c/{slug}.
Scope: cards:read
curl https://api.vertexleatherco.com/api/cards \
-H "Authorization: Bearer $VERTEX_API_KEY"[
{
"id": "cm3k9x2card01",
"slug": "omar-saeed",
"ownerId": "cm3k9x2user01",
"templateId": "swiss-indigo",
"isPublished": true,
"vcardData": {
"fullName": "Omar Saeed",
"title": "Sales Manager",
"company": "Nile Co",
"phone": "+201001234567",
"email": "omar@nile.example"
},
"actions": [
{
"id": "cm3k9x2actn1",
"type": "WHATSAPP",
"label": "WhatsApp",
"value": "+201001234567",
"isActive": true,
"order": 0
}
],
"sections": [],
"createdAt": "2026-09-01T10:00:00.000Z"
}
]Create a card
POST/cards
Made as a draft; publish it from the app. Counts toward the plan’s card limit.
Scope: cards:write
Body
| Name | Type | Description |
|---|---|---|
templateIdrequired | string | The design, such as swiss-indigo. |
fullNameoptional | string | The name on the card. |
titleoptional | string | Job title. |
slugoptional | string | Its address (lowercase letters, numbers, hyphens); made from the name when left out. |
ownerIdoptional | string | A member to own it. Default: whoever the credential acts as (see Authentication). |
curl -X POST https://api.vertexleatherco.com/api/cards \
-H "Authorization: Bearer $VERTEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"templateId":"swiss-indigo","fullName":"Sara Nabil","title":"Account Manager"}'{
"id": "cm3k9x2card02",
"slug": "sara-nabil",
"templateId": "swiss-indigo",
"isPublished": false,
"vcardData": {
"fullName": "Sara Nabil",
"title": "Account Manager"
}
}NFC chips
The chips the workspace holds, and the card each opens.
List NFC chips
GET/nfc/tags
Scope: nfc:read
Query
| Name | Type | Description |
|---|---|---|
statusoptional | string | Only chips in this state.One of: UNASSIGNEDACTIVEDISABLED |
batchIdoptional | string | Only chips from this delivery batch. |
assignedUserIdoptional | string | Only chips given to this member. |
curl https://api.vertexleatherco.com/api/nfc/tags \
-H "Authorization: Bearer $VERTEX_API_KEY"[
{
"id": "cm3k9x2tag001",
"uid": "04A2B3C4D5E6F7",
"status": "ACTIVE",
"hardwareType": "CARD",
"cardId": "cm3k9x2card01",
"assignedUserId": "cm3k9x2user01",
"batchId": null,
"activationCount": 128,
"lastScanAt": "2026-10-05T16:02:00.000Z",
"createdAt": "2026-09-01T10:00:00.000Z",
"assignedUser": {
"id": "cm3k9x2user01",
"name": "Omar Saeed",
"email": "omar@nile.example"
}
}
]Get an NFC chip
GET/nfc/tags/{id}
Scope: nfc:read
Path
| Name | Type | Description |
|---|---|---|
idrequired | string | The record’s id. |
curl https://api.vertexleatherco.com/api/nfc/tags/$ID \
-H "Authorization: Bearer $VERTEX_API_KEY"{
"id": "cm3k9x2tag001",
"uid": "04A2B3C4D5E6F7",
"status": "ACTIVE",
"hardwareType": "CARD",
"cardId": "cm3k9x2card01",
"assignedUserId": "cm3k9x2user01",
"batchId": null,
"activationCount": 128,
"lastScanAt": "2026-10-05T16:02:00.000Z",
"createdAt": "2026-09-01T10:00:00.000Z"
}Webhooks
Addresses of yours that events are sent to as they happen.
List webhook endpoints
GET/webhooks
Scope: integration:readWith a personal token, for OWNER, ADMIN, MANAGER
curl https://api.vertexleatherco.com/api/webhooks \
-H "Authorization: Bearer $VERTEX_API_KEY"[
{
"id": "cm3k9x2hook01",
"url": "https://example.com/vertex",
"description": "CRM sync",
"secretHint": "whsec_…9f2c",
"events": [
"lead.created",
"lead.updated"
],
"enabled": true,
"createdAt": "2026-09-10T08:00:00.000Z",
"updatedAt": "2026-09-10T08:00:00.000Z"
}
]Add a webhook endpoint
POST/webhooks
The answer carries the signing secret, once. Keep it to check the signature on each delivery.
Scope: integration:writeWith a personal token, for OWNER, ADMIN
Body
| Name | Type | Description |
|---|---|---|
urlrequired | string | An HTTPS address of yours. |
eventsrequired | string[] | Events to send; see Webhook events. |
descriptionoptional | string | What it is for. |
curl -X POST https://api.vertexleatherco.com/api/webhooks \
-H "Authorization: Bearer $VERTEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/vertex","events":["lead.created"],"description":"CRM sync"}'{
"id": "cm3k9x2hook02",
"url": "https://example.com/vertex",
"description": "CRM sync",
"secretHint": "whsec_…3f9a",
"events": [
"lead.created"
],
"enabled": true,
"createdAt": "2026-10-06T09:00:00.000Z",
"updatedAt": "2026-10-06T09:00:00.000Z",
"secret": "whsec_5c1e…3f9a"
}