Get started

Requests and responses

JSON in, JSON out, over HTTPS. This page covers the conventions every endpoint shares: ids, times, money, partial updates, languages and request ids.

JSON

  • Send request bodies as one JSON object with Content-Type: application/json. Responses are application/json; charset=utf-8.
  • Bodies are limited to 1 MB (body_too_large).
  • Unknown fields are rejected, not ignored: a misspelled field answers 400 unknown_field with the field named in fields. Send only the fields an endpoint documents, never whole objects copied from a response.
  • A value of the wrong type (a string where a number is expected) answers 400 bad_json.
  • Creating returns 201 Created with the new object. Reading and changing return 200 OK. Deleting returns 200 OK with a small confirmation such as {"ok": true, "id": "ld_…"} or {"id": "wh_…", "deleted": true}.

Ids

Every id is <prefix>_<26 chars>: a short type prefix, an underscore and 26 lowercase letters and digits, for example ld_01j8z7c2d4f6g8h0j2k4m6n8p0. The prefix tells you what the id points to:

PrefixObjectPrefixObject
ld_Leadtk_Task
ct_Contactnt_Note
co_Companyinvoice_Invoice
pl_Pipelinepr_Product
st_Stagefd_Custom field
cv_Conversationmsg_Message
ch_Channelmb_Member of the workspace
wh_Webhookdlv_Webhook delivery
key_API keyws_Workspace
app_App (its OAuth client id)ins_App installation

The API still accepts the older long prefixes (lead_…, contact_…, company_…, task_…, hook_… and so on) everywhere it takes an id, so ids you stored before keep working. Responses always use the short form.

Ids are canonical: store and send ids. They are sortable by creation time and never reused. Treat them as opaque strings: store them as they are and compare them exactly.

Leads, contacts, companies and tasks also have a per-workspace number (shown as #1042). Their single-record routes accept it in place of the id, for example GET /v1/leads/1042. Numbers are a convenience for people; ids are canonical. See record numbers.

An id of the wrong type in a path answers 404 not_found or 400 invalid_id_type.

Record numbers

Leads, contacts, companies and tasks carry a number: an integer, unique per workspace, shown to people as #1042. It is assigned on create and never reused. A restored record keeps its number, and so does a record merged away.

GET, PATCH and DELETE /v1/{leads|contacts|companies|tasks}/{id}, and their sub-routes, accept the plain number in place of the id: GET /v1/leads/1042. The number is resolved only inside the authenticated workspace; an unknown number answers the same 404 as an unknown id.

Numbers are a convenience for people; ids are canonical. Webhooks, forms, portal and share links, and idempotency use ids only.

Times

Times are RFC 3339 strings with fractional seconds, for example "2026-09-27T09:41:12.482913Z". Use a real RFC 3339 parser: the number of fractional digits varies. Fields that can be empty are null, for example closed_at of an open lead. When you send a time (a task's due_at), include the offset.

Money

Amounts are integers in the smallest unit of their currency, next to a three-letter currency code. For Uzbek soʻm that unit is the tiyin: 1 soʻm is 100 tiyin, so 1,500,000 soʻm is sent and returned as 150000000.

json
{ "amount": 150000000, "currency": "UZS" }

This applies to a lead's amount, a product's price, and an invoice's amount, paid_amount and line price. Never send amounts as decimals or strings. A lead without a currency uses the workspace's currency.

Partial updates

PATCH changes only the fields you send; every other field keeps its value.

  • To clear a reference such as a lead's owner_id, contact_id or company_id, send it as null.
  • custom holds the values of custom fields, keyed by the field's key. In a PATCH, only the keys you send change.
  • Names that Kweko shows in several languages (pipelines, stages, loss reasons, field labels) are objects keyed by locale, for example {"uz-Latn": "Yangi", "ru": "Новая", "en": "New"}. When you write one, you can send a plain string: it is used for every language.

Deleting and restoring

Deleting a lead, contact or company moves it to the trash: it disappears from lists, reading it answers 410 gone with deleted_at, and POST /v1/{leads|contacts|companies}/{id}/restore brings it back.

Languages

Error messages and a few labels follow the Accept-Language header: English (en, the default), Russian (ru) or Uzbek (uz). Codes such as validation_failed and field names never change with the language, so branch on codes, not on messages.

Request ids

Every response has an X-Request-Id header, and every error body repeats it as request_id. Quote it when you contact support. You can send your own X-Request-Id (up to 64 letters, digits, -, _ or .) to trace a call across your systems; Kweko uses and returns it.

Retries and idempotency

The API has no general Idempotency-Key header yet.

  • GET requests are always safe to retry.
  • Sending a message accepts a client_id (up to 64 characters): a retry with the same client_id returns the message that was already queued instead of sending it twice. See Conversations.
  • For other writes, a retry after a timeout can create a duplicate. Look the record up first (for example by your own reference in source or a custom field) or accept the rare duplicate.
  • Retry 429 and 5xx answers with exponential backoff; for 429, wait at least Retry-After seconds.

Versioning

The version is in the path (/v1). Within v1, new fields, endpoints, event types and error codes can appear at any time; changes that would break an integration are meant for a new version. Write your code to ignore unknown fields and to handle unknown error codes by their HTTP status. Changes are listed in the changelog.