# VertexCRM public API — integrator guide

> How a customer system reads and writes CRM objects in **their** organization. Live: [https://www.vertexcrm.in/developers](https://www.vertexcrm.in/developers) · Raw markdown: [https://www.vertexcrm.in/developers.md](https://www.vertexcrm.in/developers.md) · LLM index: [https://www.vertexcrm.in/llms.txt](https://www.vertexcrm.in/llms.txt) · MCP: [https://www.vertexcrm.in/mcp](https://www.vertexcrm.in/mcp) · Explorer: [https://www.vertexcrm.in/docs](https://www.vertexcrm.in/docs) · OpenAPI: [https://www.vertexcrm.in/openapi.json](https://www.vertexcrm.in/openapi.json)

Last updated: 2026-08-23. This document matches the handlers in `internal/handler/api.go` and the routes in `internal/server/router.go`. If the code and this page disagree, the code wins — then update this file.

---

## 1. Base URL and version

```
https://www.vertexcrm.in/api/v1
```

Self-hosted: `{origin}/api/v1`. There is one version (`v1`). All resources are **organization-scoped**. A key or session never sees another tenant’s rows.

Machine-readable spec: `GET /docs/openapi.json` (no auth). Interactive explorer: [`/docs`](https://www.vertexcrm.in/docs) (Swagger UI + Redoc).

---

## 2. Authentication

Pick one. Do not send more than you need.

| Method | How | Typical use |
|--------|-----|-------------|
| **Organization API key** | `Authorization: Bearer vcr_…` | Server-to-server. **Preferred.** |
| **Session cookie** | Browser login cookie `octavertex_session` | Same-origin UI / scripts |
| **JWT Bearer** | `Authorization: Bearer <login JWT>` | Mobile / custom clients after login |

### 2.1 Create an API key

1. Sign in as an org admin.
2. **Setup → API Keys** (`/admin/api-keys`).
3. Name the key (e.g. `shop-sync-prod`) and create it.
4. Copy the plaintext **once**. VertexCRM stores only a hash.

The key is bound to the **current organization**. If the creating user is missing, the API still authenticates as `api_key:{prefix}` with that org in context.

Revoke immediately if leaked. Rotate by creating a new key, switching the client, then deleting the old one.

### 2.2 Organization context

| Client | Org resolution |
|--------|----------------|
| API key `vcr_…` | Org is the key’s organization. Optional `?org=slug` is ignored for tenancy. |
| Session / JWT | Use `?org={slug}` when the user belongs to more than one org (same as the HTML app). |

### 2.3 Permissions

`/api/v1` uses the same RBAC as the UI: `accounts|contacts|leads|opportunities|cases` × `list|read|create|update|delete`.

If the key’s user has no profile grants, calls return **403**. Grant a dedicated “API integration” profile with only the objects the integrator needs.

---

## 3. Conventions

### 3.1 Content type

- Request body: `Content-Type: application/json`
- Responses: JSON. Errors: `{ "error": "…" }`
- Dates: RFC 3339 timestamps. Display in the product is Asia/Kolkata; the API does not convert.

### 3.2 List pagination

Every list returns:

```json
{
  "data": [ /* objects */ ],
  "page": 1,
  "per_page": 20
}
```

- `page` — 1-based. Default `1`. Values &lt; 1 become `1`.
- `per_page` — **fixed at 20**. There is no `per_page` query parameter today.
- Walk pages until `data` is shorter than 20 or empty.

Common query params:

| Resource | Extra filters | Default sort |
|----------|---------------|--------------|
| Accounts | `sort`, `dir` | `name` asc |
| Contacts | `sort`, `dir` | `last_name` asc |
| Leads | `status`, `rating`, `sort`, `dir` | `created_at` desc |
| Opportunities | `stage`, `sort`, `dir` | `created_at` desc |
| Cases | `status` | (repo default) |

`dir` is `asc` or `desc`.

### 3.3 Identifiers

Integer `id` values. Always send `id` from a previous response. `organization_id` is set by the server; clients should not rely on writing it.

### 3.4 Updates

`PUT /{resource}/{id}` replaces the writable fields from the JSON body. Owner and created-by are preserved from the existing row. Send the full object you intend to keep (read-modify-write).

### 3.5 Deletes

`DELETE` returns `{ "ok": true }` with HTTP 200 (not 204).

### 3.6 Status codes

| Code | Meaning |
|------|---------|
| 200 / 201 | Success (create is 201) |
| 400 | Invalid JSON |
| 401 | Missing/invalid session, JWT, or key |
| 403 | Authenticated but profile lacks the action |
| 404 | Wrong id or wrong org |
| 500 | Server / database |

---

## 4. Resources

Picklists below are the values the UI seeds. The API accepts the string you send; stay on these values so reports and filters work.

### 4.1 Accounts — `/api/v1/accounts`

| Method | Path | Permission |
|--------|------|------------|
| GET | `/accounts` | list |
| GET | `/accounts/{id}` | read |
| POST | `/accounts` | create |
| PUT | `/accounts/{id}` | update |
| DELETE | `/accounts/{id}` | delete |

Writable fields: `name`, `website`, `phone`, `industry`, `annual_revenue`, `employees`, `address`, `city`, `state`, `postal_code`, `country`, `description`.

### 4.2 Contacts — `/api/v1/contacts`

Writable: `account_id`, `first_name`, `last_name`, `email`, `phone`, `title`, `department`, `timezone`, `linkedin_url`, `instagram_handle`, `facebook_url`, `description`.

`timezone` is IANA (e.g. `Asia/Kolkata`).

### 4.3 Leads — `/api/v1/leads`

Writable: `first_name`, `last_name`, `company`, `email`, `phone`, `status`, `rating`, `source`, `lead_score`, `timezone`, social URLs, `description`.

**Status:** `Open - Not Contacted` · `Working - Contacted` · `Closed - Converted` · `Closed - Not Converted`

**Rating:** `Hot` · `Warm` · `Cold`

**Source (UI list):** Website, WhatsApp, Phone, Email, LinkedIn, Instagram, Facebook, Referral, Meta Lead Ads, Other.

Creating a lead via the API does **not** auto-convert. Conversion is a product action (Convert button or AI `track_conversion`).

### 4.4 Opportunities — `/api/v1/opportunities`

Writable: `account_id`, `name`, `amount`, `stage`, `close_date`, `probability`, `description`.

**Stage:** Prospecting, Qualification, Needs Analysis, Value Proposition, Negotiation, Closed Won, Closed Lost.

`close_date` is a timestamp or null.

### 4.5 Cases — `/api/v1/cases`

Writable: `subject`, `status`, `priority`, `account_id`, `contact_id`, `queue_id`, `description`.

**Status:** New, Working, Escalated, Closed, On Hold. **Priority:** High, Medium, Low.

---

## 5. Worked examples

Replace `YOUR_KEY` and host.

### 5.1 Health: list accounts

```bash
curl -sS -H "Authorization: Bearer YOUR_KEY" \
  "https://www.vertexcrm.in/api/v1/accounts?page=1"
```

### 5.2 Create a lead from your website / POS

```bash
curl -sS -X POST \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "first_name": "Ananya",
    "last_name": "Shah",
    "company": "Northwind Agency",
    "email": "ananya@example.com",
    "phone": "+919876543210",
    "status": "Open - Not Contacted",
    "rating": "Warm",
    "source": "Website",
    "description": "Requested Growth plan demo"
  }' \
  "https://www.vertexcrm.in/api/v1/leads"
```

### 5.3 Create an account, then a contact

```bash
curl -sS -X POST -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Northwind Agency","city":"Pune","country":"India"}' \
  "https://www.vertexcrm.in/api/v1/accounts"

# use returned id
curl -sS -X POST -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"account_id":123,"first_name":"Ananya","last_name":"Shah","email":"ananya@example.com","timezone":"Asia/Kolkata"}' \
  "https://www.vertexcrm.in/api/v1/contacts"
```

### 5.4 Open a deal

```bash
curl -sS -X POST -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "account_id": 123,
    "name": "VertexCRM Growth — FY26",
    "amount": 249000,
    "stage": "Qualification",
    "probability": 40
  }' \
  "https://www.vertexcrm.in/api/v1/opportunities"
```

### 5.5 JavaScript (Node 18+)

```js
const BASE = "https://www.vertexcrm.in/api/v1";
const KEY = process.env.VERTEXCRM_API_KEY;

async function createLead(body) {
  const res = await fetch(`${BASE}/leads`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
  return res.json();
}
```

---

## 6. Partner and webhook APIs (not `/api/v1`)

These are **separate** contracts. They do not use `vcr_` keys unless noted.

### 6.1 Inbound leads — `POST /webhooks/inbound-leads`

For ads, newsletters, Zapier, or a form tool that should only create leads.

| Header | Required | Value |
|--------|----------|--------|
| `X-Inbound-Secret` or `Authorization: Bearer …` | Yes | Platform `INBOUND_LEAD_SECRET` |
| `X-Org-Slug` | Recommended | Target org slug. If omitted, the first org is used. |

```json
{
  "first_name": "Ravi",
  "last_name": "Mehta",
  "email": "ravi@example.com",
  "company": "Mehta & Co",
  "phone": "+919811112222",
  "source": "Google Ads",
  "campaign_name": "Brand search",
  "description": "Downloaded pricing PDF"
}
```

At least one of `first_name`, `last_name`, or `email` is required. Returns **501** if the secret is not configured on the instance.

### 6.2 Booking-forms WhatsApp — `POST /api/booking-forms/whatsapp`

Used by Cafe Orelo (and similar portals) to send a confirmation on the org’s existing AiSensy/Meta sender.

Auth: `Authorization: Bearer <BOOKING_FORMS_WHATSAPP_TOKEN>` (instance env, not `vcr_`).

```json
{ "to": "+9198XXXXXXXX", "message": "Your table is confirmed.", "name": "Guest", "template": "booking_confirm" }
```

`to` and `message` are required.

### 6.3 Commerce payment (Healthy-O-Me pattern)

`POST /webhooks/healthyome-payment` with `X-HealthyOme-CRM-Key`. Marks the matching lead Closed-Converted and opens a Closed Won opportunity. Customer-specific; do not reuse without a written integration.

### 6.4 Channel webhooks (Meta / AiSensy / Instagram)

Configured per org under Setup → Integrations. You do not call these from your app; Meta/AiSensy call VertexCRM.

---

## 7. Rate limits, idempotency, and safety

- There is **no published rate-limit header** today. Treat 60 requests/minute/key as a polite ceiling; back off on 5xx.
- Creates are **not idempotent**. Deduplicate on your side (email + source + day) before POST.
- Never log full `vcr_` keys. Never embed keys in browser JavaScript on a public site.
- Do not use the API to scrape another org. Keys cannot cross tenants.
- Webhook secrets (`INBOUND_LEAD_SECRET`, booking token) are **platform** secrets — request them in the integration questionnaire; they are not self-serve in the UI.

---

## 8. What is not in v1

Inbox messages, gallery files, products, campaigns, reports, users, and automation are **not** on `/api/v1` yet. Use the UI, or ask for a scoped partner endpoint (booking-forms pattern).

---

## 9. Support

- In-app: Setup → API Keys · OpenAPI link
- Human: [vertexcrm.in/contact](https://www.vertexcrm.in/contact) — attach the integration questionnaire (`marketing/gtm/QUESTIONNAIRES.md` § API)
- Security: same form, subject “Security”
