Skip to content

Leads API

The Leads API is representative of every entity endpoint in BottleCRM, same shape, same conventions. Once you know this one, Contacts / Accounts / Opportunities / Tasks all behave identically.

Base URL

https://<your-host>/api/leads/

All requests require a valid bearer token (see Authentication).

List leads

GET /api/leads/
Authorization: Bearer <token>

Response:

{
  "count": 142,
  "next": "https://…/api/leads/?page=2",
  "previous": null,
  "results": [
    {
      "id": "ab12-cd34-…",
      "title": "Acme renewal interest",
      "first_name": "Jane",
      "last_name": "Doe",
      "email": "jane@acme.com",
      "status": "qualified",
      "source": "website_form",
      "assigned_to": { "id": "…", "email": "rep@yours.com" },
      "custom_fields": { "industry": "saas" },
      "created_at": "2026-04-01T10:14:33Z"
    }
  ]
}

Filtering

GET /api/leads/?status=qualified&source=website_form
GET /api/leads/?search=acme
GET /api/leads/?assigned_to=<profile_id>
GET /api/leads/?cf_industry=saas               # custom field filter
GET /api/leads/?ordering=-created_at

Filterable fields: status, source, assigned_to, tags, created_at__gte, created_at__lte, plus any custom field marked filterable.

Create a lead

POST /api/leads/
Content-Type: application/json
Authorization: Bearer <token>

{
  "title": "Acme renewal interest",
  "first_name": "Jane",
  "last_name": "Doe",
  "email": "jane@acme.com",
  "phone": "+1 555 1234",
  "status": "new",
  "source": "website_form",
  "custom_fields": { "industry": "saas", "bant_score": 72 }
}

201 Created returns the full record. Server-derived fields (org, created_by, id, created_at) must not be in the request body; they are populated by the view.

Retrieve / update / delete

GET    /api/leads/<id>/
PATCH  /api/leads/<id>/      # partial update, only fields in the body change
PUT    /api/leads/<id>/      # full update
DELETE /api/leads/<id>/      # hard delete

The detail GET response also includes custom_field_definitions so the frontend can render the right form fields without a second round-trip.

Convert a lead

There is no POST /api/leads/<id>/convert/. Conversion is a status change on the lead itself:

PUT /api/leads/<id>/
{ "status": "converted", ... }

Setting status to converted runs the conversion server-side: it creates the Account, the Contact and, where there is an amount, the Opportunity, links them to the lead, and returns account_id, contact_id and opportunity_id in the response so you can follow them without a second call.

Bulk CSV upload

POST /api/leads/upload/
Content-Type: multipart/form-data

file=<leads.csv>

Response contains created, updated, and errors arrays, one entry per CSV row. The endpoint de-duplicates on email within the org.

Errors

Status Meaning
400 Validation failed. Body: { "errors": { "field": "reason" } }
401 Missing or invalid token
403 Authenticated but not authorized for this record/role
404 Record does not exist or belongs to another org
429 Rate limit exceeded

See Errors for the full structure.