Reference
Negocios y Cotizaciones
The customer as a legal entity (D2): the thing a quote is actually addressed
to, with its RFC and its fiscal details. Not to be confused with the
CompanyProfile, which is the CRM owner's own branding.
external_ref is the bridge to your system — set it once and every later
call can find the company by your identifier instead of ours.
List companies
Lists the companies of the location the API token belongs to.
GET
/api/v1/companies
Query parameters
-
searchstringFree text over name, legal name and RFC.
Example:
ADPA -
external_refstringFind the one carrying your identifier.
Example:
verificamex-99 -
per_pageintegerRows per page. Default 50, maximum 100. See Lists, paging and filters.
Example:
50
curl --request GET \
--get "https://klozzo.com/api/v1/companies?search=ADPA&external_ref=verificamex-99&per_page=50" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"
const url = new URL(
"https://klozzo.com/api/v1/companies"
);
const params = {
"search": "ADPA",
"external_ref": "verificamex-99",
"per_page": "50",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://klozzo.com/api/v1/companies';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_TOKEN}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'query' => [
'search' => 'ADPA',
'external_ref' => 'verificamex-99',
'per_page' => '50',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json
url = 'https://klozzo.com/api/v1/companies'
params = {
'search': 'ADPA',
'external_ref': 'verificamex-99',
'per_page': '50',
}
headers = {
'Authorization': 'Bearer {YOUR_API_TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()
{
"data": [
{
"id": null,
"ulid": null,
"name": "Price Ltd",
"legal_name": null,
"tax_id": null,
"tax_regime": null,
"cfdi_use": null,
"address": null,
"email": "contacto@cronin.example",
"phone": null,
"website": null,
"industry": null,
"owner_id": null,
"external_ref": null,
"notes": null,
"created_at": null,
"updated_at": null
},
{
"id": null,
"ulid": null,
"name": "Hauck-Leuschke",
"legal_name": null,
"tax_id": null,
"tax_regime": null,
"cfdi_use": null,
"address": null,
"email": "contacto@baumbach.example",
"phone": null,
"website": null,
"industry": null,
"owner_id": null,
"external_ref": null,
"notes": null,
"created_at": null,
"updated_at": null
}
]
}
-
dataobject[]-
idstring -
ulidstring -
namestring -
legal_namestring -
tax_idstring -
tax_regimestring -
cfdi_usestring -
addressstring -
emailstring -
phonestring -
websitestring -
industrystring -
owner_idstring -
external_refstring -
notesstring -
created_atstring -
updated_atstring
-
Create a company
Creates a company in this location. A company is the fiscal counterpart of a deal: who the invoice is made out to. Create it when you are about to quote somebody who buys as a business, not for every contact — a person who buys as a person needs no company.
POST
/api/v1/companies
Put your own identifier in external_ref and you never have to store
ours. It is unique inside the location, so a retry that sends the same
one gets a 422 rather than a second row.
Body parameters
-
namestring requiredTrading name.
Example:
ADPA -
legal_namestringRazón social.
Example:
ADPA, S.A. de C.V. -
tax_idstringRFC. Unique inside the location.
Example:
ADP010101AB1 -
tax_regimestringSAT regime code.
Example:
601 -
cfdi_usestringSAT CFDI use code.
Example:
G03 -
emailstringExample:
contacto@example.com -
phonestringThe company switchboard. Free text: unlike a contact's phone this one is neither validated nor normalised, so send E.164 if you intend to dial it.
Example:
+525555555555 -
external_refstringYour identifier for this company. Unique inside the location.
Example:
verificamex-99
curl --request POST \
"https://klozzo.com/api/v1/companies" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"name\": \"ADPA\",
\"legal_name\": \"ADPA, S.A. de C.V.\",
\"tax_id\": \"ADP010101AB1\",
\"tax_regime\": \"601\",
\"cfdi_use\": \"G03\",
\"email\": \"contacto@example.com\",
\"phone\": \"+525555555555\",
\"external_ref\": \"verificamex-99\"
}"
const url = new URL(
"https://klozzo.com/api/v1/companies"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"name": "ADPA",
"legal_name": "ADPA, S.A. de C.V.",
"tax_id": "ADP010101AB1",
"tax_regime": "601",
"cfdi_use": "G03",
"email": "contacto@example.com",
"phone": "+525555555555",
"external_ref": "verificamex-99"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://klozzo.com/api/v1/companies';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_TOKEN}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'name' => 'ADPA',
'legal_name' => 'ADPA, S.A. de C.V.',
'tax_id' => 'ADP010101AB1',
'tax_regime' => '601',
'cfdi_use' => 'G03',
'email' => 'contacto@example.com',
'phone' => '+525555555555',
'external_ref' => 'verificamex-99',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json
url = 'https://klozzo.com/api/v1/companies'
payload = {
"name": "ADPA",
"legal_name": "ADPA, S.A. de C.V.",
"tax_id": "ADP010101AB1",
"tax_regime": "601",
"cfdi_use": "G03",
"email": "contacto@example.com",
"phone": "+525555555555",
"external_ref": "verificamex-99"
}
headers = {
'Authorization': 'Bearer {YOUR_API_TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('POST', url, headers=headers, json=payload)
response.json()
{
"data": {
"id": null,
"ulid": null,
"name": "Price Ltd",
"legal_name": null,
"tax_id": null,
"tax_regime": null,
"cfdi_use": null,
"address": null,
"email": "contacto@dare.example",
"phone": null,
"website": null,
"industry": null,
"owner_id": null,
"external_ref": null,
"notes": null,
"created_at": null,
"updated_at": null
}
}
-
dataobject-
idstring -
ulidstring -
namestring -
legal_namestring -
tax_idstring -
tax_regimestring -
cfdi_usestring -
addressstring -
emailstring -
phonestring -
websitestring -
industrystring -
owner_idstring -
external_refstring -
notesstring -
created_atstring -
updated_atstring
-
{
"message": "Falta el nombre.",
"errors": {
"name": [
"Falta el nombre."
]
}
}
Every error shares the same shape — message and errors.
See Errors.
{
"message": "El valor de tax id ya está en uso.",
"errors": {
"tax_id": [
"El valor de tax id ya está en uso."
]
}
}
Every error shares the same shape — message and errors.
See Errors.
{
"message": "El valor de external ref ya está en uso.",
"errors": {
"external_ref": [
"El valor de external ref ya está en uso."
]
}
}
Every error shares the same shape — message and errors.
See Errors.
Fetch a company
Returns one company by its ULID, with everything on file plus
contacts_count and deals_count, which is
usually what you actually wanted: whether this company is worth a call.
GET
/api/v1/companies/{ulid}
A company from another location answers 404, never 403 — telling an
outsider "that exists but is not yours" is telling them it exists.
Path parameters
-
ulidstring requiredCompany ULID (a numeric id still resolves).
Example:
01JZ8Z0ZP0000000000000000
curl --request GET \
--get "https://klozzo.com/api/v1/companies/01JZ8Z0ZP0000000000000000" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"
const url = new URL(
"https://klozzo.com/api/v1/companies/01JZ8Z0ZP0000000000000000"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://klozzo.com/api/v1/companies/01JZ8Z0ZP0000000000000000';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_TOKEN}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json
url = 'https://klozzo.com/api/v1/companies/01JZ8Z0ZP0000000000000000'
headers = {
'Authorization': 'Bearer {YOUR_API_TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()
{
"data": {
"id": null,
"ulid": null,
"name": "Price Ltd",
"legal_name": null,
"tax_id": null,
"tax_regime": null,
"cfdi_use": null,
"address": null,
"email": "contacto@hauck.example",
"phone": null,
"website": null,
"industry": null,
"owner_id": null,
"external_ref": null,
"notes": null,
"created_at": null,
"updated_at": null
}
}
-
dataobject-
idstring -
ulidstring -
namestring -
legal_namestring -
tax_idstring -
tax_regimestring -
cfdi_usestring -
addressstring -
emailstring -
phonestring -
websitestring -
industrystring -
owner_idstring -
external_refstring -
notesstring -
created_atstring -
updated_atstring
-
Update a company
Updates a company by its ULID, changing only the fields you send. Fields you leave out are left alone, so this is safe to call from a sync that only knows about the tax data.
PUT
/api/v1/companies/{ulid}
Use it to fill in fiscal details a seller could not collect at the time —
an RFC that arrives with the purchase order, a CFDI use the accountant
corrects. Changing name here does not rename anything else: deals and
quotes already issued keep the name they were issued with.
Path parameters
-
ulidstring requiredCompany ULID (a numeric id still resolves).
Example:
01JZ8Z0ZP0000000000000000
curl --request PUT \
"https://klozzo.com/api/v1/companies/01JZ8Z0ZP0000000000000000" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"
const url = new URL(
"https://klozzo.com/api/v1/companies/01JZ8Z0ZP0000000000000000"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "PUT",
headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://klozzo.com/api/v1/companies/01JZ8Z0ZP0000000000000000';
$response = $client->put(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_TOKEN}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json
url = 'https://klozzo.com/api/v1/companies/01JZ8Z0ZP0000000000000000'
headers = {
'Authorization': 'Bearer {YOUR_API_TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('PUT', url, headers=headers)
response.json()
{
"data": {
"id": null,
"ulid": null,
"name": "Price Ltd",
"legal_name": null,
"tax_id": null,
"tax_regime": null,
"cfdi_use": null,
"address": null,
"email": "contacto@leuschke.example",
"phone": null,
"website": null,
"industry": null,
"owner_id": null,
"external_ref": null,
"notes": null,
"created_at": null,
"updated_at": null
}
}
-
dataobject-
idstring -
ulidstring -
namestring -
legal_namestring -
tax_idstring -
tax_regimestring -
cfdi_usestring -
addressstring -
emailstring -
phonestring -
websitestring -
industrystring -
owner_idstring -
external_refstring -
notesstring -
created_atstring -
updated_atstring
-
List deals
Lists the deals of this location, newest first. Use it to reconcile:
ask for ?external_ref= to find the one carrying your identifier, or for
?status=open to see what is still in play before you close something on
your side.
GET
/api/v1/deals
What you see depends on the token. A token belonging to somebody who manages the location sees every deal; a seller's token sees only their own. That is not a filter you can turn off, so a partial list is not a bug — it is whose token you are using.
Query parameters
-
statusstringOne of
open,won,lost.Example:
open -
company_idintegerOnly this company's deals.
Example:
12 -
external_refstringFind the one carrying your identifier.
Example:
sub_88213 -
per_pageintegerRows per page. Default 50, maximum 100. See Lists, paging and filters.
Example:
50
curl --request GET \
--get "https://klozzo.com/api/v1/deals?status=open&company_id=12&external_ref=sub_88213&per_page=50" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"
const url = new URL(
"https://klozzo.com/api/v1/deals"
);
const params = {
"status": "open",
"company_id": "12",
"external_ref": "sub_88213",
"per_page": "50",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://klozzo.com/api/v1/deals';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_TOKEN}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'query' => [
'status' => 'open',
'company_id' => '12',
'external_ref' => 'sub_88213',
'per_page' => '50',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json
url = 'https://klozzo.com/api/v1/deals'
params = {
'status': 'open',
'company_id': '12',
'external_ref': 'sub_88213',
'per_page': '50',
}
headers = {
'Authorization': 'Bearer {YOUR_API_TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()
{
"data": [
{
"id": 1,
"ulid": "01KYDKV7N1RMWVK8H79M9H20V1",
"title": "ADPA — Paquete 1,100 firmas",
"description": null,
"status": "open",
"status_label": "Abierto",
"amount": {
"amount": 250140000,
"currency": "MXN",
"decimal": "25014.0000",
"formatted": "$25,014.00"
},
"paid": {
"amount": 0,
"currency": "MXN",
"decimal": "0.0000",
"formatted": "$0.00"
},
"balance": {
"amount": 250140000,
"currency": "MXN",
"decimal": "25014.0000",
"formatted": "$25,014.00"
},
"weighted_amount": {
"amount": 100056000,
"currency": "MXN",
"decimal": "10005.6000",
"formatted": "$10,005.60"
},
"currency": "MXN",
"pipeline_id": 1,
"pipeline_stage_id": 2,
"stage": {
"id": 2,
"name": "Cotización enviada",
"probability": 40,
"is_won": false,
"is_lost": false
},
"company_id": 1,
"primary_contact_id": null,
"owner_id": 26,
"expected_close_date": null,
"closed_at": null,
"lost_reason": null,
"source": null,
"attribution": null,
"external_ref": null,
"created_at": "2026-07-25T03:58:03+00:00",
"updated_at": "2026-07-25T03:58:03+00:00"
},
{
"id": 1,
"ulid": "01KYDKV7N1RMWVK8H79M9H20V1",
"title": "ADPA — Paquete 1,100 firmas",
"description": null,
"status": "open",
"status_label": "Abierto",
"amount": {
"amount": 250140000,
"currency": "MXN",
"decimal": "25014.0000",
"formatted": "$25,014.00"
},
"paid": {
"amount": 0,
"currency": "MXN",
"decimal": "0.0000",
"formatted": "$0.00"
},
"balance": {
"amount": 250140000,
"currency": "MXN",
"decimal": "25014.0000",
"formatted": "$25,014.00"
},
"weighted_amount": {
"amount": 100056000,
"currency": "MXN",
"decimal": "10005.6000",
"formatted": "$10,005.60"
},
"currency": "MXN",
"pipeline_id": 1,
"pipeline_stage_id": 2,
"stage": {
"id": 2,
"name": "Cotización enviada",
"probability": 40,
"is_won": false,
"is_lost": false
},
"company_id": 1,
"primary_contact_id": null,
"owner_id": 26,
"expected_close_date": null,
"closed_at": null,
"lost_reason": null,
"source": null,
"attribution": null,
"external_ref": null,
"created_at": "2026-07-25T03:58:03+00:00",
"updated_at": "2026-07-25T03:58:03+00:00"
}
]
}
-
dataobject[]-
idinteger -
ulidstring -
titlestring -
descriptionstring -
statusstring -
status_labelstring -
amountobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
paidobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
balanceobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
weighted_amountobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
currencystring -
pipeline_idinteger -
pipeline_stage_idinteger -
stageobject-
idinteger -
namestring -
probabilityinteger -
is_wonboolean -
is_lostboolean
-
-
company_idinteger -
primary_contact_idstring -
owner_idinteger -
expected_close_datestring -
closed_atstring -
lost_reasonstring -
sourcestring -
attributionstring -
external_refstring -
created_atstring -
updated_atstring
-
Create a deal
Creates a deal — an opportunity — in the location's default pipeline unless another is named. The attribution of its contact is frozen onto it at this moment and never recalculated (D17).
POST
/api/v1/deals
Body parameters
-
titlestring requiredWhat is being sold.
Example:
ADPA — Paquete 1,100 firmas -
descriptionstringMust not be greater than 5000 characters.
Example:
Et animi quos velit et fugiat. -
company_idintegerThe customer company.
Example:
12 -
primary_contact_idintegerThe person on the other end.
Example:
340 -
owner_idintegerThe agent responsible. Defaults to the token's user.
Example:
4 -
pipeline_idintegerDefaults to the location's default pipeline.
Example:
1 -
pipeline_stage_idstring -
expected_close_datestringExample:
2026-08-30 -
sourcestringWhere it came from.
Example:
verificamex -
external_refstringYour identifier for this deal. Unique inside the location.
Example:
sub_88213
curl --request POST \
"https://klozzo.com/api/v1/deals" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"title\": \"ADPA — Paquete 1,100 firmas\",
\"description\": \"Et animi quos velit et fugiat.\",
\"company_id\": 12,
\"primary_contact_id\": 340,
\"owner_id\": 4,
\"pipeline_id\": 1,
\"expected_close_date\": \"2026-08-30\",
\"source\": \"verificamex\",
\"external_ref\": \"sub_88213\"
}"
const url = new URL(
"https://klozzo.com/api/v1/deals"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"title": "ADPA — Paquete 1,100 firmas",
"description": "Et animi quos velit et fugiat.",
"company_id": 12,
"primary_contact_id": 340,
"owner_id": 4,
"pipeline_id": 1,
"expected_close_date": "2026-08-30",
"source": "verificamex",
"external_ref": "sub_88213"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://klozzo.com/api/v1/deals';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_TOKEN}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'title' => 'ADPA — Paquete 1,100 firmas',
'description' => 'Et animi quos velit et fugiat.',
'company_id' => 12,
'primary_contact_id' => 340,
'owner_id' => 4,
'pipeline_id' => 1,
'expected_close_date' => '2026-08-30',
'source' => 'verificamex',
'external_ref' => 'sub_88213',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json
url = 'https://klozzo.com/api/v1/deals'
payload = {
"title": "ADPA — Paquete 1,100 firmas",
"description": "Et animi quos velit et fugiat.",
"company_id": 12,
"primary_contact_id": 340,
"owner_id": 4,
"pipeline_id": 1,
"expected_close_date": "2026-08-30",
"source": "verificamex",
"external_ref": "sub_88213"
}
headers = {
'Authorization': 'Bearer {YOUR_API_TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('POST', url, headers=headers, json=payload)
response.json()
{
"data": {
"id": 1,
"ulid": "01KYDKV7N1RMWVK8H79M9H20V1",
"title": "ADPA — Paquete 1,100 firmas",
"description": null,
"status": "open",
"status_label": "Abierto",
"amount": {
"amount": 250140000,
"currency": "MXN",
"decimal": "25014.0000",
"formatted": "$25,014.00"
},
"paid": {
"amount": 0,
"currency": "MXN",
"decimal": "0.0000",
"formatted": "$0.00"
},
"balance": {
"amount": 250140000,
"currency": "MXN",
"decimal": "25014.0000",
"formatted": "$25,014.00"
},
"weighted_amount": {
"amount": 100056000,
"currency": "MXN",
"decimal": "10005.6000",
"formatted": "$10,005.60"
},
"currency": "MXN",
"pipeline_id": 1,
"pipeline_stage_id": 2,
"stage": {
"id": 2,
"name": "Cotización enviada",
"probability": 40,
"is_won": false,
"is_lost": false
},
"company_id": 1,
"primary_contact_id": null,
"owner_id": 26,
"expected_close_date": null,
"closed_at": null,
"lost_reason": null,
"source": null,
"attribution": null,
"external_ref": null,
"created_at": "2026-07-25T03:58:03+00:00",
"updated_at": "2026-07-25T03:58:03+00:00"
}
}
-
dataobject-
idinteger -
ulidstring -
titlestring -
descriptionstring -
statusstring -
status_labelstring -
amountobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
paidobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
balanceobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
weighted_amountobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
currencystring -
pipeline_idinteger -
pipeline_stage_idinteger -
stageobject-
idinteger -
namestring -
probabilityinteger -
is_wonboolean -
is_lostboolean
-
-
company_idinteger -
primary_contact_idstring -
owner_idinteger -
expected_close_datestring -
closed_atstring -
lost_reasonstring -
sourcestring -
attributionstring -
external_refstring -
created_atstring -
updated_atstring
-
Reconcile a deal
Finds the deal by your identifier and closes it. If you did not put an
external_ref on the deal, the company's is used instead — provided that
company has exactly one open deal, because guessing which of three deals
a payment belongs to is not a guess anybody should make.
POST
/api/v1/deals/reconcile
Body parameters
-
external_refstring requiredYour identifier for the deal, or for its company.
Example:
sub_88213 -
statusstringOne of
wonorlost. Defaults towon.Example:
won -
lost_reasonstringRequired when
statusislost.Example:
Se fue con la competencia -
accepted_quote_idsinteger[]Which live quotes the customer actually took (D26). Omitted means none of them did, which is a valid answer.
Example:
[15] -
amountstringRecord a payment of this size at the same time.
Example:
25013.94 -
paid_atstringWhen the money arrived. Defaults to now.
Example:
2026-08-02 -
referencestringBank or charge reference.
Example:
SPEI-8891 -
payment_external_refstringYour identifier for the payment.
Example:
charge_9912
curl --request POST \
"https://klozzo.com/api/v1/deals/reconcile" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Idempotency-Key: required A unique value per operation. A retry with the same key returns the first answer instead of doing the work again. Example: rec-2026-08-02-88213" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"external_ref\": \"sub_88213\",
\"status\": \"won\",
\"lost_reason\": \"Se fue con la competencia\",
\"accepted_quote_ids\": [
15
],
\"amount\": \"25013.94\",
\"paid_at\": \"2026-08-02\",
\"reference\": \"SPEI-8891\",
\"payment_external_ref\": \"charge_9912\"
}"
const url = new URL(
"https://klozzo.com/api/v1/deals/reconcile"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Idempotency-Key": "required A unique value per operation. A retry with the same key returns the first answer instead of doing the work again. Example: rec-2026-08-02-88213",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"external_ref": "sub_88213",
"status": "won",
"lost_reason": "Se fue con la competencia",
"accepted_quote_ids": [
15
],
"amount": "25013.94",
"paid_at": "2026-08-02",
"reference": "SPEI-8891",
"payment_external_ref": "charge_9912"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://klozzo.com/api/v1/deals/reconcile';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_TOKEN}',
'Idempotency-Key' => 'required A unique value per operation. A retry with the same key returns the first answer instead of doing the work again. Example: rec-2026-08-02-88213',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'external_ref' => 'sub_88213',
'status' => 'won',
'lost_reason' => 'Se fue con la competencia',
'accepted_quote_ids' => [15],
'amount' => '25013.94',
'paid_at' => '2026-08-02',
'reference' => 'SPEI-8891',
'payment_external_ref' => 'charge_9912',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json
url = 'https://klozzo.com/api/v1/deals/reconcile'
payload = {
"external_ref": "sub_88213",
"status": "won",
"lost_reason": "Se fue con la competencia",
"accepted_quote_ids": [
15
],
"amount": "25013.94",
"paid_at": "2026-08-02",
"reference": "SPEI-8891",
"payment_external_ref": "charge_9912"
}
headers = {
'Authorization': 'Bearer {YOUR_API_TOKEN}',
'Idempotency-Key': 'required A unique value per operation. A retry with the same key returns the first answer instead of doing the work again. Example: rec-2026-08-02-88213',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('POST', url, headers=headers, json=payload)
response.json()
{
"data": {
"status": "won"
},
"meta": {
"reconciled": true,
"payment_recorded": true
}
}
-
dataobject-
statusstring
-
-
metaobject-
reconciledboolean -
payment_recordedboolean
-
{
"message": "No hay ningún negocio con ese external_ref."
}
Every error shares the same shape — message and errors.
See Errors.
{
"message": "El negocio ya está cerrado…",
"data": {
"status": "won"
}
}
Every error shares the same shape — message and errors.
See Errors.
Link a deal to your identifier
Attaches your own identifier to a deal that already exists — for the ones
that predate the integration. One deal,
one external_ref, unique inside the location.
POST
/api/v1/deals/{deal_ulid}/external-ref
Path parameters
-
deal_ulidstring requiredDeal ULID.
Example:
01JZ8Z0ZP0000000000000000
Body parameters
-
external_refstring requiredYour identifier.
Example:
sub_88213
curl --request POST \
"https://klozzo.com/api/v1/deals/01JZ8Z0ZP0000000000000000/external-ref" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Idempotency-Key: required A unique value per operation. Example: link-88213" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"external_ref\": \"sub_88213\"
}"
const url = new URL(
"https://klozzo.com/api/v1/deals/01JZ8Z0ZP0000000000000000/external-ref"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Idempotency-Key": "required A unique value per operation. Example: link-88213",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"external_ref": "sub_88213"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://klozzo.com/api/v1/deals/01JZ8Z0ZP0000000000000000/external-ref';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_TOKEN}',
'Idempotency-Key' => 'required A unique value per operation. Example: link-88213',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'external_ref' => 'sub_88213',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json
url = 'https://klozzo.com/api/v1/deals/01JZ8Z0ZP0000000000000000/external-ref'
payload = {
"external_ref": "sub_88213"
}
headers = {
'Authorization': 'Bearer {YOUR_API_TOKEN}',
'Idempotency-Key': 'required A unique value per operation. Example: link-88213',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('POST', url, headers=headers, json=payload)
response.json()
{
"data": {
"id": 1,
"ulid": "01KYDKV7N1RMWVK8H79M9H20V1",
"title": "ADPA — Paquete 1,100 firmas",
"description": null,
"status": "open",
"status_label": "Abierto",
"amount": {
"amount": 250140000,
"currency": "MXN",
"decimal": "25014.0000",
"formatted": "$25,014.00"
},
"paid": {
"amount": 0,
"currency": "MXN",
"decimal": "0.0000",
"formatted": "$0.00"
},
"balance": {
"amount": 250140000,
"currency": "MXN",
"decimal": "25014.0000",
"formatted": "$25,014.00"
},
"weighted_amount": {
"amount": 100056000,
"currency": "MXN",
"decimal": "10005.6000",
"formatted": "$10,005.60"
},
"currency": "MXN",
"pipeline_id": 1,
"pipeline_stage_id": 2,
"stage": {
"id": 2,
"name": "Cotización enviada",
"probability": 40,
"is_won": false,
"is_lost": false
},
"company_id": 1,
"primary_contact_id": null,
"owner_id": 26,
"expected_close_date": null,
"closed_at": null,
"lost_reason": null,
"source": null,
"attribution": null,
"external_ref": null,
"created_at": "2026-07-25T03:58:03+00:00",
"updated_at": "2026-07-25T03:58:03+00:00"
}
}
-
dataobject-
idinteger -
ulidstring -
titlestring -
descriptionstring -
statusstring -
status_labelstring -
amountobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
paidobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
balanceobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
weighted_amountobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
currencystring -
pipeline_idinteger -
pipeline_stage_idinteger -
stageobject-
idinteger -
namestring -
probabilityinteger -
is_wonboolean -
is_lostboolean
-
-
company_idinteger -
primary_contact_idstring -
owner_idinteger -
expected_close_datestring -
closed_atstring -
lost_reasonstring -
sourcestring -
attributionstring -
external_refstring -
created_atstring -
updated_atstring
-
Fetch a deal
Returns one deal by its ULID, with its stage and its company. Read status before you act on
it: a deal a person here already closed is never flipped by the API, and
this is where you find out that happened.
GET
/api/v1/deals/{ulid}
Path parameters
-
ulidstring requiredDeal ULID (a numeric id still resolves).
Example:
01JZ8Z0ZP0000000000000000
curl --request GET \
--get "https://klozzo.com/api/v1/deals/01JZ8Z0ZP0000000000000000" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"
const url = new URL(
"https://klozzo.com/api/v1/deals/01JZ8Z0ZP0000000000000000"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://klozzo.com/api/v1/deals/01JZ8Z0ZP0000000000000000';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_TOKEN}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json
url = 'https://klozzo.com/api/v1/deals/01JZ8Z0ZP0000000000000000'
headers = {
'Authorization': 'Bearer {YOUR_API_TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()
{
"data": {
"id": 1,
"ulid": "01KYDKV7N1RMWVK8H79M9H20V1",
"title": "ADPA — Paquete 1,100 firmas",
"description": null,
"status": "open",
"status_label": "Abierto",
"amount": {
"amount": 250140000,
"currency": "MXN",
"decimal": "25014.0000",
"formatted": "$25,014.00"
},
"paid": {
"amount": 0,
"currency": "MXN",
"decimal": "0.0000",
"formatted": "$0.00"
},
"balance": {
"amount": 250140000,
"currency": "MXN",
"decimal": "25014.0000",
"formatted": "$25,014.00"
},
"weighted_amount": {
"amount": 100056000,
"currency": "MXN",
"decimal": "10005.6000",
"formatted": "$10,005.60"
},
"currency": "MXN",
"pipeline_id": 1,
"pipeline_stage_id": 2,
"stage": {
"id": 2,
"name": "Cotización enviada",
"probability": 40,
"is_won": false,
"is_lost": false
},
"company_id": 1,
"primary_contact_id": null,
"owner_id": 26,
"expected_close_date": null,
"closed_at": null,
"lost_reason": null,
"source": null,
"attribution": null,
"external_ref": null,
"created_at": "2026-07-25T03:58:03+00:00",
"updated_at": "2026-07-25T03:58:03+00:00"
}
}
-
dataobject-
idinteger -
ulidstring -
titlestring -
descriptionstring -
statusstring -
status_labelstring -
amountobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
paidobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
balanceobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
weighted_amountobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
currencystring -
pipeline_idinteger -
pipeline_stage_idinteger -
stageobject-
idinteger -
namestring -
probabilityinteger -
is_wonboolean -
is_lostboolean
-
-
company_idinteger -
primary_contact_idstring -
owner_idinteger -
expected_close_datestring -
closed_atstring -
lost_reasonstring -
sourcestring -
attributionstring -
external_refstring -
created_atstring -
updated_atstring
-
List quotes
Lists the quotes issued in this location, newest first. Filter by
deal_id to see the
options offered on one opportunity, or by folio to look up the code a
customer read out on the phone — with or without its prefix and dash,
because nobody dictates punctuation.
GET
/api/v1/quotes
A draft has no folio yet: the code is assigned when the quote is shared, which is also the moment its contents are frozen.
Query parameters
-
deal_idintegerOnly this deal's quotes.
Example:
7 -
statusstringOne of
draft,shared,viewed,accepted,rejected,withdrawn,expired,superseded.Example:
shared -
foliostringThe dictated code, with or without prefix or dash.
Example:
COT-7K3M9 -
per_pageintegerRows per page. Default 50, maximum 100. See Lists, paging and filters.
Example:
50
curl --request GET \
--get "https://klozzo.com/api/v1/quotes?deal_id=7&status=shared&folio=COT-7K3M9&per_page=50" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"
const url = new URL(
"https://klozzo.com/api/v1/quotes"
);
const params = {
"deal_id": "7",
"status": "shared",
"folio": "COT-7K3M9",
"per_page": "50",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://klozzo.com/api/v1/quotes';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_TOKEN}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'query' => [
'deal_id' => '7',
'status' => 'shared',
'folio' => 'COT-7K3M9',
'per_page' => '50',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json
url = 'https://klozzo.com/api/v1/quotes'
params = {
'deal_id': '7',
'status': 'shared',
'folio': 'COT-7K3M9',
'per_page': '50',
}
headers = {
'Authorization': 'Bearer {YOUR_API_TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()
{
"data": [
{
"id": 1,
"ulid": "01KYDKV7P4RVYJZZ6JNAJ521YR",
"deal_id": 15,
"company_id": null,
"contact_id": 6295,
"owner_id": 26,
"quote_template_id": null,
"folio": null,
"reference": "Borrador",
"version": 1,
"title": "Patito",
"intro": null,
"status": "draft",
"status_label": "Borrador",
"is_option": false,
"currency": "MXN",
"subtotal": {
"amount": 0,
"currency": "MXN",
"decimal": "0.0000",
"formatted": "$0.00"
},
"discount_total": {
"amount": 0,
"currency": "MXN",
"decimal": "0.0000",
"formatted": "$0.00"
},
"tax_total": {
"amount": 0,
"currency": "MXN",
"decimal": "0.0000",
"formatted": "$0.00"
},
"total": {
"amount": 0,
"currency": "MXN",
"decimal": "0.0000",
"formatted": "$0.00"
},
"valid_until": "2026-08-09",
"shared_at": null,
"decided_at": null,
"snapshot_hash": null,
"external_ref": null,
"pdf_url": "http://klozzo.com/api/v1/quotes/01KYDKV7P4RVYJZZ6JNAJ521YR/pdf/signed?893456000&signature=a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
"created_at": "2026-07-25T18:11:14+00:00",
"updated_at": "2026-07-25T18:11:14+00:00"
},
{
"id": 1,
"ulid": "01KYDKV7P4RVYJZZ6JNAJ521YR",
"deal_id": 15,
"company_id": null,
"contact_id": 6295,
"owner_id": 26,
"quote_template_id": null,
"folio": null,
"reference": "Borrador",
"version": 1,
"title": "Patito",
"intro": null,
"status": "draft",
"status_label": "Borrador",
"is_option": false,
"currency": "MXN",
"subtotal": {
"amount": 0,
"currency": "MXN",
"decimal": "0.0000",
"formatted": "$0.00"
},
"discount_total": {
"amount": 0,
"currency": "MXN",
"decimal": "0.0000",
"formatted": "$0.00"
},
"tax_total": {
"amount": 0,
"currency": "MXN",
"decimal": "0.0000",
"formatted": "$0.00"
},
"total": {
"amount": 0,
"currency": "MXN",
"decimal": "0.0000",
"formatted": "$0.00"
},
"valid_until": "2026-08-09",
"shared_at": null,
"decided_at": null,
"snapshot_hash": null,
"external_ref": null,
"pdf_url": "http://klozzo.com/api/v1/quotes/01KYDKV7P4RVYJZZ6JNAJ521YR/pdf/signed?893456000&signature=a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
"created_at": "2026-07-25T18:11:14+00:00",
"updated_at": "2026-07-25T18:11:14+00:00"
}
]
}
-
dataobject[]-
idinteger -
ulidstring -
deal_idinteger -
company_idstring -
contact_idinteger -
owner_idinteger -
quote_template_idstring -
foliostring -
referencestring -
versioninteger -
titlestring -
introstring -
statusstring -
status_labelstring -
is_optionboolean -
currencystring -
subtotalobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
discount_totalobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
tax_totalobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
totalobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
valid_untilstring -
shared_atstring -
decided_atstring -
snapshot_hashstring -
external_refstring -
pdf_urlstring -
created_atstring -
updated_atstring
-
Create a quote
Creates a quote as a draft on an existing deal, with its lines. A draft burns no folio and is not visible to the customer: sharing it is a separate, deliberate act that freezes the document (D24).
POST
/api/v1/quotes
Body parameters
-
deal_idinteger requiredThe deal this offer belongs to.
Example:
7 -
titlestring requiredExample:
Firmado Electrónico -
introstringOpening paragraph, may carry
{{quote.*}}variables. -
valid_untilstringCommercial validity. Defaults to the location's setting.
Example:
2026-08-14 -
is_optionbooleanMark it when the customer will choose between several (D25).
Example:
false -
company_idintegerExample:
12 -
contact_idintegerExample:
340 -
quote_template_idintegerThe design to print with.
Example:
3 -
itemsobject[] requiredThe lines. Totals are derived from them.
-
namestring requiredExample:
Costo de las firmas por volumen -
quantitystring requiredExample:
1100 -
unit_pricestring requiredUp to four decimals.
Example:
19.6034 -
unit_labelstringExample:
firma -
tax_rate_idintegerA tax rate of this location.
Example:
1 -
discount_percentstringExample:
0 -
is_informationalbooleanShows a price and adds nothing.
Example:
false -
attributesobjectThe service's own columns, e.g.
{"tokens_por_validacion": 3}.
-
curl --request POST \
"https://klozzo.com/api/v1/quotes" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"deal_id\": 7,
\"title\": \"Firmado Electrónico\",
\"valid_until\": \"2026-08-14\",
\"is_option\": false,
\"company_id\": 12,
\"contact_id\": 340,
\"quote_template_id\": 3,
\"items\": [
{
\"name\": \"Costo de las firmas por volumen\",
\"quantity\": \"1100\",
\"unit_price\": \"19.6034\",
\"unit_label\": \"firma\",
\"tax_rate_id\": 1,
\"discount_percent\": \"0\",
\"is_informational\": false
}
]
}"
const url = new URL(
"https://klozzo.com/api/v1/quotes"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"deal_id": 7,
"title": "Firmado Electrónico",
"valid_until": "2026-08-14",
"is_option": false,
"company_id": 12,
"contact_id": 340,
"quote_template_id": 3,
"items": [
{
"name": "Costo de las firmas por volumen",
"quantity": "1100",
"unit_price": "19.6034",
"unit_label": "firma",
"tax_rate_id": 1,
"discount_percent": "0",
"is_informational": false
}
]
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://klozzo.com/api/v1/quotes';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_TOKEN}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'deal_id' => 7,
'title' => 'Firmado Electrónico',
'valid_until' => '2026-08-14',
'is_option' => false,
'company_id' => 12,
'contact_id' => 340,
'quote_template_id' => 3,
'items' => [
['name' => 'Costo de las firmas por volumen', 'quantity' => '1100', 'unit_price' => '19.6034', 'unit_label' => 'firma', 'tax_rate_id' => 1, 'discount_percent' => '0', 'is_informational' => false],
],
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json
url = 'https://klozzo.com/api/v1/quotes'
payload = {
"deal_id": 7,
"title": "Firmado Electrónico",
"valid_until": "2026-08-14",
"is_option": false,
"company_id": 12,
"contact_id": 340,
"quote_template_id": 3,
"items": [
{
"name": "Costo de las firmas por volumen",
"quantity": "1100",
"unit_price": "19.6034",
"unit_label": "firma",
"tax_rate_id": 1,
"discount_percent": "0",
"is_informational": false
}
]
}
headers = {
'Authorization': 'Bearer {YOUR_API_TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('POST', url, headers=headers, json=payload)
response.json()
{
"data": {
"id": 1,
"ulid": "01KYDKV7P4RVYJZZ6JNAJ521YR",
"deal_id": 15,
"company_id": null,
"contact_id": 6295,
"owner_id": 26,
"quote_template_id": null,
"folio": null,
"reference": "Borrador",
"version": 1,
"title": "Patito",
"intro": null,
"status": "draft",
"status_label": "Borrador",
"is_option": false,
"currency": "MXN",
"subtotal": {
"amount": 0,
"currency": "MXN",
"decimal": "0.0000",
"formatted": "$0.00"
},
"discount_total": {
"amount": 0,
"currency": "MXN",
"decimal": "0.0000",
"formatted": "$0.00"
},
"tax_total": {
"amount": 0,
"currency": "MXN",
"decimal": "0.0000",
"formatted": "$0.00"
},
"total": {
"amount": 0,
"currency": "MXN",
"decimal": "0.0000",
"formatted": "$0.00"
},
"valid_until": "2026-08-09",
"shared_at": null,
"decided_at": null,
"snapshot_hash": null,
"external_ref": null,
"pdf_url": "http://klozzo.com/api/v1/quotes/01KYDKV7P4RVYJZZ6JNAJ521YR/pdf/signed?893456000&signature=a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
"created_at": "2026-07-25T18:11:14+00:00",
"updated_at": "2026-07-25T18:11:14+00:00"
}
}
-
dataobject-
idinteger -
ulidstring -
deal_idinteger -
company_idstring -
contact_idinteger -
owner_idinteger -
quote_template_idstring -
foliostring -
referencestring -
versioninteger -
titlestring -
introstring -
statusstring -
status_labelstring -
is_optionboolean -
currencystring -
subtotalobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
discount_totalobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
tax_totalobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
totalobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
valid_untilstring -
shared_atstring -
decided_atstring -
snapshot_hashstring -
external_refstring -
pdf_urlstring -
created_atstring -
updated_atstring
-
Fetch a quote
Returns one quote by its ULID, with its line items and its totals. Money always comes back as
an object — amount is an integer at scale 10⁴, decimal is the exact
string, formatted is for display. Never parse formatted.
GET
/api/v1/quotes/{ulid}
pdf_url is a short-lived signed link you can put straight into your own
interface without proxying a token through a browser. It is not the
customer's link: that one carries the commercial validity and the read
tracking behind it.
Path parameters
-
ulidstring requiredQuote ULID (a numeric id still resolves).
Example:
01JZ8Z0ZP0000000000000000
curl --request GET \
--get "https://klozzo.com/api/v1/quotes/01JZ8Z0ZP0000000000000000" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"
const url = new URL(
"https://klozzo.com/api/v1/quotes/01JZ8Z0ZP0000000000000000"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://klozzo.com/api/v1/quotes/01JZ8Z0ZP0000000000000000';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_TOKEN}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json
url = 'https://klozzo.com/api/v1/quotes/01JZ8Z0ZP0000000000000000'
headers = {
'Authorization': 'Bearer {YOUR_API_TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()
{
"data": {
"id": 1,
"ulid": "01KYDKV7P4RVYJZZ6JNAJ521YR",
"deal_id": 15,
"company_id": null,
"contact_id": 6295,
"owner_id": 26,
"quote_template_id": null,
"folio": null,
"reference": "Borrador",
"version": 1,
"title": "Patito",
"intro": null,
"status": "draft",
"status_label": "Borrador",
"is_option": false,
"currency": "MXN",
"subtotal": {
"amount": 0,
"currency": "MXN",
"decimal": "0.0000",
"formatted": "$0.00"
},
"discount_total": {
"amount": 0,
"currency": "MXN",
"decimal": "0.0000",
"formatted": "$0.00"
},
"tax_total": {
"amount": 0,
"currency": "MXN",
"decimal": "0.0000",
"formatted": "$0.00"
},
"total": {
"amount": 0,
"currency": "MXN",
"decimal": "0.0000",
"formatted": "$0.00"
},
"valid_until": "2026-08-09",
"shared_at": null,
"decided_at": null,
"snapshot_hash": null,
"external_ref": null,
"pdf_url": "http://klozzo.com/api/v1/quotes/01KYDKV7P4RVYJZZ6JNAJ521YR/pdf/signed?893456000&signature=a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
"created_at": "2026-07-25T18:11:14+00:00",
"updated_at": "2026-07-25T18:11:14+00:00"
}
}
-
dataobject-
idinteger -
ulidstring -
deal_idinteger -
company_idstring -
contact_idinteger -
owner_idinteger -
quote_template_idstring -
foliostring -
referencestring -
versioninteger -
titlestring -
introstring -
statusstring -
status_labelstring -
is_optionboolean -
currencystring -
subtotalobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
discount_totalobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
tax_totalobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
totalobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
valid_untilstring -
shared_atstring -
decided_atstring -
snapshot_hashstring -
external_refstring -
pdf_urlstring -
created_atstring -
updated_atstring
-
Download the quote PDF
Returns the PDF of one quote, rendered from the frozen snapshot (D8), so
the same quote produces the
same bytes in a year's time. Reachable two ways: with an API token, or
through the short-lived signed pdf_url the quote resource hands out —
which is what lets your own UI show the document without proxying a token
through somebody's browser.
GET
/api/v1/quotes/{quote_ulid}/pdf
This is not the customer's link. That one is a DocumentLink with the
commercial validity, the branded viewer and read tracking behind it (D13).
Path parameters
-
quote_ulidstring requiredQuote ULID.
Example:
01JZ8Z0ZP0000000000000000
curl --request GET \
--get "https://klozzo.com/api/v1/quotes/01JZ8Z0ZP0000000000000000/pdf" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"
const url = new URL(
"https://klozzo.com/api/v1/quotes/01JZ8Z0ZP0000000000000000/pdf"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://klozzo.com/api/v1/quotes/01JZ8Z0ZP0000000000000000/pdf';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_TOKEN}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json
url = 'https://klozzo.com/api/v1/quotes/01JZ8Z0ZP0000000000000000/pdf'
headers = {
'Authorization': 'Bearer {YOUR_API_TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers)
response.json()
"%PDF-1.7 …"
Record a sale
Records a sale for a purchase that happened entirely on your side, with
nobody here
opening a deal for it — a self-service checkout, a renewal that charged
itself. Send the money and the customer; the CRM works out what it means
and tells you which rule fired in meta.matched_by.
POST
/api/v1/sales
It may attach the sale to an open deal the customer already had, or open
one already won and with no owner when there is none — because
nobody here attended this customer, and giving the commission to whoever
happens to be listed would be worse than leaving it unclaimed. When that
happens meta.claimable is true and a seller claims it from the CRM.
Two open deals for the same customer never resolve to a guess: a new deal is opened and the ambiguity is left for a person.
Use POST /api/v1/deals/reconcile instead when the deal already exists
and you know which one, and POST /api/v1/payments for a further
instalment of a sale already recorded here.
Body parameters
-
external_refstring requiredYour identifier for THIS sale. Recording it twice is impossible.
Example:
sale_88213 -
amountstring requiredWhat was paid. Up to four decimals, separators tolerated.
Example:
1499.00 -
currencystringISO 4217. Defaults to the location's.
Example:
MXN -
paid_atstringWhen the money arrived. Defaults to now.
Example:
2026-08-23T10:00:00Z -
methodstringOne of
transferencia,efectivo,tarjeta,otro. Defaults totarjeta.Example:
tarjeta -
referencestringBank or charge reference.
Example:
ch_9912 -
titlestringTitle for the deal if one has to be created.
Example:
Plan Pro anual -
deal_external_refstringPoint the sale at one specific deal. Answers 404 if that deal is not here.
Example:
sub_44 -
customerobjectWho bought. Required unless
deal_external_refidentifies the deal.-
emailstringExample:
ada@example.com -
phonestringExample:
+52 55 1234 5678 -
phone_countrystringMust not be greater than 2 characters.
Example:
kh -
first_namestringExample:
Ada -
last_namestringExample:
Lovelace -
company_namestringExample:
ADPA -
company_external_refstringYour identifier for the company.
Example:
verificamex-99
-
-
planobjectHow long what was bought lasts. Omit it for a one-off purchase.
-
namestringExample:
Plan Pro -
product_skustringMatches a product in the catalogue.
Example:
PLAN-PRO -
external_refstringYour identifier for the term. Sending it again EXTENDS the same term instead of opening a second one — that is how a renewal is told from a new sale.
Example:
sub_88213 -
starts_atstringDefaults to today.
Example:
2026-08-23 -
ends_atstringLeave both this and
term_monthsout for a purchase that never expires.Example:
2027-08-23 -
term_monthsintegerUsed to derive
ends_atwhen you do not send one.Example:
12 -
auto_renewbooleanWhether your system will renew it on its own.
Example:
true
-
curl --request POST \
"https://klozzo.com/api/v1/sales" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Idempotency-Key: required A unique value per operation. A retry with the same key returns the first answer instead of doing the work again. Example: sale-2026-08-23-88213" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"external_ref\": \"sale_88213\",
\"amount\": \"1499.00\",
\"currency\": \"MXN\",
\"paid_at\": \"2026-08-23T10:00:00Z\",
\"method\": \"tarjeta\",
\"reference\": \"ch_9912\",
\"title\": \"Plan Pro anual\",
\"deal_external_ref\": \"sub_44\",
\"customer\": {
\"email\": \"ada@example.com\",
\"phone\": \"+52 55 1234 5678\",
\"phone_country\": \"kh\",
\"first_name\": \"Ada\",
\"last_name\": \"Lovelace\",
\"company_name\": \"ADPA\",
\"company_external_ref\": \"verificamex-99\"
},
\"plan\": {
\"name\": \"Plan Pro\",
\"product_sku\": \"PLAN-PRO\",
\"external_ref\": \"sub_88213\",
\"starts_at\": \"2026-08-23\",
\"ends_at\": \"2027-08-23\",
\"term_months\": 12,
\"auto_renew\": true
}
}"
const url = new URL(
"https://klozzo.com/api/v1/sales"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Idempotency-Key": "required A unique value per operation. A retry with the same key returns the first answer instead of doing the work again. Example: sale-2026-08-23-88213",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"external_ref": "sale_88213",
"amount": "1499.00",
"currency": "MXN",
"paid_at": "2026-08-23T10:00:00Z",
"method": "tarjeta",
"reference": "ch_9912",
"title": "Plan Pro anual",
"deal_external_ref": "sub_44",
"customer": {
"email": "ada@example.com",
"phone": "+52 55 1234 5678",
"phone_country": "kh",
"first_name": "Ada",
"last_name": "Lovelace",
"company_name": "ADPA",
"company_external_ref": "verificamex-99"
},
"plan": {
"name": "Plan Pro",
"product_sku": "PLAN-PRO",
"external_ref": "sub_88213",
"starts_at": "2026-08-23",
"ends_at": "2027-08-23",
"term_months": 12,
"auto_renew": true
}
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://klozzo.com/api/v1/sales';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_TOKEN}',
'Idempotency-Key' => 'required A unique value per operation. A retry with the same key returns the first answer instead of doing the work again. Example: sale-2026-08-23-88213',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'external_ref' => 'sale_88213',
'amount' => '1499.00',
'currency' => 'MXN',
'paid_at' => '2026-08-23T10:00:00Z',
'method' => 'tarjeta',
'reference' => 'ch_9912',
'title' => 'Plan Pro anual',
'deal_external_ref' => 'sub_44',
'customer' => ['email' => 'ada@example.com', 'phone' => '+52 55 1234 5678', 'phone_country' => 'kh', 'first_name' => 'Ada', 'last_name' => 'Lovelace', 'company_name' => 'ADPA', 'company_external_ref' => 'verificamex-99'],
'plan' => ['name' => 'Plan Pro', 'product_sku' => 'PLAN-PRO', 'external_ref' => 'sub_88213', 'starts_at' => '2026-08-23', 'ends_at' => '2027-08-23', 'term_months' => 12, 'auto_renew' => true],
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json
url = 'https://klozzo.com/api/v1/sales'
payload = {
"external_ref": "sale_88213",
"amount": "1499.00",
"currency": "MXN",
"paid_at": "2026-08-23T10:00:00Z",
"method": "tarjeta",
"reference": "ch_9912",
"title": "Plan Pro anual",
"deal_external_ref": "sub_44",
"customer": {
"email": "ada@example.com",
"phone": "+52 55 1234 5678",
"phone_country": "kh",
"first_name": "Ada",
"last_name": "Lovelace",
"company_name": "ADPA",
"company_external_ref": "verificamex-99"
},
"plan": {
"name": "Plan Pro",
"product_sku": "PLAN-PRO",
"external_ref": "sub_88213",
"starts_at": "2026-08-23",
"ends_at": "2027-08-23",
"term_months": 12,
"auto_renew": true
}
}
headers = {
'Authorization': 'Bearer {YOUR_API_TOKEN}',
'Idempotency-Key': 'required A unique value per operation. A retry with the same key returns the first answer instead of doing the work again. Example: sale-2026-08-23-88213',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('POST', url, headers=headers, json=payload)
response.json()
{
"meta": {
"created": false,
"matched_by": "payment_external_ref"
}
}
-
metaobject-
createdboolean -
matched_bystring
-
{
"data": {
"deal": {
"status": "won",
"owner_id": null
}
},
"meta": {
"created": true,
"deal_created": true,
"matched_by": "created",
"claimable": true
}
}
-
dataobject-
dealobject-
statusstring -
owner_idstring
-
-
-
metaobject-
createdboolean -
deal_createdboolean -
matched_bystring -
claimableboolean
-
{
"message": "No hay ningún negocio con el external_ref «sub_44»."
}
Every error shares the same shape — message and errors.
See Errors.
List payments
Lists the payments recorded in this location, most recent first. Use it to
check what your side has
already reported before reporting it again — ?external_ref= finds the
one carrying your charge identifier, which is the cheapest way to answer
"did that webhook land?".
GET
/api/v1/payments
Query parameters
-
deal_idintegerOnly this deal's payments.
Example:
7 -
external_refstringFind the one carrying your identifier.
Example:
spei-8891 -
per_pageintegerRows per page. Default 50, maximum 100. See Lists, paging and filters.
Example:
50
curl --request GET \
--get "https://klozzo.com/api/v1/payments?deal_id=7&external_ref=spei-8891&per_page=50" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"
const url = new URL(
"https://klozzo.com/api/v1/payments"
);
const params = {
"deal_id": "7",
"external_ref": "spei-8891",
"per_page": "50",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://klozzo.com/api/v1/payments';
$response = $client->get(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_TOKEN}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'query' => [
'deal_id' => '7',
'external_ref' => 'spei-8891',
'per_page' => '50',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json
url = 'https://klozzo.com/api/v1/payments'
params = {
'deal_id': '7',
'external_ref': 'spei-8891',
'per_page': '50',
}
headers = {
'Authorization': 'Bearer {YOUR_API_TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('GET', url, headers=headers, params=params)
response.json()
{
"data": [
{
"id": null,
"deal_id": null,
"quote_id": null,
"subscription_id": null,
"amount": {
"amount": 100000000,
"currency": "MXN",
"decimal": "10000.0000",
"formatted": "$10,000.00"
},
"currency": "MXN",
"paid_at": "2026-08-17T02:19:50+00:00",
"method": "transferencia",
"method_label": "Transferencia",
"origin": null,
"reference": "SPEI-3724",
"note": null,
"recorded_by": null,
"external_ref": null,
"created_at": null
},
{
"id": null,
"deal_id": null,
"quote_id": null,
"subscription_id": null,
"amount": {
"amount": 100000000,
"currency": "MXN",
"decimal": "10000.0000",
"formatted": "$10,000.00"
},
"currency": "MXN",
"paid_at": "2026-08-15T02:19:50+00:00",
"method": "transferencia",
"method_label": "Transferencia",
"origin": null,
"reference": "SPEI-0432",
"note": null,
"recorded_by": null,
"external_ref": null,
"created_at": null
}
]
}
-
dataobject[]-
idstring -
deal_idstring -
quote_idstring -
subscription_idstring -
amountobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
currencystring -
paid_atstring -
methodstring -
method_labelstring -
originstring -
referencestring -
notestring -
recorded_bystring -
external_refstring -
created_atstring
-
Record a payment
Records a payment against a deal that already exists. It does not
close the deal and it does not accept a quote — use
POST /api/v1/deals/reconcile when the payment is what closes the sale,
or POST /api/v1/sales when there was never a deal to begin with.
POST
/api/v1/payments
Instalments are several calls against the same deal_id; give each one
its own external_ref so a retry of the second never looks like the
first.
Body parameters
-
deal_idinteger requiredThe deal being paid.
Example:
7 -
quote_idintegerThe quote it settles, when there is one.
Example:
15 -
amountstring requiredUp to four decimals, separators tolerated.
Example:
25013.94 -
paid_atstring requiredWhen the money arrived.
Example:
2026-08-02 -
methodstringOne of
transferencia,efectivo,tarjeta,otro.Example:
transferencia -
referencestringBank reference.
Example:
SPEI-8891 -
notestringExample:
Primer parcialidad -
external_refstringYour identifier for this payment.
Example:
charge_9912
curl --request POST \
"https://klozzo.com/api/v1/payments" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"deal_id\": 7,
\"quote_id\": 15,
\"amount\": \"25013.94\",
\"paid_at\": \"2026-08-02\",
\"method\": \"transferencia\",
\"reference\": \"SPEI-8891\",
\"note\": \"Primer parcialidad\",
\"external_ref\": \"charge_9912\"
}"
const url = new URL(
"https://klozzo.com/api/v1/payments"
);
const headers = {
"Authorization": "Bearer {YOUR_API_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"deal_id": 7,
"quote_id": 15,
"amount": "25013.94",
"paid_at": "2026-08-02",
"method": "transferencia",
"reference": "SPEI-8891",
"note": "Primer parcialidad",
"external_ref": "charge_9912"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://klozzo.com/api/v1/payments';
$response = $client->post(
$url,
[
'headers' => [
'Authorization' => 'Bearer {YOUR_API_TOKEN}',
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'deal_id' => 7,
'quote_id' => 15,
'amount' => '25013.94',
'paid_at' => '2026-08-02',
'method' => 'transferencia',
'reference' => 'SPEI-8891',
'note' => 'Primer parcialidad',
'external_ref' => 'charge_9912',
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json
url = 'https://klozzo.com/api/v1/payments'
payload = {
"deal_id": 7,
"quote_id": 15,
"amount": "25013.94",
"paid_at": "2026-08-02",
"method": "transferencia",
"reference": "SPEI-8891",
"note": "Primer parcialidad",
"external_ref": "charge_9912"
}
headers = {
'Authorization': 'Bearer {YOUR_API_TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('POST', url, headers=headers, json=payload)
response.json()
{
"data": {
"id": null,
"deal_id": null,
"quote_id": null,
"subscription_id": null,
"amount": {
"amount": 100000000,
"currency": "MXN",
"decimal": "10000.0000",
"formatted": "$10,000.00"
},
"currency": "MXN",
"paid_at": "2026-08-12T02:19:50+00:00",
"method": "transferencia",
"method_label": "Transferencia",
"origin": null,
"reference": "SPEI-0564",
"note": null,
"recorded_by": null,
"external_ref": null,
"created_at": null
}
}
-
dataobject-
idstring -
deal_idstring -
quote_idstring -
subscription_idstring -
amountobject-
amountinteger -
currencystring -
decimalstring -
formattedstring
-
-
currencystring -
paid_atstring -
methodstring -
method_labelstring -
originstring -
referencestring -
notestring -
recorded_bystring -
external_refstring -
created_atstring
-
{
"message": "Deal id no es válido.",
"errors": {
"deal_id": [
"Deal id no es válido."
]
}
}
Every error shares the same shape — message and errors.
See Errors.
{
"message": "Amount no es un importe válido.",
"errors": {
"amount": [
"Amount no es un importe válido."
]
}
}
Every error shares the same shape — message and errors.
See Errors.