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/api

Authentication

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"
  }
}
Checking the signature (Node.js)
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

Request
curl https://api.vertexleatherco.com/api/leads \
  -H "Authorization: Bearer $VERTEX_API_KEY"
Response · 200
[
  {
    "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

NameTypeDescription
nameoptionalstringFull name.
emailoptionalstringEmail address.
phoneoptionalstringPhone number, ideally with its country code.
companyoptionalstringCompany.
titleoptionalstringJob title.
websiteoptionalstringWebsite.
addressoptionalstringAddress.
noteoptionalstringA first note, kept in the lead’s activity.
sourceoptionalstringWhere it came from. Default: manual.One of: manualcard_scanin_person
Request
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"}'
Response · 201
{
  "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

NameTypeDescription
idrequiredstringThe record’s id.
Request
curl https://api.vertexleatherco.com/api/leads/$ID \
  -H "Authorization: Bearer $VERTEX_API_KEY"
Response · 200
{
  "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

NameTypeDescription
kindoptionalstringOnly one kind of thing. Default all.One of: allconversationsnotesvisitstaskschanges
beforeoptionalstringISO date: things at or before it.
limitoptionalnumber1–100, default 40.
Request
curl https://api.vertexleatherco.com/api/leads/activity \
  -H "Authorization: Bearer $VERTEX_API_KEY"
Response · 200
{
  "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

NameTypeDescription
idrequiredstringThe record’s id.
Request
curl https://api.vertexleatherco.com/api/leads/$ID/timeline \
  -H "Authorization: Bearer $VERTEX_API_KEY"
Response · 200
{
  "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

NameTypeDescription
idrequiredstringThe record’s id.

Body

NameTypeDescription
stageIdoptionalstring | nullA stage from GET /leads/stages.
temperatureoptionalstringHow interested they are.One of: COLDWARMHOT
valueoptionalnumberExpected deal value, in whole currency units.
nameoptionalstring | nullFull name.
emailoptionalstring | nullEmail address.
phoneoptionalstring | nullPhone number.
companyoptionalstring | nullCompany.
customFieldsoptionalobjectYour own fields by field id (GET /leads/fields); null clears one.
Request
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}'
Response · 200
{
  "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

NameTypeDescription
idrequiredstringThe record’s id.

Body

NameTypeDescription
typerequiredstringWhat happened.One of: NOTECALLEMAILWHATSAPPMEETING
noteoptionalstringWhat was said or agreed (up to 2,000 characters).
meetingAtoptionaldate-timeFor a MEETING: when.
Request
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"}'
Response · 201
{
  "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

Request
curl https://api.vertexleatherco.com/api/leads/stages \
  -H "Authorization: Bearer $VERTEX_API_KEY"
Response · 200
[
  {
    "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

Request
curl https://api.vertexleatherco.com/api/leads/fields \
  -H "Authorization: Bearer $VERTEX_API_KEY"
Response · 200
[
  {
    "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

NameTypeDescription
leadIdoptionalstringOnly this lead’s tasks.
Request
curl https://api.vertexleatherco.com/api/tasks \
  -H "Authorization: Bearer $VERTEX_API_KEY"
Response · 200
[
  {
    "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

NameTypeDescription
titlerequiredstringWhat to do.
leadIdoptionalstringThe lead it is about.
notesoptionalstringDetails.
priorityoptionalstringDefault: MEDIUM.One of: LOWMEDIUMHIGH
dueDateoptionaldate-timeWhen it is due; a reminder goes out then.
Request
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"}'
Response · 201
{
  "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

NameTypeDescription
idrequiredstringThe record’s id.

Body

NameTypeDescription
titleoptionalstringWhat to do.
notesoptionalstringDetails.
priorityoptionalstringPriority.One of: LOWMEDIUMHIGH
dueDateoptionaldate-time | nullWhen it is due; null removes it.
completedoptionalbooleanDone or not.
Request
curl -X PATCH https://api.vertexleatherco.com/api/tasks/$ID \
  -H "Authorization: Bearer $VERTEX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"completed":true}'
Response · 200
{
  "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

NameTypeDescription
idrequiredstringThe record’s id.
Request
curl -X DELETE https://api.vertexleatherco.com/api/tasks/$ID \
  -H "Authorization: Bearer $VERTEX_API_KEY"
Response · 200
{
  "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

Request
curl https://api.vertexleatherco.com/api/cards \
  -H "Authorization: Bearer $VERTEX_API_KEY"
Response · 200
[
  {
    "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

NameTypeDescription
templateIdrequiredstringThe design, such as swiss-indigo.
fullNameoptionalstringThe name on the card.
titleoptionalstringJob title.
slugoptionalstringIts address (lowercase letters, numbers, hyphens); made from the name when left out.
ownerIdoptionalstringA member to own it. Default: whoever the credential acts as (see Authentication).
Request
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"}'
Response · 201
{
  "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

NameTypeDescription
statusoptionalstringOnly chips in this state.One of: UNASSIGNEDACTIVEDISABLED
batchIdoptionalstringOnly chips from this delivery batch.
assignedUserIdoptionalstringOnly chips given to this member.
Request
curl https://api.vertexleatherco.com/api/nfc/tags \
  -H "Authorization: Bearer $VERTEX_API_KEY"
Response · 200
[
  {
    "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

NameTypeDescription
idrequiredstringThe record’s id.
Request
curl https://api.vertexleatherco.com/api/nfc/tags/$ID \
  -H "Authorization: Bearer $VERTEX_API_KEY"
Response · 200
{
  "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

Request
curl https://api.vertexleatherco.com/api/webhooks \
  -H "Authorization: Bearer $VERTEX_API_KEY"
Response · 200
[
  {
    "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

NameTypeDescription
urlrequiredstringAn HTTPS address of yours.
eventsrequiredstring[]Events to send; see Webhook events.
descriptionoptionalstringWhat it is for.
Request
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"}'
Response · 201
{
  "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"
}