API Reference Overview

This page summarizes the Public API v1 endpoints for leads. Every request uses the Authorization: Bearer <api_key> header and the following base URL:

https://konektor.id

The examples on this page show the fields most integrations use. For machine-readable references and importable artifacts, use the OpenAPI and Postman links at the end of this page.

Authentication and API Key Scopes

Official supported scopes:

ScopeAccess
leads.readGET /api/v1/leads, GET /api/v1/leads/{identifier}
leads.writePOST /api/v1/leads, PATCH /api/v1/leads/{identifier}, POST /api/v1/leads/upsert
leads.deleteDELETE /api/v1/leads/{identifier}
leads.bulkPOST /api/v1/leads/bulk-upsert and compatible access for upsert

The API rejects keys that are invalid, revoked, expired, missing the required scope, or sent from an IP outside the key allowlist.

Delete Scope Deprecation

The delete endpoint accepts the legacy leads.write scope only until September 30, 2026. After that date, delete requests must use leads.delete.

When the legacy fallback scope is used during the transition window, the backend sends x-konektor-deprecation-warning and Warning headers.

Public API v1 Lead Endpoints

MethodPathScopeDescription
GET/api/v1/leadsleads.readList leads with advanced filters and pagination
POST/api/v1/leadsleads.writeCreate a lead
GET/api/v1/leads/{identifier}leads.readGet lead detail
PATCH/api/v1/leads/{identifier}leads.writeUpdate a lead
DELETE/api/v1/leads/{identifier}leads.deleteSoft delete a lead
POST/api/v1/leads/upsertleads.write or leads.bulkUpsert a lead by matcher
POST/api/v1/leads/bulk-upsertleads.bulkBulk upsert up to 100 items

{identifier} Format

{identifier} can be any of the following:

  1. id
  2. uniqueCode
  3. externalRef
  4. phone

Examples:

  1. /api/v1/leads/550e8400-e29b-41d4-a716-446655440000
  2. /api/v1/leads/API_7F3A1B2C
  3. /api/v1/leads/CRM-1024
  4. /api/v1/leads/6281234567890

When you use a phone number, send a stable canonical format if possible. The backend still normalizes common phone formatting variations.

List Leads GET /api/v1/leads

This endpoint supports filtering, sorting, and cursor pagination. Cursor pagination stays stable with id as the tie-breaker.

Available query parameters:

QueryDescription
statusSingle status or CSV, for example new,qualified
prioritylow, medium, high, urgent
sourcewebsite, whatsapp, phone, email, referral, social, ads, event, other
adPlatformmeta, google, tiktok, linkedin, posthog, other
assignedToAssigned user UUID
createdFrom, createdTocreatedAt bounds in ISO 8601 format
updatedFrom, updatedToupdatedAt bounds in ISO 8601 format
sortBycreatedAt or updatedAt
sortOrderasc or desc
cursorKeyset pagination token
limitMaximum 100, default 50
pageLegacy mode. Ignored when cursor is present
searchSearch by name, email, phone, uniqueCode, or externalRef

Example request:

curl -X GET "https://konektor.id/api/v1/leads?status=new,qualified&limit=2&sortBy=createdAt&sortOrder=desc" \
  -H "Authorization: Bearer <api_key>"

Example response:

{
  "success": true,
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "uniqueCode": "API_7F3A1B2C",
      "externalRef": "CRM-1024",
      "firstName": "Budi",
      "lastName": "Santoso",
      "email": "budi@example.com",
      "phone": "6281234567890",
      "status": "new",
      "priority": "high",
      "source": "website",
      "adPlatform": "meta",
      "assignedTo": null,
      "estimatedValue": 3500000,
      "actualValue": null,
      "notes": "Product demo requested",
      "createdAt": "2026-03-06T02:15:30.000Z",
      "updatedAt": "2026-03-06T02:15:30.000Z",
      "deletedAt": null
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 2,
    "total": 24,
    "totalPages": 12,
    "cursor": null,
    "nextCursor": "<opaque-cursor-token>"
  }
}

When you use cursor mode, page and totalPages are null. Treat nextCursor as an opaque token and send it back unchanged in the next request.

Create Lead POST /api/v1/leads

The only required field is firstName. When these fields are omitted, the backend applies the following defaults: status = new, priority = medium, source = other, and uniqueCode is generated automatically.

Example request:

curl -X POST "https://konektor.id/api/v1/leads" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "firstName": "Budi",
    "lastName": "Santoso",
    "email": "budi@example.com",
    "phone": "6281234567890",
    "priority": "high",
    "source": "website",
    "adPlatform": "meta",
    "estimatedValue": 3500000,
    "externalRef": "CRM-1024",
    "notes": "Product demo requested"
  }'

Example response:

{
  "success": true,
  "message": "Lead created successfully",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "uniqueCode": "API_7F3A1B2C",
    "externalRef": "CRM-1024",
    "firstName": "Budi",
    "lastName": "Santoso",
    "email": "budi@example.com",
    "phone": "6281234567890",
    "status": "new",
    "priority": "high",
    "source": "website",
    "adPlatform": "meta",
    "estimatedValue": 3500000,
    "actualValue": null,
    "notes": "Product demo requested",
    "createdAt": "2026-03-06T02:15:30.000Z",
    "updatedAt": "2026-03-06T02:15:30.000Z",
    "deletedAt": null
  },
  "lead": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "uniqueCode": "API_7F3A1B2C",
    "externalRef": "CRM-1024",
    "firstName": "Budi",
    "lastName": "Santoso",
    "email": "budi@example.com",
    "phone": "6281234567890",
    "status": "new",
    "priority": "high",
    "source": "website",
    "adPlatform": "meta",
    "estimatedValue": 3500000,
    "actualValue": null,
    "notes": "Product demo requested",
    "createdAt": "2026-03-06T02:15:30.000Z",
    "updatedAt": "2026-03-06T02:15:30.000Z",
    "deletedAt": null
  }
}

Get Lead Detail GET /api/v1/leads/{identifier}

This endpoint returns one lead by id, uniqueCode, externalRef, or phone.

If the lead has custom fields from Lead Form or k_* query params, the detail response includes customFields and customFieldValues. Use customFields when you need the label and field type. Use customFieldValues when you only need a quick lookup by key.

For k_* query params, Konektor keeps the original key, such as k_budget. In customFieldValues, Konektor also adds an alias without the k_ prefix, such as budget, as long as that alias does not conflict with another custom field.

Current custom field limits:

  • Up to 10 custom fields per lead.
  • Keys are up to 40 characters, lowercase snake_case, and must start with a letter.
  • Text values from k_* query params are capped at 255 characters.
  • Values can be strings or booleans, depending on the field type.

Example request:

curl -X GET "https://konektor.id/api/v1/leads/CRM-1024" \
  -H "Authorization: Bearer <api_key>"

Example response:

{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "uniqueCode": "API_7F3A1B2C",
    "externalRef": "CRM-1024",
    "firstName": "Budi",
    "lastName": "Santoso",
    "email": "budi@example.com",
    "phone": "6281234567890",
    "status": "new",
    "priority": "high",
    "source": "website",
    "customFields": [
      {
        "key": "k_budget",
        "label": "Budget",
        "type": "text",
        "value": "50jt"
      }
    ],
    "customFieldValues": {
      "k_budget": "50jt",
      "budget": "50jt"
    },
    "createdAt": "2026-03-06T02:15:30.000Z",
    "updatedAt": "2026-03-06T02:15:30.000Z",
    "deletedAt": null
  }
}

Update Lead PATCH /api/v1/leads/{identifier}

Send only the fields you want to change. For nullable fields such as lastName, phone, externalRef, notes, estimatedValue, and actualValue, send null to clear the stored value.

This endpoint also accepts value as an alias of actualValue. The update response includes previousStatus and the value alias inside the returned lead object.

Example request:

curl -X PATCH "https://konektor.id/api/v1/leads/CRM-1024" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "qualified",
    "actualValue": 1500000,
    "notes": "Demo has been scheduled"
  }'

Example response:

{
  "success": true,
  "message": "Lead updated successfully",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "uniqueCode": "API_7F3A1B2C",
    "externalRef": "CRM-1024",
    "firstName": "Budi",
    "status": "qualified",
    "priority": "high",
    "source": "website",
    "actualValue": 1500000,
    "notes": "Demo has been scheduled",
    "previousStatus": "new",
    "value": 1500000,
    "createdAt": "2026-03-06T02:15:30.000Z",
    "updatedAt": "2026-03-06T03:10:00.000Z",
    "deletedAt": null
  },
  "lead": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "uniqueCode": "API_7F3A1B2C",
    "externalRef": "CRM-1024",
    "firstName": "Budi",
    "status": "qualified",
    "priority": "high",
    "source": "website",
    "actualValue": 1500000,
    "notes": "Demo has been scheduled",
    "previousStatus": "new",
    "value": 1500000,
    "createdAt": "2026-03-06T02:15:30.000Z",
    "updatedAt": "2026-03-06T03:10:00.000Z",
    "deletedAt": null
  }
}

If the payload is valid but does not change any stored value, the backend returns success: true, message: "No changes detected", and the latest lead object.

Upsert Lead POST /api/v1/leads/upsert

The upsert body must include match and lead. match.by only accepts id, uniqueCode, phone, or externalRef.

When a matching lead exists, the backend returns action: "updated". When no lead is found and createIfMissing is not set to false, the backend creates a new lead and returns action: "created".

When match.by is phone, uniqueCode, or externalRef, the backend uses match.value for creation if the same field is omitted from lead.

Example request:

curl -X POST "https://konektor.id/api/v1/leads/upsert" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "match": {
      "by": "phone",
      "value": "6281234567890"
    },
    "lead": {
      "firstName": "Budi",
      "status": "qualified",
      "priority": "high",
      "source": "whatsapp"
    },
    "createIfMissing": true
  }'

Example response:

{
  "success": true,
  "action": "updated",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "uniqueCode": "API_7F3A1B2C",
    "externalRef": "CRM-1024",
    "firstName": "Budi",
    "phone": "6281234567890",
    "status": "qualified",
    "priority": "high",
    "source": "whatsapp",
    "createdAt": "2026-03-06T02:15:30.000Z",
    "updatedAt": "2026-03-06T03:25:00.000Z",
    "deletedAt": null
  },
  "lead": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "uniqueCode": "API_7F3A1B2C",
    "externalRef": "CRM-1024",
    "firstName": "Budi",
    "phone": "6281234567890",
    "status": "qualified",
    "priority": "high",
    "source": "whatsapp",
    "createdAt": "2026-03-06T02:15:30.000Z",
    "updatedAt": "2026-03-06T03:25:00.000Z",
    "deletedAt": null
  }
}

Bulk Upsert POST /api/v1/leads/bulk-upsert

This endpoint accepts items[] with a maximum of 100 items. options.continueOnError defaults to true, so later items still run when an earlier item fails.

Example request:

curl -X POST "https://konektor.id/api/v1/leads/bulk-upsert" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      {
        "match": {
          "by": "externalRef",
          "value": "CRM-1024"
        },
        "lead": {
          "firstName": "Budi",
          "status": "qualified"
        }
      },
      {
        "match": {
          "by": "externalRef",
          "value": "CRM-2048"
        },
        "lead": {},
        "createIfMissing": false
      }
    ],
    "options": {
      "continueOnError": true
    }
  }'

Example response:

{
  "success": false,
  "processed": 2,
  "created": 0,
  "updated": 1,
  "failed": 1,
  "results": [
    {
      "index": 0,
      "match": {
        "by": "externalRef",
        "value": "CRM-1024"
      },
      "success": true,
      "action": "updated",
      "lead": {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "uniqueCode": "API_7F3A1B2C",
        "externalRef": "CRM-1024",
        "firstName": "Budi",
        "status": "qualified",
        "createdAt": "2026-03-06T02:15:30.000Z",
        "updatedAt": "2026-03-06T03:25:00.000Z",
        "deletedAt": null
      }
    },
    {
      "index": 1,
      "match": {
        "by": "externalRef",
        "value": "CRM-2048"
      },
      "success": false,
      "error": {
        "statusCode": 404,
        "statusMessage": "Lead not found and createIfMissing is false"
      }
    }
  ]
}

Delete Lead DELETE /api/v1/leads/{identifier}

This endpoint performs a soft delete. A successful response only returns the status and message.

Example request:

curl -X DELETE "https://konektor.id/api/v1/leads/CRM-1024" \
  -H "Authorization: Bearer <api_key>"

Example response:

{
  "success": true,
  "message": "Lead deleted successfully"
}

If the request still uses the legacy leads.write scope during the transition window, the successful response also includes the x-konektor-deprecation-warning header.

Response and Error Format

Backend response wrappers:

  1. List endpoints return success, data, and pagination.
  2. Create, update, and upsert return success, the lead object in data, and the lead alias.
  3. Detail returns success and data.
  4. Delete returns success and message.

Common error format:

{
  "statusCode": 404,
  "statusMessage": "Lead not found"
}

Other common errors:

{
  "statusCode": 400,
  "statusMessage": "Invalid cursor format"
}
{
  "statusCode": 409,
  "statusMessage": "Lead with this externalRef already exists",
  "data": {
    "field": "externalRef"
  }
}
{
  "statusCode": 429,
  "statusMessage": "Daily lead limit reached"
}

Validation errors can also include a data field with per-field validator details.

Data Security

Primary lead PII fields, such as name, email, and phone, are encrypted at rest. Phone lookup also considers normalized phone variants so common formatting differences can still match the same lead.

Current Limits and Quotas

  1. Create and upsert-create still enforce the workspace daily lead quota.
  2. bulk-upsert accepts at most 100 items per request.
  3. Transport or runtime platform limits can still apply outside the business quotas above.

Contract Artifacts

Public contract artifacts are available at:

  1. OpenAPI Specification
  2. Postman Collection

Need More Help?

Our team is ready to help you maximize ad tracking and business attribution.

Email Supportsupport@konektor.id
YouTubeYouTube

© 2026 Konektor. All rights reserved.