> ## Documentation Index
> Fetch the complete documentation index at: https://docs.saasybill.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Customers and Xero

> How customers reach Saasybill, and when the API can create one in Xero

<Info>
  For how customers work in the app, see [Customers](/billing-workspace/customers).
</Info>

Xero is the source of truth for contacts. Saasybill syncs them as customers, and by default never creates one. The API can create a Xero contact, but only when the organisation has allowed it **and** your request asks for it.

***

## The default: look up by code

Give each customer a code, which is their contact number in Xero. Your platform then finds them without storing Saasybill ids.

```bash theme={null}
curl "https://app.saasybill.com/api/v1/customers?code=ACME-001" \
  -H "Authorization: Bearer $SAASYBILL_API_KEY"
```

If a subscription request names a `customer_code` that doesn't exist, the API answers `404 customer_not_found`. It never creates a contact by accident, so a mistyped code can't add a stray customer to your Xero.

***

## Let the API create customers

Two things must both be true before the API creates a Xero contact.

<Steps>
  <Step title="The organisation allows it">
    An owner or admin turns on **Create Xero Customers** under **Developer Settings**, then **API Keys**. It is off by default. While it's off, a request that asks to create a contact is refused with `403 xero_contact_creation_disabled`. The setting shows as `can_create_xero_contacts` on [the organisation](/api-reference/organisation/retrieve-the-organisation).
  </Step>

  <Step title="The request asks for it">
    Nothing creates a contact implicitly. Two requests can ask.
  </Step>
</Steps>

### Create a customer directly

`POST /v1/customers` needs the `customers:write` scope.

```json theme={null}
{
  "code": "ACME-001",
  "name": "Acme Pty Ltd",
  "email": "accounts@acme.example"
}
```

| Field | Description |
| - | - |
| `code` | Required. 1 to 50 printable characters, with no quotes, backslashes, or spaces at either end. Unique in the organisation. |
| `name` | Required. The name the contact is created under in Xero. Up to 255 characters. |
| `email` | Optional. A valid email address. |

### Create a customer while creating a subscription

Send `create_customer` next to `customer_code`. It is used only if no customer has that code.

```json theme={null}
{
  "customer_code": "NORTH-100",
  "create_customer": { "name": "Northwind Traders", "email": "ap@northwind.example" },
  "plan_code": "TEAM-MONTHLY",
  "units": 5
}
```

<Warning>
  `create_customer` needs `customer_code`. It is the code the new customer is created with.
</Warning>

***

## What the API does, in order

`POST /v1/customers` follows these steps. Each is safe to repeat, so a retry never creates a second contact.

<Steps>
  <Step title="Look for a customer with that code">
    Found: return it with `200`. Nothing is created.
  </Step>

  <Step title="Look in Xero for a contact whose contact number is the code">
    Found: adopt it as the customer and return it with `201`. Nothing new is created in Xero.
  </Step>

  <Step title="Create the contact in Xero">
    Otherwise create a contact with the name, the email if you sent one, and the code as its contact number. Adopt it and return the customer with `201`.
  </Step>
</Steps>

The status tells you which happened: `200` means the customer already existed, and `201` means it is new to Saasybill.

***

## When it doesn't work

| Status | Code | Cause and fix |
| - | - | - |
| `403` | `xero_contact_creation_disabled` | The organisation hasn't allowed it. Ask an owner or admin to turn on **Create Xero Customers**. |
| `403` | `plan_limit_reached` | The organisation has reached its customer limit. |
| `409` | `xero_contact_name_taken` | Xero already has a contact with that name. Saasybill doesn't guess that "Acme Ltd" and "Acme Limited" are the same customer. Use the existing contact's code, or a different name. |
| `409` | `xero_contact_archived` | The Xero contact with that code is archived. Restore it in Xero, or use another code. |
| `409` | `customer_not_importable` | A Xero contact has that code but hasn't been imported as a customer. Import it on the Customers page. |
| `409` | `xero_not_connected` | The organisation isn't connected to Xero. |
| `502` | `xero_error` | Xero failed the request. The message says why. Retry with the same `Idempotency-Key`. |

<Tip>
  Use your own stable account id as the customer `code`. Then a retry, or a second signup event for the same account, finds the customer that already exists.
</Tip>
