Customers
Overview
Section titled “Overview”A Customer is a reusable entity that groups a payer’s saved payment methods, payment history, and payment plans under a single identifier. Attaching customers to payment intents lets you:
- Charge saved payment methods without the customer present
- Link transactions to a payer for reporting and reconciliation
- Set up recurring payment plans via the API
All customer operations require a secret key (sk_xxx).
Your customer reference is a candidate key
Section titled “Your customer reference is a candidate key”Every customer has two identifiers. id (cus_…) is ours, minted when the
customer is created. reference is yours — the identifier your own
system already uses for that customer (a CRM id, a member number, a
subscriber id) — and it is treated as a candidate key:
- Unique per account. Two live customers on the same account cannot
share a reference. Matching ignores surrounding whitespace and letter
case (
CRM-983andcrm-983are the same reference), so a duplicate is refused at create, update and import time. - Optional, but the join key for everything that moves in bulk. When
you import customers with an agent, set
referenceto your source system’s customer id; when you import their payment plans, each plan names its customer bycustomer_reference— no round-trip to look upcus_…ids for customers you created a minute ago, and it works just the same for customers created years ago through this API. - Stable. We never change it; only you can.
Set it at creation whenever you have one. A customer with no reference can
only ever be named by its cus_… id.
{ "name": "Alice Smith", "email": "alice@example.com", "reference": "alice-user-42" }To find a customer by reference, pass it as the reference filter when you
list customers. It is an exact
match under the same rule as uniqueness, ignoring surrounding whitespace and
letter case, so it returns at most one customer:
curl "https://api.elasticpay.co/api/v1/customers?reference=alice-user-42" \ -H "Authorization: Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"An empty data array means no customer on the account holds that reference.
Agents connected over MCP get the same lookup from list_customers.
Create a customer
Section titled “Create a customer”curl -X POST https://api.elasticpay.co/api/v1/customers \ -H "Authorization: Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "name": "Alice Smith", "email": "alice@example.com", "reference": "alice-user-42" }'const res = await fetch("https://api.elasticpay.co/api/v1/customers", { method: "POST", headers: { "Authorization": "Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "Content-Type": "application/json", }, body: JSON.stringify({ name: "Alice Smith", email: "alice@example.com", reference: "alice-user-42", }),});const customer = await res.json();import requests
res = requests.post( "https://api.elasticpay.co/api/v1/customers", headers={ "Authorization": "Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "Content-Type": "application/json", }, json={"name": "Alice Smith", "email": "alice@example.com", "reference": "alice-user-42"},)customer = res.json()<?phpuse GuzzleHttp\Client;
$client = new Client([ 'base_uri' => 'https://api.elasticpay.co', 'headers' => [ 'Authorization' => 'Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'Content-Type' => 'application/json', ],]);
$res = $client->post('/api/v1/customers', [ 'json' => [ 'name' => 'Alice Smith', 'email' => 'alice@example.com', 'reference' => 'alice-user-42', ],]);$customer = json_decode($res->getBody(), true);require 'faraday'require 'json'
conn = Faraday.new('https://api.elasticpay.co') do |f| f.request :json f.response :jsonendconn.headers['Authorization'] = 'Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'conn.headers['Content-Type'] = 'application/json'
res = conn.post('/api/v1/customers') do |req| req.body = { name: 'Alice Smith', email: 'alice@example.com', reference: 'alice-user-42' }endcustomer = res.bodyusing System.Net.Http;using System.Net.Http.Headers;using System.Net.Http.Json;
using var http = new HttpClient { BaseAddress = new Uri("https://api.elasticpay.co") };http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
var customer = await http.PostAsJsonAsync("/api/v1/customers", new { name = "Alice Smith", email = "alice@example.com", reference = "alice-user-42",});var result = await customer.Content.ReadFromJsonAsync<JsonElement>();Response
{ "id": "cus_0abc123def456ghi789jkl012mno", "name": "Alice Smith", "email": "alice@example.com", "reference": "alice-user-42", "created_at": "2026-04-01T10:00:00Z", "updated_at": "2026-04-01T10:00:00Z"}The id (e.g. cus_0abc...) is the customer’s stable identifier to use in subsequent requests.
The reference field is your own identifier for the customer — a candidate key, unique per account, that you can look the customer up by and that bulk imports join on.
In sandbox, you can also pass test_clock: "clk_..." to attach the customer
to a test clock at creation. The field is
immutable, and clock-bound customers are excluded from the default customer
list.
Fetch a customer
Section titled “Fetch a customer”curl https://api.elasticpay.co/api/v1/customers/cus_0abc123def456ghi789jkl012mno \ -H "Authorization: Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"const res = await fetch( "https://api.elasticpay.co/api/v1/customers/cus_0abc123def456ghi789jkl012mno", { headers: { "Authorization": "Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } });const customer = await res.json();import requests
res = requests.get( "https://api.elasticpay.co/api/v1/customers/cus_0abc123def456ghi789jkl012mno", headers={"Authorization": "Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"},)customer = res.json()<?phpuse GuzzleHttp\Client;
$client = new Client([ 'base_uri' => 'https://api.elasticpay.co', 'headers' => [ 'Authorization' => 'Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'Content-Type' => 'application/json', ],]);
$res = $client->get('/api/v1/customers/cus_0abc123def456ghi789jkl012mno');$customer = json_decode($res->getBody(), true);require 'faraday'require 'json'
conn = Faraday.new('https://api.elasticpay.co') do |f| f.request :json f.response :jsonendconn.headers['Authorization'] = 'Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'conn.headers['Content-Type'] = 'application/json'
res = conn.get('/api/v1/customers/cus_0abc123def456ghi789jkl012mno')customer = res.bodyusing System.Net.Http;using System.Net.Http.Headers;using System.Net.Http.Json;
using var http = new HttpClient { BaseAddress = new Uri("https://api.elasticpay.co") };http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
var customer = await http.GetFromJsonAsync<JsonElement>( "/api/v1/customers/cus_0abc123def456ghi789jkl012mno");List customers
Section titled “List customers”curl "https://api.elasticpay.co/api/v1/customers?limit=20" \ -H "Authorization: Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"const res = await fetch( "https://api.elasticpay.co/api/v1/customers?limit=20", { headers: { "Authorization": "Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } });const { data, has_more } = await res.json();import requests
res = requests.get( "https://api.elasticpay.co/api/v1/customers", headers={"Authorization": "Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}, params={"limit": 20},)data = res.json()<?phpuse GuzzleHttp\Client;
$client = new Client([ 'base_uri' => 'https://api.elasticpay.co', 'headers' => [ 'Authorization' => 'Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'Content-Type' => 'application/json', ],]);
$res = $client->get('/api/v1/customers', ['query' => ['limit' => 20]]);$data = json_decode($res->getBody(), true);require 'faraday'require 'json'
conn = Faraday.new('https://api.elasticpay.co') do |f| f.request :json f.response :jsonendconn.headers['Authorization'] = 'Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'conn.headers['Content-Type'] = 'application/json'
res = conn.get('/api/v1/customers') { |req| req.params['limit'] = 20 }data = res.bodyusing System.Net.Http;using System.Net.Http.Headers;using System.Net.Http.Json;
using var http = new HttpClient { BaseAddress = new Uri("https://api.elasticpay.co") };http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
var data = await http.GetFromJsonAsync<JsonElement>( "/api/v1/customers?limit=20");Response shape
{ "data": [], "has_more": false}Supported query parameters:
| Parameter | Description |
|---|---|
limit |
Number of results per page (default 20, max 100) |
starting_after |
Cursor: return results after this customer ID |
email |
Customers whose email or billing_email equals this address. Exact match, ignoring surrounding whitespace and letter case; several customers can share an address |
reference |
Your own customer reference. Exact match, ignoring surrounding whitespace and letter case |
test_clock |
Sandbox only: list customers attached to this test clock. Without it, clock-bound customers are excluded |
Update a customer
Section titled “Update a customer”Use PUT to update any mutable field. Fields not included in the request body are left unchanged.
curl -X PUT https://api.elasticpay.co/api/v1/customers/cus_0abc123def456ghi789jkl012mno \ -H "Authorization: Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{"email": "new-email@example.com"}'const res = await fetch( "https://api.elasticpay.co/api/v1/customers/cus_0abc123def456ghi789jkl012mno", { method: "PUT", headers: { "Authorization": "Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "Content-Type": "application/json", }, body: JSON.stringify({ email: "new-email@example.com" }), });const updated = await res.json();import requests
res = requests.put( "https://api.elasticpay.co/api/v1/customers/cus_0abc123def456ghi789jkl012mno", headers={ "Authorization": "Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "Content-Type": "application/json", }, json={"email": "new-email@example.com"},)updated = res.json()<?phpuse GuzzleHttp\Client;
$client = new Client([ 'base_uri' => 'https://api.elasticpay.co', 'headers' => [ 'Authorization' => 'Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'Content-Type' => 'application/json', ],]);
$res = $client->put('/api/v1/customers/cus_0abc123def456ghi789jkl012mno', [ 'json' => ['email' => 'new-email@example.com'],]);$updated = json_decode($res->getBody(), true);require 'faraday'require 'json'
conn = Faraday.new('https://api.elasticpay.co') do |f| f.request :json f.response :jsonendconn.headers['Authorization'] = 'Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'conn.headers['Content-Type'] = 'application/json'
res = conn.put('/api/v1/customers/cus_0abc123def456ghi789jkl012mno') do |req| req.body = { email: 'new-email@example.com' }endupdated = res.bodyusing System.Net.Http;using System.Net.Http.Headers;using System.Net.Http.Json;
using var http = new HttpClient { BaseAddress = new Uri("https://api.elasticpay.co") };http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
var res = await http.PutAsJsonAsync( "/api/v1/customers/cus_0abc123def456ghi789jkl012mno", new { email = "new-email@example.com" });var updated = await res.Content.ReadFromJsonAsync<JsonElement>();Delete a customer
Section titled “Delete a customer”Deleting a customer performs a soft-delete — the record is hidden from API responses and the portal but retained in the database for audit purposes. Payment intents and transactions associated with the customer are preserved.
curl -X DELETE https://api.elasticpay.co/api/v1/customers/cus_0abc123def456ghi789jkl012mno \ -H "Authorization: Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"await fetch( "https://api.elasticpay.co/api/v1/customers/cus_0abc123def456ghi789jkl012mno", { method: "DELETE", headers: { "Authorization": "Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, });import requests
requests.delete( "https://api.elasticpay.co/api/v1/customers/cus_0abc123def456ghi789jkl012mno", headers={"Authorization": "Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"},)<?phpuse GuzzleHttp\Client;
$client = new Client([ 'base_uri' => 'https://api.elasticpay.co', 'headers' => [ 'Authorization' => 'Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'Content-Type' => 'application/json', ],]);
$client->delete('/api/v1/customers/cus_0abc123def456ghi789jkl012mno');require 'faraday'require 'json'
conn = Faraday.new('https://api.elasticpay.co') do |f| f.request :json f.response :jsonendconn.headers['Authorization'] = 'Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'conn.headers['Content-Type'] = 'application/json'
conn.delete('/api/v1/customers/cus_0abc123def456ghi789jkl012mno')using System.Net.Http;using System.Net.Http.Headers;using System.Net.Http.Json;
using var http = new HttpClient { BaseAddress = new Uri("https://api.elasticpay.co") };http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
await http.DeleteAsync("/api/v1/customers/cus_0abc123def456ghi789jkl012mno");A successful deletion returns 204 No Content.
List a customer’s payment methods
Section titled “List a customer’s payment methods”Retrieve all saved payment methods (pm_xxx) for a customer:
curl https://api.elasticpay.co/api/v1/customers/cus_0abc123def456ghi789jkl012mno/payment_methods \ -H "Authorization: Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"const res = await fetch( "https://api.elasticpay.co/api/v1/customers/cus_0abc123def456ghi789jkl012mno/payment_methods", { headers: { "Authorization": "Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } });const { data } = await res.json();import requests
res = requests.get( "https://api.elasticpay.co/api/v1/customers/cus_0abc123def456ghi789jkl012mno/payment_methods", headers={"Authorization": "Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"},)data = res.json()<?phpuse GuzzleHttp\Client;
$client = new Client([ 'base_uri' => 'https://api.elasticpay.co', 'headers' => [ 'Authorization' => 'Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'Content-Type' => 'application/json', ],]);
$res = $client->get('/api/v1/customers/cus_0abc123def456ghi789jkl012mno/payment_methods');$data = json_decode($res->getBody(), true);require 'faraday'require 'json'
conn = Faraday.new('https://api.elasticpay.co') do |f| f.request :json f.response :jsonendconn.headers['Authorization'] = 'Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'conn.headers['Content-Type'] = 'application/json'
res = conn.get('/api/v1/customers/cus_0abc123def456ghi789jkl012mno/payment_methods')data = res.bodyusing System.Net.Http;using System.Net.Http.Headers;using System.Net.Http.Json;
using var http = new HttpClient { BaseAddress = new Uri("https://api.elasticpay.co") };http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
var data = await http.GetFromJsonAsync<JsonElement>( "/api/v1/customers/cus_0abc123def456ghi789jkl012mno/payment_methods");Attach a customer to a payment intent
Section titled “Attach a customer to a payment intent”Pass customer_id when creating a payment intent to associate it with a customer:
curl -X POST https://api.elasticpay.co/api/v1/payment_intents \ -H "Authorization: Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "amount": 4900, "currency": "AUD", "customer_id": "cus_0abc123def456ghi789jkl012mno" }'const res = await fetch("https://api.elasticpay.co/api/v1/payment_intents", { method: "POST", headers: { "Authorization": "Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "Content-Type": "application/json", }, body: JSON.stringify({ amount: 4900, currency: "AUD", customer_id: "cus_0abc123def456ghi789jkl012mno", }),});const pi = await res.json();import requests
res = requests.post( "https://api.elasticpay.co/api/v1/payment_intents", headers={ "Authorization": "Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "Content-Type": "application/json", }, json={"amount": 4900, "currency": "AUD", "customer_id": "cus_0abc123def456ghi789jkl012mno"},)pi = res.json()<?phpuse GuzzleHttp\Client;
$client = new Client([ 'base_uri' => 'https://api.elasticpay.co', 'headers' => [ 'Authorization' => 'Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'Content-Type' => 'application/json', ],]);
$res = $client->post('/api/v1/payment_intents', [ 'json' => [ 'amount' => 4900, 'currency' => 'AUD', 'customer_id' => 'cus_0abc123def456ghi789jkl012mno', ],]);$pi = json_decode($res->getBody(), true);require 'faraday'require 'json'
conn = Faraday.new('https://api.elasticpay.co') do |f| f.request :json f.response :jsonendconn.headers['Authorization'] = 'Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'conn.headers['Content-Type'] = 'application/json'
res = conn.post('/api/v1/payment_intents') do |req| req.body = { amount: 4900, currency: 'AUD', customer_id: 'cus_0abc123def456ghi789jkl012mno' }endpi = res.bodyusing System.Net.Http;using System.Net.Http.Headers;using System.Net.Http.Json;
using var http = new HttpClient { BaseAddress = new Uri("https://api.elasticpay.co") };http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
var res = await http.PostAsJsonAsync("/api/v1/payment_intents", new { amount = 4900, currency = "AUD", customer_id = "cus_0abc123def456ghi789jkl012mno",});var pi = await res.Content.ReadFromJsonAsync<JsonElement>();Required permissions
Section titled “Required permissions”| Operation | Required scope |
|---|---|
| Create / update / delete | customers:write |
| Fetch / list | customers:read |
Secret keys (sk_xxx) have both scopes by default.