---
title: "Customers"
description: "Keep a buyer as a Customer: create, find, update and delete it, and store its tax IDs."
url: https://docs.fluveo.com/payments/customers/
---

> Index of every page: https://docs.fluveo.com/llms.txt. Integrating Fluveo? Read https://docs.fluveo.com/agents/integrate.md first.

> Too long? This page with code in one language only: https://docs.fluveo.com/payments/customers.curl.md (cURL), https://docs.fluveo.com/payments/customers.node.md (Node.js), https://docs.fluveo.com/payments/customers.python.md (Python), https://docs.fluveo.com/payments/customers.php.md (PHP).

# Customers

Keep a buyer as a Customer: create, find, update and delete it, and store its tax IDs.

A Customer (`cus_...`) is a buyer your store keeps on file: an email, a name, a phone, addresses, your own metadata and up to five tax IDs. Create one for a buyer you expect to see again. You can then name it in `customer` on a PaymentIntent, list the cards saved to it, and look it up by email or by search. A one-off payment does not need a Customer.

This page walks one Customer from create to delete. Every request below runs on a fresh test store in the order shown, and each id comes from an earlier answer. The full list of fields is in the [Customers API reference](https://docs.fluveo.com/api/customers.md).

## Create a Customer

Send what you know about the buyer. Every field is optional. To store tax IDs at the same time, add `tax_id_data` entries, numbered from `0`, each with a `type` and a `value`. Make one `Idempotency-Key` for each Customer you mean to create: a retry with that key answers the same Customer instead of a second one.

**Create a Customer with a CPF**

cURL:

```bash
curl https://api.fluveo.com/v1/customers \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: crm-a1042-create" \
  -d email=ada@example.com \
  -d name="Ada Lovelace" \
  -d "metadata[crm_id]=A-1042" \
  -d "tax_id_data[0][type]=br_cpf" \
  -d "tax_id_data[0][value]=52998224725"
```

Node.js:

```javascript
const response = await fetch("https://api.fluveo.com/v1/customers", {
  method: "POST",
  headers: {
    Authorization: "Bearer sk_test_...",
    "Idempotency-Key": "crm-a1042-create",
  },
  body: new URLSearchParams({
    email: "ada@example.com",
    name: "Ada Lovelace",
    "metadata[crm_id]": "A-1042",
    "tax_id_data[0][type]": "br_cpf",
    "tax_id_data[0][value]": "52998224725",
  }),
});
const customer = await response.json();
console.log(customer.id);
```

Python:

```python
import requests

response = requests.post(
    "https://api.fluveo.com/v1/customers",
    headers={
        "Authorization": "Bearer sk_test_...",
        "Idempotency-Key": "crm-a1042-create",
    },
    data={
        "email": "ada@example.com",
        "name": "Ada Lovelace",
        "metadata[crm_id]": "A-1042",
        "tax_id_data[0][type]": "br_cpf",
        "tax_id_data[0][value]": "52998224725",
    },
)
customer = response.json()
print(customer["id"])
```

PHP:

```php
<?php
$ch = curl_init("https://api.fluveo.com/v1/customers");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer sk_test_...",
        "Idempotency-Key: crm-a1042-create",
    ],
    CURLOPT_POSTFIELDS => http_build_query([
        "email" => "ada@example.com",
        "name" => "Ada Lovelace",
        "metadata" => ["crm_id" => "A-1042"],
        "tax_id_data" => [["type" => "br_cpf", "value" => "52998224725"]],
    ]),
]);
$customer = json_decode(curl_exec($ch), true);
echo $customer["id"];
```

The answer is the new Customer. Keep its `id`: every later call on this page uses it.

*200 response (shortened)*

```json
{
  "id": "cus_...",
  "object": "customer",
  "email": "ada@example.com",
  "name": "Ada Lovelace",
  "phone": null,
  "description": null,
  "metadata": { "crm_id": "A-1042" },
  "tax_exempt": "none",
  "preferred_locales": [],
  "created": 1762862400,
  "livemode": false
}
```

The tax IDs are kept, but a Customer shows its `tax_ids` list only when you ask for it with `expand[]=tax_ids` (see [Expanding responses](https://docs.fluveo.com/start/expand.md)).

## Retrieve a Customer

`GET /v1/customers/{customer}` reads one Customer. Here it also expands `tax_ids`, so you can see the CPF stored by the create.

**Retrieve the Customer with its tax IDs**

cURL:

```bash
curl -G https://api.fluveo.com/v1/customers/cus_... \
  -H "Authorization: Bearer sk_test_..." \
  -d "expand[]=tax_ids"
```

Node.js:

```javascript
const response = await fetch("https://api.fluveo.com/v1/customers/cus_...?expand[]=tax_ids", {
  headers: { Authorization: "Bearer sk_test_..." },
});
const customer = await response.json();
console.log(customer.tax_ids.data);
```

Python:

```python
import requests

response = requests.get(
    "https://api.fluveo.com/v1/customers/cus_...",
    headers={"Authorization": "Bearer sk_test_..."},
    params={"expand[]": "tax_ids"},
)
customer = response.json()
print(customer["tax_ids"]["data"])
```

PHP:

```php
<?php
$ch = curl_init("https://api.fluveo.com/v1/customers/cus_...?expand[]=tax_ids");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer sk_test_..."],
]);
$customer = json_decode(curl_exec($ch), true);
print_r($customer["tax_ids"]["data"]);
```

Fluveo stores a tax ID in the usual written form of its type, so the digits `52998224725` come back as `529.982.247-25`.

*200 response (shortened)*

```json
{
  "id": "cus_...",
  "object": "customer",
  "email": "ada@example.com",
  "tax_ids": {
    "object": "list",
    "data": [
      {
        "id": "txi_...",
        "object": "tax_id",
        "type": "br_cpf",
        "value": "529.982.247-25",
        "customer": "cus_..."
      }
    ],
    "has_more": false,
    "url": "/v1/customers/cus_.../tax_ids"
  }
}
```

## Update a Customer

`POST /v1/customers/{customer}` changes only the fields you send and leaves the rest as they were. `metadata` merges key by key: a new key is added, and a key sent with an empty value is removed. Tax IDs are not changed here: use the [tax ID calls](https://docs.fluveo.com/payments/customers.md#tax-ids) below.

**Update the description and the metadata**

cURL:

```bash
curl https://api.fluveo.com/v1/customers/cus_... \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: crm-a1042-update-1" \
  -d description="Wholesale buyer" \
  -d "metadata[segment]=wholesale"
```

Node.js:

```javascript
const response = await fetch("https://api.fluveo.com/v1/customers/cus_...", {
  method: "POST",
  headers: {
    Authorization: "Bearer sk_test_...",
    "Idempotency-Key": "crm-a1042-update-1",
  },
  body: new URLSearchParams({
    description: "Wholesale buyer",
    "metadata[segment]": "wholesale",
  }),
});
const customer = await response.json();
console.log(customer.description, customer.metadata);
```

Python:

```python
import requests

response = requests.post(
    "https://api.fluveo.com/v1/customers/cus_...",
    headers={
        "Authorization": "Bearer sk_test_...",
        "Idempotency-Key": "crm-a1042-update-1",
    },
    data={
        "description": "Wholesale buyer",
        "metadata[segment]": "wholesale",
    },
)
customer = response.json()
print(customer["description"], customer["metadata"])
```

PHP:

```php
<?php
$ch = curl_init("https://api.fluveo.com/v1/customers/cus_...");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer sk_test_...",
        "Idempotency-Key: crm-a1042-update-1",
    ],
    CURLOPT_POSTFIELDS => http_build_query([
        "description" => "Wholesale buyer",
        "metadata" => ["segment" => "wholesale"],
    ]),
]);
$customer = json_decode(curl_exec($ch), true);
echo $customer["description"];
```

The Customer comes back with the new description and both metadata keys.

*200 response (shortened)*

```json
{
  "id": "cus_...",
  "object": "customer",
  "description": "Wholesale buyer",
  "metadata": { "crm_id": "A-1042", "segment": "wholesale" }
}
```

## Find Customers

### List by email

`GET /v1/customers` lists your Customers, newest first, paged with `limit` (1 to 100, default 10), `starting_after` and `ending_before` (see [Pagination](https://docs.fluveo.com/start/pagination.md)). `email` narrows the list to one exact address. The match is case-sensitive: `Ada@example.com` does not find `ada@example.com`. `created` filters on the creation time.

**List the Customers with one email**

cURL:

```bash
curl -G https://api.fluveo.com/v1/customers \
  -H "Authorization: Bearer sk_test_..." \
  -d email=ada@example.com \
  -d limit=3
```

Node.js:

```javascript
const query = new URLSearchParams({ email: "ada@example.com", limit: "3" });
const response = await fetch("https://api.fluveo.com/v1/customers?" + query, {
  headers: { Authorization: "Bearer sk_test_..." },
});
const list = await response.json();
console.log(list.data.length, list.has_more);
```

Python:

```python
import requests

response = requests.get(
    "https://api.fluveo.com/v1/customers",
    headers={"Authorization": "Bearer sk_test_..."},
    params={"email": "ada@example.com", "limit": 3},
)
page = response.json()
print(len(page["data"]), page["has_more"])
```

PHP:

```php
<?php
$query = http_build_query(["email" => "ada@example.com", "limit" => 3]);
$ch = curl_init("https://api.fluveo.com/v1/customers?" . $query);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer sk_test_..."],
]);
$list = json_decode(curl_exec($ch), true);
echo count($list["data"]);
```

*200 response (shortened)*

```json
{
  "object": "list",
  "data": [{ "id": "cus_...", "object": "customer", "email": "ada@example.com" }],
  "has_more": false,
  "url": "/v1/customers"
}
```

### Search

`GET /v1/customers/search` takes a `query` and answers a `search_result` with `data`, `has_more` and `next_page`. A Customer can be found as soon as the write that made it returns.

**Search by email**

cURL:

```bash
curl -G https://api.fluveo.com/v1/customers/search \
  -H "Authorization: Bearer sk_test_..." \
  --data-urlencode 'query=email:"ada@example.com"'
```

Node.js:

```javascript
const query = new URLSearchParams({ query: 'email:"ada@example.com"' });
const response = await fetch("https://api.fluveo.com/v1/customers/search?" + query, {
  headers: { Authorization: "Bearer sk_test_..." },
});
const result = await response.json();
console.log(result.data.length);
```

Python:

```python
import requests

response = requests.get(
    "https://api.fluveo.com/v1/customers/search",
    headers={"Authorization": "Bearer sk_test_..."},
    params={"query": 'email:"ada@example.com"'},
)
result = response.json()
print(len(result["data"]))
```

PHP:

```php
<?php
$query = http_build_query(["query" => 'email:"ada@example.com"']);
$ch = curl_init("https://api.fluveo.com/v1/customers/search?" . $query);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer sk_test_..."],
]);
$result = json_decode(curl_exec($ch), true);
echo count($result["data"]);
```

*200 response (shortened)*

```json
{
  "object": "search_result",
  "url": "/v1/customers/search",
  "data": [{ "id": "cus_...", "object": "customer", "email": "ada@example.com" }],
  "has_more": false,
  "next_page": null
}
```

A query is made of clauses: a field, an operator and a value in quotes. The fields are `email`, `name`, `phone`, `created` and `metadata["key"]`.

| Query | Finds |
| --- | --- |
| `email:"ada@example.com"` | that address, in any letter case |
| `name:"ada lovelace"` | names holding those whole words, in that order, in any case |
| `name~"love"` | names holding "love" anywhere; a `~` value needs at least 3 characters, and a shorter one finds nothing |
| `metadata["crm_id"]:"A-1042"` | Customers whose `crm_id` metadata is `A-1042` |
| `created>1762000000` | Customers made after that Unix time; `created` takes `=`, `:`, `>`, `>=`, `<` and `<=` with a whole number |
| `-phone:null` | Customers that have a phone; a `-` negates a clause |
| `email:"a@example.com" OR email:"b@example.com"` | either address |

Join up to 10 clauses with `AND` or with `OR`, never both in one query, and without parentheses. A space between clauses counts as `AND`. When `has_more` is `true`, send `next_page` back as `page`, with the same `query`, to read the next page. Add `expand[]=total_count` to get `total_count`, the number of matches. A deleted Customer is never found.

## Tax IDs

A Customer holds up to five tax IDs (`txi_...`). Each has a `type` and a `value`. The `type` is `br_cpf` for a CPF or `br_cnpj` for a CNPJ. Send the `value` with or without its dots, dash and slash: Fluveo checks that it has the form of its type and answers it in that written form. Add tax IDs with `tax_id_data` when you [create the Customer](https://docs.fluveo.com/payments/customers.md#create), or one at a time with the calls below. A tax ID cannot be edited: delete it and add the new one.

### Add a tax ID

**Add a CNPJ to the Customer**

cURL:

```bash
curl https://api.fluveo.com/v1/customers/cus_.../tax_ids \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: crm-a1042-cnpj" \
  -d type=br_cnpj \
  -d value=11222333000181
```

Node.js:

```javascript
const response = await fetch("https://api.fluveo.com/v1/customers/cus_.../tax_ids", {
  method: "POST",
  headers: {
    Authorization: "Bearer sk_test_...",
    "Idempotency-Key": "crm-a1042-cnpj",
  },
  body: new URLSearchParams({ type: "br_cnpj", value: "11222333000181" }),
});
const taxId = await response.json();
console.log(taxId.id, taxId.value);
```

Python:

```python
import requests

response = requests.post(
    "https://api.fluveo.com/v1/customers/cus_.../tax_ids",
    headers={
        "Authorization": "Bearer sk_test_...",
        "Idempotency-Key": "crm-a1042-cnpj",
    },
    data={"type": "br_cnpj", "value": "11222333000181"},
)
tax_id = response.json()
print(tax_id["id"], tax_id["value"])
```

PHP:

```php
<?php
$ch = curl_init("https://api.fluveo.com/v1/customers/cus_.../tax_ids");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer sk_test_...",
        "Idempotency-Key: crm-a1042-cnpj",
    ],
    CURLOPT_POSTFIELDS => http_build_query(["type" => "br_cnpj", "value" => "11222333000181"]),
]);
$taxId = json_decode(curl_exec($ch), true);
echo $taxId["id"];
```

The answer is the new tax ID. Keep its `id` for the calls below.

*200 response (shortened)*

```json
{
  "id": "txi_...",
  "object": "tax_id",
  "type": "br_cnpj",
  "value": "11.222.333/0001-81",
  "customer": "cus_...",
  "created": 1762862460,
  "livemode": false
}
```

### List a Customer's tax IDs

`GET /v1/customers/{customer}/tax_ids` answers them oldest first, paged with `limit`, `starting_after` and `ending_before`. This Customer now has two: the CPF from the create and the CNPJ.

**List the tax IDs**

cURL:

```bash
curl https://api.fluveo.com/v1/customers/cus_.../tax_ids \
  -H "Authorization: Bearer sk_test_..."
```

Node.js:

```javascript
const response = await fetch("https://api.fluveo.com/v1/customers/cus_.../tax_ids", {
  headers: { Authorization: "Bearer sk_test_..." },
});
const list = await response.json();
console.log(list.data.map((taxId) => taxId.type));
```

Python:

```python
import requests

response = requests.get(
    "https://api.fluveo.com/v1/customers/cus_.../tax_ids",
    headers={"Authorization": "Bearer sk_test_..."},
)
print([tax_id["type"] for tax_id in response.json()["data"]])
```

PHP:

```php
<?php
$ch = curl_init("https://api.fluveo.com/v1/customers/cus_.../tax_ids");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer sk_test_..."],
]);
$list = json_decode(curl_exec($ch), true);
echo count($list["data"]);
```

*200 response (shortened)*

```json
{
  "object": "list",
  "data": [
    { "id": "txi_...", "object": "tax_id", "type": "br_cpf", "value": "529.982.247-25" },
    { "id": "txi_...", "object": "tax_id", "type": "br_cnpj", "value": "11.222.333/0001-81" }
  ],
  "has_more": false,
  "url": "/v1/customers/cus_.../tax_ids"
}
```

### Retrieve a tax ID

**Retrieve the CNPJ**

cURL:

```bash
curl https://api.fluveo.com/v1/customers/cus_.../tax_ids/txi_... \
  -H "Authorization: Bearer sk_test_..."
```

Node.js:

```javascript
const response = await fetch("https://api.fluveo.com/v1/customers/cus_.../tax_ids/txi_...", {
  headers: { Authorization: "Bearer sk_test_..." },
});
const taxId = await response.json();
console.log(taxId.value);
```

Python:

```python
import requests

response = requests.get(
    "https://api.fluveo.com/v1/customers/cus_.../tax_ids/txi_...",
    headers={"Authorization": "Bearer sk_test_..."},
)
print(response.json()["value"])
```

PHP:

```php
<?php
$ch = curl_init("https://api.fluveo.com/v1/customers/cus_.../tax_ids/txi_...");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer sk_test_..."],
]);
$taxId = json_decode(curl_exec($ch), true);
echo $taxId["value"];
```

The answer is the same tax ID object the create returned.

### Delete a tax ID

**Delete the CNPJ**

cURL:

```bash
curl -X DELETE https://api.fluveo.com/v1/customers/cus_.../tax_ids/txi_... \
  -H "Authorization: Bearer sk_test_..."
```

Node.js:

```javascript
const response = await fetch("https://api.fluveo.com/v1/customers/cus_.../tax_ids/txi_...", {
  method: "DELETE",
  headers: { Authorization: "Bearer sk_test_..." },
});
const deleted = await response.json();
console.log(deleted.deleted);
```

Python:

```python
import requests

response = requests.delete(
    "https://api.fluveo.com/v1/customers/cus_.../tax_ids/txi_...",
    headers={"Authorization": "Bearer sk_test_..."},
)
print(response.json()["deleted"])
```

PHP:

```php
<?php
$ch = curl_init("https://api.fluveo.com/v1/customers/cus_.../tax_ids/txi_...");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => "DELETE",
    CURLOPT_HTTPHEADER => ["Authorization: Bearer sk_test_..."],
]);
$deleted = json_decode(curl_exec($ch), true);
var_dump($deleted["deleted"]);
```

*200 response*

```json
{
  "id": "txi_...",
  "object": "tax_id",
  "deleted": true
}
```

The tax ID leaves the Customer's list at once, and its id then answers 404 `resource_missing`.

## Use a Customer in a payment

### Name it on a PaymentIntent

Send the Customer's id in `customer` when you create a PaymentIntent. The payment is then tied to that buyer, and the PaymentIntent and its charge answer that `customer`. The amount is in the currency's smallest unit: `2000` is $20.00. This request only creates the PaymentIntent; to confirm and pay it, see [Accept a payment](https://docs.fluveo.com/payments/accept-a-payment.md).

**Create a PaymentIntent for the Customer**

cURL:

```bash
curl https://api.fluveo.com/v1/payment_intents \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: order-2077-pay" \
  -d amount=2000 \
  -d currency=usd \
  -d "payment_method_types[]=card" \
  -d customer=cus_...
```

Node.js:

```javascript
const response = await fetch("https://api.fluveo.com/v1/payment_intents", {
  method: "POST",
  headers: {
    Authorization: "Bearer sk_test_...",
    "Idempotency-Key": "order-2077-pay",
  },
  body: new URLSearchParams({
    amount: "2000",
    currency: "usd",
    "payment_method_types[]": "card",
    customer: "cus_...",
  }),
});
const paymentIntent = await response.json();
console.log(paymentIntent.id, paymentIntent.customer);
```

Python:

```python
import requests

response = requests.post(
    "https://api.fluveo.com/v1/payment_intents",
    headers={
        "Authorization": "Bearer sk_test_...",
        "Idempotency-Key": "order-2077-pay",
    },
    data={
        "amount": 2000,
        "currency": "usd",
        "payment_method_types[]": "card",
        "customer": "cus_...",
    },
)
payment_intent = response.json()
print(payment_intent["id"], payment_intent["customer"])
```

PHP:

```php
<?php
$ch = curl_init("https://api.fluveo.com/v1/payment_intents");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer sk_test_...",
        "Idempotency-Key: order-2077-pay",
    ],
    CURLOPT_POSTFIELDS => http_build_query([
        "amount" => 2000,
        "currency" => "usd",
        "payment_method_types" => ["card"],
        "customer" => "cus_...",
    ]),
]);
$paymentIntent = json_decode(curl_exec($ch), true);
echo $paymentIntent["customer"];
```

*200 response (shortened)*

```json
{
  "id": "pi_...",
  "object": "payment_intent",
  "amount": 2000,
  "currency": "usd",
  "customer": "cus_...",
  "status": "requires_payment_method"
}
```

`GET /v1/payment_intents` takes `customer` too, to list one buyer's payments. See [the `customer` parameter](https://docs.fluveo.com/api/payment_intents.md#post-v1-payment-intents) in the reference.

### List its saved cards

`GET /v1/customers/{customer}/payment_methods` lists the PaymentMethods saved to a Customer. Narrow it with `type=card`, and page it with `limit`, `starting_after` and `ending_before`. A new Customer has none, so this list is empty until a card is saved to it.

**List the Customer's cards**

cURL:

```bash
curl -G https://api.fluveo.com/v1/customers/cus_.../payment_methods \
  -H "Authorization: Bearer sk_test_..." \
  -d type=card
```

Node.js:

```javascript
const response = await fetch("https://api.fluveo.com/v1/customers/cus_.../payment_methods?type=card", {
  headers: { Authorization: "Bearer sk_test_..." },
});
const list = await response.json();
console.log(list.data.length);
```

Python:

```python
import requests

response = requests.get(
    "https://api.fluveo.com/v1/customers/cus_.../payment_methods",
    headers={"Authorization": "Bearer sk_test_..."},
    params={"type": "card"},
)
print(len(response.json()["data"]))
```

PHP:

```php
<?php
$ch = curl_init("https://api.fluveo.com/v1/customers/cus_.../payment_methods?type=card");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer sk_test_..."],
]);
$list = json_decode(curl_exec($ch), true);
echo count($list["data"]);
```

*200 response*

```json
{
  "object": "list",
  "data": [],
  "has_more": false,
  "url": "/v1/customers/cus_.../payment_methods"
}
```

`GET /v1/customers/{customer}/payment_methods/{payment_method}` reads one card through its Customer.

## Delete a Customer

`DELETE /v1/customers/{customer}` deletes a Customer for good. There is no undo.

**Delete the Customer**

cURL:

```bash
curl -X DELETE https://api.fluveo.com/v1/customers/cus_... \
  -H "Authorization: Bearer sk_test_..."
```

Node.js:

```javascript
const response = await fetch("https://api.fluveo.com/v1/customers/cus_...", {
  method: "DELETE",
  headers: { Authorization: "Bearer sk_test_..." },
});
const deleted = await response.json();
console.log(deleted.deleted);
```

Python:

```python
import requests

response = requests.delete(
    "https://api.fluveo.com/v1/customers/cus_...",
    headers={"Authorization": "Bearer sk_test_..."},
)
print(response.json()["deleted"])
```

PHP:

```php
<?php
$ch = curl_init("https://api.fluveo.com/v1/customers/cus_...");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => "DELETE",
    CURLOPT_HTTPHEADER => ["Authorization: Bearer sk_test_..."],
]);
$deleted = json_decode(curl_exec($ch), true);
var_dump($deleted["deleted"]);
```

*200 response*

```json
{
  "id": "cus_...",
  "object": "customer",
  "deleted": true
}
```

What changes after a delete:

- **Reading it.** `GET /v1/customers/{customer}` still answers 200, with only `id`, `object` and `"deleted": true`. The list and the search leave the Customer out. An update answers 404 `resource_missing`.
- **Its saved cards.** They are not detached: each keeps `customer` set to the deleted id, and no `payment_method.detached` event is sent. They can no longer pay. Confirming a PaymentIntent that holds one is refused with 400 `payment_method_unexpected_state`, and no charge is made. You can still detach a card with `POST /v1/payment_methods/{payment_method}/detach`.
- **Open PaymentIntents.** A PaymentIntent that names the deleted Customer and is not paid yet cannot be confirmed: the confirm is refused with `resource_missing` on `customer`, and no charge is made. A new PaymentIntent that names the deleted Customer is refused the same way.
- **Past payments.** The delete changes only the Customer. PaymentIntents, charges and refunds made before it stay as they were, with their `customer` id.
- **Events.** Only `customer.deleted` is sent.

## Parameters

### Customer fields

`POST /v1/customers` takes all of these. `POST /v1/customers/{customer}` takes all but `tax_id_data`.

**Body parameters**

- `email` (string, optional): The buyer's email address. It must look like an address, and it is stored as sent.
- `name` (string, optional): The person's or the company's name. Leading and trailing spaces are dropped.
- `phone` (string, optional): The buyer's phone number, trimmed like `name`.
- `description` (string, optional): Your own note about this buyer.
- `address` (object, optional): The buyer's address.
  - `line1` (string, optional)
  - `line2` (string, optional)
  - `city` (string, optional)
  - `state` (string, optional)
  - `postal_code` (string, optional)
  - `country` (string, optional)
- `shipping` (object, optional): Where to ship orders. When you send `shipping`, it needs a `name` and an `address`.
  - `name` (string, required)
  - `address` (object, required): The same fields as the Customer's `address`.
  - `phone` (string, optional)
- `metadata` (object, optional): Up to 50 of your own key-value pairs, sent as `metadata[key]=value`. On an update, an empty value removes its key. See [Metadata](https://docs.fluveo.com/start/metadata.md).
- `preferred_locales` (array, optional): The buyer's languages, most preferred first, as `preferred_locales[]=pt-BR`. Send `preferred_locales=` to empty the list.
- `tax_exempt` (string, optional): `none` (the default), `exempt` or `reverse`. Fluveo stores and shows it, and an empty value sets it back to `none`.
- `tax_id_data` (array, optional): Create only. Tax IDs made with the Customer, as `tax_id_data[0][type]` and `tax_id_data[0][value]`, then `[1]` and so on, up to five.
  - `type` (string, required): `br_cpf` or `br_cnpj`.
  - `value` (string, required): The CPF or CNPJ, with or without punctuation.

### Tax ID fields

**Body parameters of POST /v1/customers/{customer}/tax_ids**

- `type` (string, required): `br_cpf` for a CPF, `br_cnpj` for a CNPJ.
- `value` (string, required): The number, with or without its punctuation. It is answered in the written form of its type.

## States and events

A Customer has no status field. It exists until you delete it, and then reads back as deleted.

| Event | When |
| --- | --- |
| [`customer.created`](https://docs.fluveo.com/webhooks/events.md#customer.created) | A Customer was created. A create with `tax_id_data` first sends one `customer.tax_id.created` per tax ID, then this event. |
| [`customer.updated`](https://docs.fluveo.com/webhooks/events.md#customer.updated) | An update changed the Customer. `data.previous_attributes` holds the old values. |
| [`customer.deleted`](https://docs.fluveo.com/webhooks/events.md#customer.deleted) | The Customer was deleted. It is the only event a delete sends. |
| [`customer.tax_id.created`](https://docs.fluveo.com/webhooks/events.md#customer.tax_id.created) | A tax ID was added, at create or with `POST /v1/customers/{customer}/tax_ids`. |
| [`customer.tax_id.deleted`](https://docs.fluveo.com/webhooks/events.md#customer.tax_id.deleted) | A tax ID was deleted. |
| [`customer.tax_id.updated`](https://docs.fluveo.com/webhooks/events.md#customer.tax_id.updated) | A tax ID's verification check finished. A `br_cpf` or `br_cnpj` tax ID goes through no such check, so it never sends this event. |

## Errors you may see

| Error | Why |
| --- | --- |
| 400 [`email_invalid`](https://docs.fluveo.com/errors.md#email_invalid) | The `email` is not an email address. No Customer is created. |
| 400 [`parameter_missing`](https://docs.fluveo.com/errors.md#parameter_missing) | `shipping` was sent without its `name`, or without its `address`. |
| 400 [`parameter_unknown`](https://docs.fluveo.com/errors.md#parameter_unknown) | A parameter this call does not take, such as `balance` or `tax`. |
| 400 [`tax_id_invalid`](https://docs.fluveo.com/errors.md#tax_id_invalid) | The `value` does not have the form of its `type`, such as a CPF with too few digits. |
| 400 [`resource_already_exists`](https://docs.fluveo.com/errors.md#resource_already_exists) | The Customer already has a tax ID with this `type` and `value`. |
| 400 "Customer cus_... already has the maximum 5 tax IDs attached." | The Customer has five tax IDs: delete one first. |
| 404 [`resource_missing`](https://docs.fluveo.com/errors.md#resource_missing) | The `cus_...` or `txi_...` is not one of your store's, or the Customer was deleted (on update). |
| 400 [`payment_method_unexpected_state`](https://docs.fluveo.com/errors.md#payment_method_unexpected_state) | The PaymentIntent holds a card of a deleted Customer. Confirm it with another payment method. |

See [Errors](https://docs.fluveo.com/errors.md) for the shape of every error.

## Test it

1. Create a Customer with `email`, `name` and `tax_id_data[0][type]=br_cpf` with `tax_id_data[0][value]=52998224725`: you get a `cus_...`, one `customer.tax_id.created` and then `customer.created`.
2. Retrieve it with `expand[]=tax_ids`: the list holds the CPF as `529.982.247-25`.
3. Update its `description`: the answer shows the new value, and `customer.updated` carries the old one in `previous_attributes`.
4. List with `email=` and the same address: the Customer is there. List with the address in capitals: the list is empty. Search with `email:"..."`: it is found.
5. Add a `br_cnpj` tax ID: you get a `txi_...`. Add the same one again: 400 `resource_already_exists`. Add `type=br_cpf` with `value=123`: 400 `tax_id_invalid`.
6. Delete the CNPJ: `"deleted": true`, and `customer.tax_id.deleted` arrives.
7. Create a PaymentIntent with `customer=cus_...`: it answers that `customer`. List the Customer's cards: the list is empty.
8. Delete the Customer: `"deleted": true` and `customer.deleted`. Retrieve it: 200 with `"deleted": true`. Search for it: no match. Confirm the PaymentIntent of step 7 with `payment_method=pm_card_visa`: it is refused with `resource_missing` on `customer`.

> **For AI agents:**
>
> Customers for agents: create with POST /v1/customers and an Idempotency-Key unique to that buyer; reuse the key on retry so you never make two Customers. Look a buyer up with GET /v1/customers?email= (exact, case-sensitive) or GET /v1/customers/search before you create a new one. Use tax ID type br_cpf for a CPF or br_cnpj for a CNPJ, at most five per Customer; send tax IDs in tax_id_data at create or with POST /v1/customers/{customer}/tax_ids; there is no tax ID update, so delete and add again. A Customer shows tax_ids only with expand[]=tax_ids. Never send balance, cash_balance, source, tax or test_clock: they answer parameter_unknown. Deleting a Customer cannot be undone, sends only customer.deleted, leaves its cards with customer set but unusable, and makes any unpaid PaymentIntent that names it fail to confirm; ask the user before you delete one. Past payments keep their customer id.

## Next

- [Accept a payment](https://docs.fluveo.com/payments/accept-a-payment.md): Confirm a PaymentIntent with the card, Pix or Boleto details.
- [Save a card](https://docs.fluveo.com/payments/save-a-card.md): Keep a card on the Customer and charge it later.
- [The Customers reference](https://docs.fluveo.com/api/customers.md): Every field of the Customer and tax ID objects.
- [Event types](https://docs.fluveo.com/webhooks/events.md): The object each `customer.*` event carries.
- [Metadata](https://docs.fluveo.com/start/metadata.md): Store your own keys on a Customer.
