Clients
Create a client in a Refrens business, then get it using clientId — your own customer reference,
which you choose.
Like every Refrens API endpoint, the Clients API is authorised with an app token sent as Authorization: Bearer <jwt>. You can obtain the token in either of two ways:
- Call Create Token with
strategy: app-secret, or - Self-sign a token with your private key (
strategy: app-iss-app-token).
The calling app must be attached to the business in the path, otherwise the request is rejected with 403 PERMISSION_DENIED. A user session token is not accepted on these endpoints and is rejected with 401.
Create, get one, and find are live. Editing and deleting are not live yet and return
405; see Unsupported operations. Corrections are made in Refrens.
Quick start
-
Create a client:
POST /api/v1/businesses/:urlKey/clients
{ "clientId": "acme-042", "name": "Acme Traders Pvt Ltd" } -
Get it back:
GET /api/v1/businesses/:urlKey/clients/acme-042
clientId and name are the only required fields. Both requests need
Authorization: Bearer <jwt>, and the create also needs Content-Type: application/json.
Create New Client
post /api/v1/businesses/:urlKey/clients
Creates a client in the given business with full billing identity, address, tax ids, shipping
addresses, bank accounts and custom fields in one call. clientId is your own reference for the
customer and makes the call safe to retry.
When omitted, the API derives panNumber and gstState from gstin, and fills taxPayerType,
clientType and locale. A value you send is always kept — deriving never overwrites it.
- Request
- Response
Path Params
| Name | Description |
|---|---|
urlKey | The business urlKey provided by Refrens. |
Headers
| Name | Type | Value Description |
|---|---|---|
| Content-Type * | string | application/json |
| Authorization * | string | Bearer <jwt> |
Body — identity and contact
| Name | Type | Value Description |
|---|---|---|
| clientId * | string | Your customer reference. Unique within the business, maximum 128 characters, trimmed before storing. See Idempotency for retry behaviour. |
| name * | string | Client name. At least 1 character after trimming, maximum 500. |
| alias | string | Short name / alias. Maximum 500 characters. |
| clientType | string | INDIVIDUAL or COMPANY. Derived from the client's tax ids when omitted. |
| isClient | boolean | Whether this party is a customer. Defaults to true. |
| isVendor | boolean | Also marks the party a vendor. Defaults to false. isClient and isVendor cannot both be false. |
| string | Validated as an email address. | |
| phone | string | Normalised to + followed by digits — brackets, spaces and hyphens are stripped and a leading + added if absent, so (91) 12345-67890 is stored as +911234567890. Maximum 30 characters. |
| phoneShowInInvoice | boolean | Show the phone number on invoices. Defaults to false. |
| emailShowInInvoice | boolean | Show the email address on invoices. Defaults to false. |
| dueInDays | number | Default payment terms for this client, in days. |
| locale | string | Locale for the client's documents, e.g. en-IN. Derived from the client's country — or the business's country when the client has none — when omitted. |
Body — address
| Name | Type | Value Description |
|---|---|---|
| street | string | Maximum 2,500 characters. |
| building | string | Maximum 500 characters. |
| city | string | Maximum 500 characters. |
| district | string | Maximum 500 characters. |
| state | string | Maximum 500 characters. Requires country in the same request. |
| pincode | string | Maximum 20 characters. |
| country | string | ISO 3166-1 alpha-2 country code. Defaults to the business's own country when omitted. |
Body — tax identifiers
| Name | Type | Value Description |
|---|---|---|
| gstin | string | 15-character Indian GSTIN, including the UIN, TDS/TCS and OIDAR forms. Fills panNumber and gstState when those are omitted. |
| gstState | string | Indian GST state code, e.g. 29. Derived from gstin when omitted. |
| panNumber | string | 10 alphanumeric characters. Derived from gstin when omitted — a GSTIN that carries no PAN simply leaves it empty, which is not an error. |
| trnNumber | string | UAE TRN — 15 digits beginning with 1. |
| tinNumber | string | Malaysia TIN — 10 to 15 uppercase letters or digits. |
| sstNumber | string | Malaysia SST — AAA-9999-99999999 or A99-9999-99999999. |
| taxPayerType | string | From the vocabulary for the business's country (see below). The API rejects this field for businesses outside India and the UAE. Derived when omitted. |
| vatNumber | string | Stored as given; not format-validated. Maximum 500 characters. |
| vatLabel | string | Label shown against the VAT number. Defaults to VAT Number. |
taxPayerType accepted values, by the business's country:
| Business country | Accepted values |
|---|---|
India (IN) | B2C REG UNREG EXP SEZ TDS DEX COM |
UAE (AE) | VATR VATNR GCCVATR GCCVATNR NGCC VATRDZ VATRNDZ |
Body — lists
| Name | Type | Value Description |
|---|---|---|
| additionalIds | array[object] | Country-specific identifiers. Entry shape and vocabulary under Additional ids. |
| shippingDetails | array[object] | Shipping addresses. Entry shape under Shipping addresses. |
| bankAccounts | array[object] | The client's bank accounts. Entry shape under Bank accounts. |
| customFields | object | Custom field values keyed by the field's display name, e.g. { "Industry": "SaaS" }. Rules and errors under Custom fields. |
Any field not in the tables above is rejected with 400 INVALID_FIELD, naming the key.
Full sample body
{
"clientId": "acme-042",
"name": "Acme Traders Pvt Ltd",
"alias": "Acme",
"email": "billing@acme.example",
"phone": "(91) 12345-67890",
"street": "1 MG Road",
"building": "Tower A",
"city": "Bengaluru",
"district": "Bengaluru Urban",
"state": "Karnataka",
"pincode": "560001",
"country": "IN",
"gstin": "29AAPFU0939F1ZV",
"dueInDays": 15,
"additionalIds": [
{
"label": "UDY",
"value": "UDYAM-KA-03-1234567",
"showInInvoice": true
}
],
"shippingDetails": [
{
"name": "Warehouse",
"street": "Dock Road",
"city": "Bengaluru",
"country": "IN",
"gstin": "29AAPFU0939F1ZV",
"customHeaders": [{ "label": "PO", "value": "PO-1" }]
}
],
"bankAccounts": [
{
"bankAccountName": "Acme Current",
"bankAccountNumber": "000111222",
"ifscCode": "HDFC0000001",
"bankAccountType": "CURRENT",
"bankCountry": "IN",
"bankCurrency": "INR",
"isPrimary": true
}
],
"customFields": {
"Industry": "Manufacturing"
}
}
201: CreatedClient created successfully. On an idempotent retry (same clientId, same details) the same
client is returned with an additional "idempotent": true flag.
panNumber, gstState, taxPayerType, clientType and locale below were derived — the
request did not send them.
{
"success": true,
"data": {
"clientId": "acme-042",
"name": "Acme Traders Pvt Ltd",
"alias": "Acme",
"email": "billing@acme.example",
"phone": "+911234567890",
"phoneShowInInvoice": false,
"emailShowInInvoice": false,
"clientType": "COMPANY",
"isClient": true,
"isVendor": false,
"street": "1 MG Road",
"city": "Bengaluru",
"district": "Bengaluru Urban",
"building": "Tower A",
"pincode": "560001",
"state": "Karnataka",
"country": "IN",
"gstin": "29AAPFU0939F1ZV",
"gstState": "29",
"panNumber": "AAPFU0939F",
"trnNumber": null,
"tinNumber": null,
"sstNumber": null,
"taxPayerType": "REG",
"vatNumber": null,
"vatLabel": "VAT Number",
"dueInDays": 15,
"locale": "en-IN",
"shippingDetails": [
{
"name": "Warehouse",
"street": "Dock Road",
"city": "Bengaluru",
"district": null,
"building": null,
"pincode": null,
"state": null,
"country": "IN",
"gstin": "29AAPFU0939F1ZV",
"gstState": null,
"sstNumber": null,
"tinNumber": null,
"taxId": null,
"taxPayerType": null,
"additionalIds": [],
"customHeaders": [{ "label": "PO", "value": "PO-1" }]
}
],
"bankAccounts": [
{
"bankAccountName": "Acme Current",
"bankAccountHolderName": null,
"bankAccountNumber": "000111222",
"ifscCode": "HDFC0000001",
"iban": null,
"sortCode": null,
"swift": null,
"bankAccountType": "CURRENT",
"bankCountry": "IN",
"bankCurrency": "INR",
"isPrimary": true
}
],
"additionalIds": [
{
"label": "UDY",
"value": "UDYAM-KA-03-1234567",
"showInInvoice": true,
"useForEInvoice": false
}
],
"customFields": {
"Industry": "Manufacturing"
},
"source": "API",
"createdAt": "2026-08-13T09:14:22.481Z",
"updatedAt": "2026-08-13T09:14:22.481Z",
"balance": {
"invoice": 0,
"proforma": 0,
"creditNote": 0,
"paymentReceipt": 0,
"debitNote": 0,
"salesOrder": 0,
"invoicePayment": 0,
"proformaPayment": 0,
"expenditure": 0,
"expenditurePayment": 0,
"creditConsumed": 0,
"currency": "INR"
}
}
}
400: Bad RequestValidation failed and nothing was stored. data.error.code identifies the reason (see
Error codes). When several fields fail, data.error.errors lists every one of
them while data.error.message and data.error.code carry the first.
{
"name": "BadRequest",
"message": "gstin must be a valid 15-character GSTIN (including UIN / TDS-TCS / OIDAR forms)",
"code": 400,
"className": "bad-request",
"data": {
"success": false,
"error": {
"message": "gstin must be a valid 15-character GSTIN (including UIN / TDS-TCS / OIDAR forms)",
"code": "INVALID_FIELD_VALUE",
"status": 400,
"errors": [
{
"field": "gstin",
"message": "gstin must be a valid 15-character GSTIN (including UIN / TDS-TCS / OIDAR forms)",
"code": "INVALID_FIELD_VALUE"
},
{
"field": "country",
"message": "country is required when state is provided",
"code": "INVALID_FIELD_VALUE"
}
]
}
},
"errors": {}
}
409: ConflictA client already uses this clientId and the details you sent are different from what is
stored — see Idempotency. Create is never an edit: correct the client in
Refrens, or use a different clientId for a different customer.
{
"name": "BadRequest",
"message": "A client with this clientId already exists with different details",
"code": 409,
"className": "bad-request",
"data": {
"success": false,
"error": {
"message": "A client with this clientId already exists with different details",
"code": "IDEMPOTENCY_CONFLICT",
"status": 409
}
},
"errors": {}
}
401: UnauthorizedMissing, malformed, or invalid app token — including a valid user session token, which these endpoints do not accept.
{
"name": "NotAuthenticated",
"message": "Invalid login",
"code": 401,
"className": "not-authenticated",
"data": {
"message": "Invalid login"
},
"errors": {}
}
Idempotency
clientId makes the create safe to retry — if the connection drops, send the identical request
again:
| Case | Response |
|---|---|
New clientId | 201 + the full client. |
Same clientId, same details | 201 + the full client with "idempotent": true. |
Same clientId, different details | 409 IDEMPOTENCY_CONFLICT. Nothing is changed. |
Same clientId, but the body fails validation | 400, never 409 — whether or not the reference is taken. |
Same clientId, held by a client archived in Refrens | 409 IDEMPOTENCY_CONFLICT. The reference stays taken. |
What counts as different details. Refrens compares the client your request would store — after normalisation and after filling in derived values — against the client already stored. Details are different when they would store a different value. So these are all identical retries, not conflicts:
- Omitting a field the API filled in for you. Send a
gstinand nopanNumbertwice: the same PAN is derived both times. - Restating a derived value unchanged, or omitting a field whose stored value is its default.
- A differently formatted but equivalent value —
Billing@Acme.exampleandbilling@acme.example, or(91) 12345-67890and+911234567890. - Reordering entries in
additionalIds,shippingDetails, orbankAccounts.
balance, source, createdAt and updatedAt are never part of the comparison.
Dropping a value is a change, so a retry that omits a plain optional field the first call stored —
alias, say — conflicts. Send the same body you sent the first time.
No silent merging. A matching email, phone or tax id never merges two clients — identity is
clientId alone. A client created through this API and one created as a side effect of an invoice
for the same customer stay two separate records.
Additional ids
Each entry is { label, value, showInInvoice, useForEInvoice }, where label is the ID code
below, not its display name. value is required. One entry per code — the same code twice is
rejected.
Available codes depend on the client's country and role: client codes need isClient, seller
codes need isVendor, and both suits either.
| Country | Code | Identifier | Scope | Format |
|---|---|---|---|---|
India (IN) | UDY | Udyam Registration Number | both | UDYAM-XX-00-0000000 |
IEC | Importer Exporter Code | seller | 10 uppercase letters or digits | |
LUT | Letter of Undertaking | seller | 15 characters — two letters, twelve digits, then one letter or digit | |
Saudi Arabia (SA) | NAT | National ID | client | any non-empty value |
TIN | Tax Identification Number | client | any non-empty value | |
IQA | Iqama Number | client | any non-empty value | |
PAS | Passport ID | client | any non-empty value | |
GCC | GCC ID | client | any non-empty value | |
CRN | Commercial Registration Number | both | any non-empty value | |
MOM | Momra License | both | any non-empty value | |
MLS | MLSD License | both | any non-empty value | |
SAG | Sagia License | both | any non-empty value | |
OTH | Other ID | both | any non-empty value | |
HQ | Group VAT Number | seller | any non-empty value | |
Malaysia (MY) | BRN | Business Registration Number | both | any non-empty value |
NRIC | National Registration Identity Card | both | any non-empty value | |
PASSPORT | Passport Number | both | any non-empty value |
A code outside the list for the client's country — or any code at all when the client has no country
set — is rejected with 400 INVALID_FIELD_VALUE.
useForEInvoice selects the identifier e-invoicing reads in Saudi Arabia and Malaysia. At most one
entry per client can carry it: setting it on several is not refused, but only the first entry in the
array keeps the flag. India's e-invoicing never reads it — sending it on an Indian client is accepted
and has no effect.
Shipping addresses
Each shippingDetails[] entry accepts name, street, building, city, district, state,
pincode, country, gstin, gstState, sstNumber, tinNumber, taxId, taxPayerType,
additionalIds[] and customHeaders[] of { label, value }.
| Fields | Rule |
|---|---|
name, building, city, district, state | Maximum 500 characters. |
street | Maximum 2,500 characters. |
pincode | Maximum 20 characters. |
country, gstin, gstState, sstNumber, tinNumber | Validated exactly as the client's own fields of the same name. |
taxId, taxPayerType | Free text, maximum 500 characters. No vocabulary check. |
additionalIds[] | Same codes and formats as the client's own additionalIds. |
customHeaders[] | label maximum 500 characters, value maximum 2,500. |
An address's additionalIds are validated against the client's own country and party type, not
the address's. Nothing is derived inside a shipping address — a shipping gstin does not fill that
address's gstState or panNumber.
Bank accounts
Each bankAccounts[] entry accepts:
| Name | Type | Value Description |
|---|---|---|
| bankAccountName | string | Maximum 500 characters. |
| bankAccountHolderName | string | Maximum 500 characters. |
| bankAccountNumber | string | Maximum 2,500 characters. |
| ifscCode | string | Maximum 500 characters. |
| iban | string | Maximum 500 characters. |
| sortCode | string | NN-NN-NN or NNNNNN. Only valid when bankCountry is GB or IE. |
| swift | string | Maximum 500 characters. |
| bankAccountType | string | SAVINGS or CURRENT. |
| bankCountry | string | ISO 3166-1 alpha-2 country code. |
| bankCurrency | string | ISO 4217 currency code. |
| isPrimary | boolean | Marks the client's primary account. Defaults to false. |
No operation in this API removes a bank account — that is done in Refrens.
Custom fields
customFields is an object keyed by the display name the business sees in Refrens, e.g.
{ "Industry": "SaaS" }. Internal field keys never appear in either direction.
- Custom fields must be enabled for the business, otherwise
400 CUSTOM_FIELDS_NOT_ENABLED. - Every name must match a live, non-archived Clients custom field. Unknown names are reported
together in one
400 INVALID_CUSTOM_FIELD. - A name that resolves but carries a value the field does not accept is
400 INVALID_CUSTOM_FIELD_VALUE. - The API never creates a custom field or an option value.
Get a Client
get /api/v1/businesses/:urlKey/clients/:clientId
Returns the client stored under clientId. Use this endpoint after creating a client to confirm what
the API stored. See Response shape for the returned fields.
- Request
- Response
Path Params
| Name | Description |
|---|---|
urlKey | The business urlKey provided by Refrens. |
clientId | The reference you sent as clientId at create. For a client with no clientId, this can instead be the 24-character _id from Find Clients. |
Headers
| Name | Type | Value Description |
|---|---|---|
| Authorization * | string | Bearer <jwt> |
No request body and no query parameters — any query parameter is rejected with
400 INVALID_QUERY_FIELD.
If a value matches both, the clientId match wins. An unknown, archived, or other-business _id
returns the same 404 CLIENT_NOT_FOUND. Get responses do not include _id.
200: OK200 returns the current client in the shared response shape. It does not
include the create-only idempotent flag.
{
"success": true,
"data": {
"clientId": "acme-042",
"name": "Acme Traders Pvt Ltd",
"clientType": "COMPANY",
"isClient": true,
"isVendor": false,
"country": "IN",
"gstin": "29AAPFU0939F1ZV",
"gstState": "29",
"panNumber": "AAPFU0939F",
"taxPayerType": "REG",
"locale": "en-IN",
"shippingDetails": [],
"bankAccounts": [],
"additionalIds": [],
"customFields": {},
"source": "API",
"createdAt": "2026-08-13T09:14:22.481Z",
"updatedAt": "2026-08-13T09:31:07.220Z",
"balance": {
"invoice": 25000,
"proforma": 0,
"creditNote": 0,
"paymentReceipt": 10000,
"debitNote": 0,
"salesOrder": 0,
"invoicePayment": 10000,
"proformaPayment": 0,
"expenditure": 0,
"expenditurePayment": 0,
"creditConsumed": 0,
"currency": "INR"
}
}
}
404: Not FoundNo reachable client under this reference. A client that never existed, one archived or deleted in Refrens, and one belonging to another business all return this same response — see Visibility.
{
"name": "NotFound",
"message": "Client not found",
"code": 404,
"className": "not-found",
"data": {
"success": false,
"error": {
"message": "Client not found",
"code": "CLIENT_NOT_FOUND",
"status": 404
}
},
"errors": {}
}
400: Bad RequestA query parameter was sent. Fetch takes none.
{
"name": "BadRequest",
"message": "Unknown query field: includeArchived",
"code": 400,
"className": "bad-request",
"data": {
"success": false,
"error": {
"message": "Unknown query field: includeArchived",
"code": "INVALID_QUERY_FIELD",
"status": 400
}
},
"errors": {}
}
Visibility
GET returns a client only when it exists in the business your app is attached to and has not been
archived or deleted in Refrens. Everything else returns one indistinguishable
404 CLIENT_NOT_FOUND.
Clients created elsewhere in Refrens have no clientId. Find them with Find Clients,
then fetch them with the _id from the result.
Vendors created through this API are readable here too.
Find Clients
get /api/v1/businesses/:urlKey/clients
Lists and searches your business's clients. Archived clients and your own business record are left
out. Vendors are included; use isVendor to narrow to vendors or away from them. Each item is the
Get shape plus _id; see Response shape.
- Request
- Response
Path Params
| Name | Type | Value Description |
|---|---|---|
urlKey | string | The business urlKey provided by Refrens. |
Headers
| Name | Type | Value Description |
|---|---|---|
| Authorization * | string | Bearer <jwt> |
Pagination and sorting
| Name | Type | Value Description |
|---|---|---|
$limit | number | Page size. Whole number from 1 to 50. Default 10. |
$skip | number | Number of clients to skip. Whole number from 0 to 1000. Default 0. |
$sort | object | Bracket form, for example ?$sort[createdAt]=-1. Fields: createdAt, updatedAt, name. Values: 1 (ascending) or -1 (descending). Default: createdAt, newest first. |
Structured filters
Send several values three ways: commas (?clientType=INDIVIDUAL,COMPANY), repeat the key with
[] (?country[]=IN&country[]=AE), or $in (?country[$in][]=IN&country[$in][]=AE). Values
within one filter are joined with OR. Different filters are joined with AND. One bad value rejects
the whole call with INVALID_QUERY_VALUE.
| Name | Type | Value Description |
|---|---|---|
clientType | string | INDIVIDUAL or COMPANY, uppercase and case-sensitive. |
isVendor | boolean | true or false only. One value. |
city | string | Exact match. |
state | string | Exact match. |
pincode | string | Exact match. |
country | string | Two-letter ISO country code in uppercase, such as IN or AE. Case-sensitive: in returns INVALID_QUERY_VALUE. |
Exact-match lookups
email is trimmed and lowercased, then matched exactly. For phone, spaces, dashes and parentheses
are stripped and a leading + is added, then it is matched exactly. Send the country code:
919999988888 matches +919999988888. gstin is exact and case-sensitive. A value that matches
nothing returns an empty page, not an error.
| Name | Type | Value Description |
|---|---|---|
email | string | Trimmed, lowercased, exact match. |
phone | string | Normalized then exact match. Include the country code. |
gstin | string | Exact, case-sensitive match. |
Ranges
Both bounds are inclusive. Either may be left out. Both take ISO-8601 dates. A date with no time
means midnight UTC, so createdAtTo=2026-10-01 stops at the start of 1 October; send
2026-10-01T23:59:59.999Z to include that whole day. A bad date, or From after To, returns
INVALID_QUERY_VALUE.
| Name | Type | Value Description |
|---|---|---|
createdAtFrom | string | Clients created on or after this ISO-8601 date. |
createdAtTo | string | Clients created on or before this ISO-8601 date. |
There is no updatedAt range. To find recent changes, sort by $sort[updatedAt]=-1 and read
pages until you reach clients you have already seen.
Free-text search
q looks for your text in the client's name, alias, email and phone. You can use it together
with the other filters.
Your text only needs to match the start of a word. q=Shar finds "Sharma Foods" and
"Ravi Sharma". It does not find a client whose name only has those letters in the middle of a
word.
Results come back closest match first. $sort only orders clients that match equally well.
400 SEARCH_UNAVAILABLE is returned when search is switched off in an environment; use the
structured filters instead.
| Name | Type | Value Description |
|---|---|---|
q | string | Free-text search. |
Examples
# Second page of ten, newest first
GET /api/v1/businesses/acme/clients?$limit=10&$skip=10
# Companies in India and the UAE
GET /api/v1/businesses/acme/clients?clientType=COMPANY&country[]=IN&country[]=AE
# Check a GSTIN before creating
GET /api/v1/businesses/acme/clients?gstin=29AAPFU0939F1ZV
# Find by email
GET /api/v1/businesses/acme/clients?email=accounts@acme.example
# Vendors only
GET /api/v1/businesses/acme/clients?isVendor=true
# Created in a date range
GET /api/v1/businesses/acme/clients?createdAtFrom=2026-10-01&createdAtTo=2026-10-01T23:59:59.999Z
# Free text
GET /api/v1/businesses/acme/clients?q=acm
200: OKItems are in data.items. total counts every match, not just this page. clientId is null
for clients created outside this API.
{
"success": true,
"data": {
"items": [
{
"_id": "<a 24-hex string>",
"clientId": "acme-042",
"name": "Acme Traders Pvt Ltd",
"clientType": "COMPANY",
"isClient": true,
"isVendor": false,
"country": "IN",
"gstin": "29AAPFU0939F1ZV",
"gstState": "29",
"panNumber": "AAPFU0939F",
"taxPayerType": "REG",
"locale": "en-IN",
"shippingDetails": [],
"bankAccounts": [],
"additionalIds": [],
"customFields": {},
"source": "API",
"createdAt": "2026-08-13T09:14:22.481Z",
"updatedAt": "2026-08-13T09:31:07.220Z",
"balance": {
"invoice": 25000,
"proforma": 0,
"creditNote": 0,
"paymentReceipt": 10000,
"debitNote": 0,
"salesOrder": 0,
"invoicePayment": 10000,
"proformaPayment": 0,
"expenditure": 0,
"expenditurePayment": 0,
"creditConsumed": 0,
"currency": "INR"
}
},
{
"_id": "<another 24-hex string>",
"clientId": null,
"name": "Imported Client",
"clientType": "COMPANY",
"isClient": true,
"isVendor": false,
"country": "AE",
"source": "CLIENT_UPLOAD",
"createdAt": "2026-08-12T09:14:22.481Z",
"updatedAt": "2026-08-12T09:14:22.481Z"
}
],
"total": 2,
"limit": 10,
"skip": 0
}
}
No match is not an error, and never a 404. You get a successful page with no items:
{
"success": true,
"data": {
"items": [],
"total": 0,
"limit": 10,
"skip": 0
}
}
400: Bad RequestA query parameter was unknown, invalid, or out of bounds. The error.code identifies the reason.
{
"name": "BadRequest",
"message": "Limit must be an integer between 1 and 50, requested 51",
"code": 400,
"className": "bad-request",
"data": {
"success": false,
"error": {
"message": "Limit must be an integer between 1 and 50, requested 51",
"code": "INVALID_PAGINATION_LIMIT",
"status": 400
}
},
"errors": {}
}
Reading a page
Raise $skip until skip + items.length reaches total. Caps are $limit 50 and $skip 1000,
so one set of filters reaches at most 1,050 rows. Narrow the filters, for example by a createdAt
range, to walk more. Results can shift between calls if clients are added.
Unknown values vs no matches
| You send | You get |
|---|---|
| Unknown parameter name | 400 INVALID_QUERY_FIELD |
Bad value, for example clientType=PERSON or country=XX | 400 INVALID_QUERY_VALUE |
| Valid value with no matches | 200 with empty items |
Query safety
Only the listed parameters are accepted. $in is the only operator. Anything else, such as $regex
or $ne, returns INVALID_QUERY_VALUE.
Find error codes
error.code | Status | When |
|---|---|---|
INVALID_QUERY_FIELD | 400 | Unknown parameter or $sort field, or a $sort not in bracket form. |
INVALID_QUERY_VALUE | 400 | A bad filter value, date, range, or unsupported operator. |
INVALID_PAGINATION_LIMIT | 400 | $limit is not a whole number from 1 to 50. |
INVALID_PAGINATION_SKIP | 400 | $skip is not a whole number from 0 to 1000. |
SEARCH_UNAVAILABLE | 400 | q was used where search is switched off. |
PERMISSION_DENIED | 403 | The app is not attached to this business. |
SERVICE_ERROR | 500 | Unexpected server error. |
401 has no error.code, matching the wording of the page's existing 401 note.
Response shape
Create and Get return this shape. Each Find item is this shape with _id added as the first field:
- Your reference as
clientIdfor a client created through this API, ornullfor a client created elsewhere in Refrens. - Every stored field from the Create New Client body tables, including the three lists: additional ids, shipping addresses and bank accounts.
customFields, keyed by display name.source—APIfor a client created through this API. Clients created elsewhere in Refrens carry their own origin, such asINVOICE,CLIENT_DASHBOARD,CLIENT_UPLOAD,DASHBOARD,INVOICE_BULK_UPLOADorLEADFORM.createdAtandupdatedAt.balance— a read-only summary derived from the client's documents and payments. It changes as the client transacts, without this API being called.
Create adds "idempotent": true on a retry that matched. No Refrens-internal identifier appears,
except the top-level _id on each Find item, and no error message names an internal field.
Error codes
error.code | Status | When |
|---|---|---|
INVALID_FIELD | 400 | A body key outside the accepted set, a server-owned field, a wrongly typed value, or a body that is not a JSON object. |
INVALID_FIELD_VALUE | 400 | An accepted field carrying a malformed or unsupported value, or a missing clientId / name. Names the field as you sent it. |
INVALID_QUERY_FIELD | 400 | Any query parameter on Get, which takes none; or an unknown parameter or $sort field on Find. |
INVALID_QUERY_VALUE | 400 | A Find query value is malformed, unsupported, or invalid. |
INVALID_PAGINATION_LIMIT | 400 | Find $limit is not a whole number between 1 and 50. |
INVALID_PAGINATION_SKIP | 400 | Find $skip is not a whole number between 0 and 1000. |
SEARCH_UNAVAILABLE | 400 | Find q was used where search is switched off. |
CUSTOM_FIELDS_NOT_ENABLED | 400 | customFields sent to a business without custom fields enabled. |
INVALID_CUSTOM_FIELD | 400 | A custom field name the business has not defined, or has archived. |
INVALID_CUSTOM_FIELD_VALUE | 400 | A defined custom field carrying a value it does not accept. |
PERMISSION_DENIED | 403 | The calling app is not attached to this business. |
CLIENT_NOT_FOUND | 404 | Unknown, archived, deleted, or another business's client. |
IDEMPOTENCY_CONFLICT | 409 | Same clientId with different details, or a reference held by an archived client. |
SERVICE_ERROR | 500 | Unexpected server error. The response carries a generic message. |
401 responses carry no error.code — they come from authentication before this API's own error
envelope applies.
Name errors versus value errors:
| Surface | Bad name | Bad value |
|---|---|---|
| Body field | INVALID_FIELD | INVALID_FIELD_VALUE |
| Query parameter | INVALID_QUERY_FIELD | INVALID_QUERY_VALUE |
| Custom field | INVALID_CUSTOM_FIELD | INVALID_CUSTOM_FIELD_VALUE |
Unsupported operations
You can't edit or delete clients through the API yet. PATCH, PUT and DELETE on a client return
405.
A 405 has no error.code. That is how you tell it apart from 404 CLIENT_NOT_FOUND: a 405 means
the action is not built yet, and a 404 means the client is not there.