Customers
Record who pays — contact details, billing address and the visitor they are.
Customers
PUT https://api.faurya.com/api/v1/billing/customers/{external_id}
GET https://api.faurya.com/api/v1/billing/customers/{external_id}Requires Bearer Token authentication with your Faurya API key.
A customer is the person or company that pays. Everything else in the billing API hangs off one: invoices name a customer, subscriptions belong to a customer, transactions are the customer's money.
Send your customers before the invoices that name them — an invoice for an unknown customer is refused with CUSTOMER_NOT_FOUND.
Create or update a customer
The {external_id} in the path is your customer id. Reuse it and you update the same customer; every payment with the same id belongs to one person, whichever device, session or renewal it came from.
const response = await fetch(
`https://api.faurya.com/api/v1/billing/customers/${user.id}`,
{
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.FAURYA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
email: user.email,
name: user.name,
billing_address: {
country: "IN",
state: "Maharashtra",
city: "Mumbai",
street: "12 Linking Road",
zip: "400050",
},
faurya_visitor_id: req.cookies.faurya_visitor_id,
faurya_session_id: req.cookies.faurya_session_id,
}),
},
);Request body
Every field is optional. A PUT with an empty body {} is valid and creates a customer that is nothing but an id — useful when you only have the id at signup and fill the rest in later.
email(string): The customer's email address. At most 320 characters.name(string): The customer's name. At most 255 characters.billing_address(object): Any ofcountry,state,city,street,zip.countryis an ISO 3166-1 alpha-2 code;zipis at most 32 characters and the rest at most 255.faurya_visitor_id(string): Visitor id from thefaurya_visitor_idbrowser cookie. Links this customer to the visit that produced them, so their revenue is attributed to a traffic source.faurya_session_id(string): Session id from thefaurya_session_idcookie. Only meaningful alongside a visitor id; sent on its own it is ignored.
email and name are stored for display. They are never used to match
customers to one another — only the external_id in the path does that, so a
typo in an id creates a second customer rather than merging two.
Updating: absent is not null
A customer update is a patch, not a replacement:
| What you send | What happens |
|---|---|
| field absent | the stored value is left alone |
"email": null | the stored email is cleared |
"email": "a@b.com" | the stored email is replaced |
So PUT {"name": "Acme Ltd"} renames the customer and leaves their email and address exactly as they were. Sending "billing_address": null clears the whole address; sending {"billing_address": {"city": "Pune"}} changes only the city.
A visitor link is never removed this way — "faurya_visitor_id": null does not unlink anything.
Response
201 the first time, 200 on every later write.
{
"object": "customer",
"external_id": "cus_42",
"created": true,
"data": {
"external_id": "cus_42",
"email": "ada@example.com",
"name": "Ada Lovelace",
"billing_address": {
"country": "IN",
"state": "Maharashtra",
"city": "Mumbai",
"street": "12 Linking Road",
"zip": "400050"
},
"linked_visitor_count": 1,
"subscription_external_ids": ["sub_900"]
},
"warnings": [],
"request_id": "req_01JQ8Z0Y5N6W3K2M4P7R9T1V3X"
}linked_visitor_count— how many tracked visitors are linked to this customer. Zero means none of their revenue is attributed to a traffic source yet.subscription_external_ids— every subscription that currently belongs to them.
Visitor warnings
Two things can happen to a visitor link without failing the write:
{
"warnings": [
{
"code": "VISITOR_PENDING",
"field": "faurya_visitor_id",
"message": "This visitor has not been recorded by the tracker yet. The link completes automatically when it is."
}
]
}VISITOR_PENDING— the tracker has not written that visitor yet, usually because the payment happened seconds after the first page view. The link completes by itself.VISITOR_ALREADY_LINKED— that visitor already belongs to a different customer. The customer was saved; the link was not moved, because moving it would silently merge two people's revenue.
Read a customer
const response = await fetch(
`https://api.faurya.com/api/v1/billing/customers/${user.id}`,
{ headers: { Authorization: `Bearer ${process.env.FAURYA_API_KEY}` } },
);Answers 200 with the same data shape, or 404 if nothing was ever written under that id.
{
"object": "customer",
"external_id": "cus_42",
"data": { "external_id": "cus_42", "email": "ada@example.com" },
"request_id": "req_01JQ8Z0Y5N6W3K2M4P7R9T1V3X"
}Fields accepted for compatibility
If you are porting an export from ChartMogul, these customer fields are accepted and dropped rather than refused, so the export loads unchanged. Each one comes back as an IGNORED_FIELD warning:
company, website_url, lead_created_at, free_trial_started_at, tags
Any other unknown field is a 400 with UNKNOWN_FIELD, so a typo never passes silently.
Response codes
200 OK— customer updated, or the request matched what was stored.201 Created— the customer did not exist before.400 Bad Request— invalid fields, or an unknown field.errorsnames every one.401 Unauthorized— missing or invalid API key.404 Not Found— (GETonly) no customer with that id.409 Conflict— another request is writing the same customer; retry shortly.429 Too Many Requests— over the rate limit. Retry after theRetry-Afterheader.