curl --request POST \
--url https://app.eat-now.io/api/partner/v1/customers \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "Jane Doe",
"email": "jane@example.com",
"phone_number": "+33612345678",
"external_id": "crm-4821",
"tags": [
"VIP"
],
"newsletter_consent": true,
"on_match": "update"
}
'import requests
url = "https://app.eat-now.io/api/partner/v1/customers"
payload = {
"name": "Jane Doe",
"email": "jane@example.com",
"phone_number": "+33612345678",
"external_id": "crm-4821",
"tags": ["VIP"],
"newsletter_consent": True,
"on_match": "update"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'Jane Doe',
email: 'jane@example.com',
phone_number: '+33612345678',
external_id: 'crm-4821',
tags: ['VIP'],
newsletter_consent: true,
on_match: 'update'
})
};
fetch('https://app.eat-now.io/api/partner/v1/customers', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.eat-now.io/api/partner/v1/customers",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Jane Doe',
'email' => 'jane@example.com',
'phone_number' => '+33612345678',
'external_id' => 'crm-4821',
'tags' => [
'VIP'
],
'newsletter_consent' => true,
'on_match' => 'update'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.eat-now.io/api/partner/v1/customers"
payload := strings.NewReader("{\n \"name\": \"Jane Doe\",\n \"email\": \"jane@example.com\",\n \"phone_number\": \"+33612345678\",\n \"external_id\": \"crm-4821\",\n \"tags\": [\n \"VIP\"\n ],\n \"newsletter_consent\": true,\n \"on_match\": \"update\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.eat-now.io/api/partner/v1/customers")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"Jane Doe\",\n \"email\": \"jane@example.com\",\n \"phone_number\": \"+33612345678\",\n \"external_id\": \"crm-4821\",\n \"tags\": [\n \"VIP\"\n ],\n \"newsletter_consent\": true,\n \"on_match\": \"update\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.eat-now.io/api/partner/v1/customers")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"Jane Doe\",\n \"email\": \"jane@example.com\",\n \"phone_number\": \"+33612345678\",\n \"external_id\": \"crm-4821\",\n \"tags\": [\n \"VIP\"\n ],\n \"newsletter_consent\": true,\n \"on_match\": \"update\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"id": "cus_123",
"external_id": "crm-4821",
"name": "Jane Doe",
"email": "jane@example.com",
"phone_number": "+33612345678",
"country_code": "FR",
"lang": "FR",
"tags": [
"VIP"
],
"note": "Prefers the terrace",
"custom_fields": {
"field_birthday": {
"name": "Birthday",
"value": "1990-04-12"
}
},
"newsletter_consent": true,
"marketing_opt_outs": [],
"blacklisted": false,
"stats": {
"reservation_count": 7,
"confirmed_count": 6,
"no_show_count": 1,
"feedback_count": 2,
"average_feedback_score": 4.5,
"total_amount_paid": 42000,
"average_basket": 2800,
"last_reservation_date": "2026-09-12"
},
"created_at": "2025-11-03T18:22:10.000Z",
"updated_at": "2026-09-12T21:04:55.000Z"
},
"result": "matched"
}{
"data": {
"id": "<string>",
"external_id": "<string>",
"name": "<string>",
"email": "jsmith@example.com",
"phone_number": "<string>",
"country_code": "<string>",
"lang": "FR",
"tags": [
"<string>"
],
"note": "<string>",
"custom_fields": {},
"newsletter_consent": true,
"marketing_opt_outs": [
"EMAIL"
],
"blacklisted": true,
"stats": {
"reservation_count": 123,
"confirmed_count": 123,
"no_show_count": 123,
"feedback_count": 123,
"average_feedback_score": 123,
"total_amount_paid": 123,
"average_basket": 123,
"last_reservation_date": "<string>"
},
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z"
},
"result": "created"
}{
"error": {
"code": "AUTHENTICATION_REQUIRED",
"message": "<string>",
"request_id": "<string>",
"details": {}
}
}{
"error": {
"code": "AUTHENTICATION_REQUIRED",
"message": "<string>",
"request_id": "<string>",
"details": {}
}
}{
"error": {
"code": "AUTHENTICATION_REQUIRED",
"message": "<string>",
"request_id": "<string>",
"details": {}
}
}{
"error": {
"code": "AUTHENTICATION_REQUIRED",
"message": "<string>",
"request_id": "<string>",
"details": {}
}
}{
"error": {
"code": "AUTHENTICATION_REQUIRED",
"message": "<string>",
"request_id": "<string>",
"details": {}
}
}Create a customer
Creates a customer without any reservation. The restaurant’s existing customers are matched first, on external_id, then phone number (national and international spellings of a number match), then email (case-insensitive). Replaying a call that succeeded finds the customer it created, as long as it carries an external_id, a phone or an email; two identical calls sent at the same time can still both create one. external_id is stored on creation and with on_match: update, not by a plain return_existing match. on_match decides what a match does: return_existing (default) returns the existing customer untouched, update writes the provided fields onto it, create_new skips matching and always creates. The response says which happened in result: HTTP 201 for created, 200 for matched and updated. A blacklisted customer is returned like any other, with blacklisted: true. Requires CUSTOMERS_WRITE and CUSTOMERS_READ (the response is the full customer).
curl --request POST \
--url https://app.eat-now.io/api/partner/v1/customers \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "Jane Doe",
"email": "jane@example.com",
"phone_number": "+33612345678",
"external_id": "crm-4821",
"tags": [
"VIP"
],
"newsletter_consent": true,
"on_match": "update"
}
'import requests
url = "https://app.eat-now.io/api/partner/v1/customers"
payload = {
"name": "Jane Doe",
"email": "jane@example.com",
"phone_number": "+33612345678",
"external_id": "crm-4821",
"tags": ["VIP"],
"newsletter_consent": True,
"on_match": "update"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'Jane Doe',
email: 'jane@example.com',
phone_number: '+33612345678',
external_id: 'crm-4821',
tags: ['VIP'],
newsletter_consent: true,
on_match: 'update'
})
};
fetch('https://app.eat-now.io/api/partner/v1/customers', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.eat-now.io/api/partner/v1/customers",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Jane Doe',
'email' => 'jane@example.com',
'phone_number' => '+33612345678',
'external_id' => 'crm-4821',
'tags' => [
'VIP'
],
'newsletter_consent' => true,
'on_match' => 'update'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.eat-now.io/api/partner/v1/customers"
payload := strings.NewReader("{\n \"name\": \"Jane Doe\",\n \"email\": \"jane@example.com\",\n \"phone_number\": \"+33612345678\",\n \"external_id\": \"crm-4821\",\n \"tags\": [\n \"VIP\"\n ],\n \"newsletter_consent\": true,\n \"on_match\": \"update\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.eat-now.io/api/partner/v1/customers")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"Jane Doe\",\n \"email\": \"jane@example.com\",\n \"phone_number\": \"+33612345678\",\n \"external_id\": \"crm-4821\",\n \"tags\": [\n \"VIP\"\n ],\n \"newsletter_consent\": true,\n \"on_match\": \"update\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.eat-now.io/api/partner/v1/customers")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"Jane Doe\",\n \"email\": \"jane@example.com\",\n \"phone_number\": \"+33612345678\",\n \"external_id\": \"crm-4821\",\n \"tags\": [\n \"VIP\"\n ],\n \"newsletter_consent\": true,\n \"on_match\": \"update\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"id": "cus_123",
"external_id": "crm-4821",
"name": "Jane Doe",
"email": "jane@example.com",
"phone_number": "+33612345678",
"country_code": "FR",
"lang": "FR",
"tags": [
"VIP"
],
"note": "Prefers the terrace",
"custom_fields": {
"field_birthday": {
"name": "Birthday",
"value": "1990-04-12"
}
},
"newsletter_consent": true,
"marketing_opt_outs": [],
"blacklisted": false,
"stats": {
"reservation_count": 7,
"confirmed_count": 6,
"no_show_count": 1,
"feedback_count": 2,
"average_feedback_score": 4.5,
"total_amount_paid": 42000,
"average_basket": 2800,
"last_reservation_date": "2026-09-12"
},
"created_at": "2025-11-03T18:22:10.000Z",
"updated_at": "2026-09-12T21:04:55.000Z"
},
"result": "matched"
}{
"data": {
"id": "<string>",
"external_id": "<string>",
"name": "<string>",
"email": "jsmith@example.com",
"phone_number": "<string>",
"country_code": "<string>",
"lang": "FR",
"tags": [
"<string>"
],
"note": "<string>",
"custom_fields": {},
"newsletter_consent": true,
"marketing_opt_outs": [
"EMAIL"
],
"blacklisted": true,
"stats": {
"reservation_count": 123,
"confirmed_count": 123,
"no_show_count": 123,
"feedback_count": 123,
"average_feedback_score": 123,
"total_amount_paid": 123,
"average_basket": 123,
"last_reservation_date": "<string>"
},
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z"
},
"result": "created"
}{
"error": {
"code": "AUTHENTICATION_REQUIRED",
"message": "<string>",
"request_id": "<string>",
"details": {}
}
}{
"error": {
"code": "AUTHENTICATION_REQUIRED",
"message": "<string>",
"request_id": "<string>",
"details": {}
}
}{
"error": {
"code": "AUTHENTICATION_REQUIRED",
"message": "<string>",
"request_id": "<string>",
"details": {}
}
}{
"error": {
"code": "AUTHENTICATION_REQUIRED",
"message": "<string>",
"request_id": "<string>",
"details": {}
}
}{
"error": {
"code": "AUTHENTICATION_REQUIRED",
"message": "<string>",
"request_id": "<string>",
"details": {}
}
}Authorizations
Restaurant-scoped partner API token passed as Authorization: Bearer <token>.
Body
Customer creation payload. Existing customers are matched first (see on_match), so a call replayed after it succeeded finds the customer it created.
1 - 200E.164 (+33612345678) recommended. A national number is read with the restaurant's country.
3 - 40Defaults to the restaurant's language.
FR, EN, IT, ES, DE, PT Partner-side identifier of the customer.
1 - 255Replaces all the customer's tags.
1001 - 100Internal staff note.
10000Custom field values keyed by field id (see GET /api/partner/v1/customer-fields). Only the given fields change; null clears one. The value must fit the field type: TEXT → string, NUMBER → number within min/max, DATE → YYYY-MM-DD, BOOLEAN → boolean, SELECT → one option value, MULTI_SELECT → an array of distinct option values (at most max_items).
Show child attributes
Show child attributes
Newsletter consent. false withdraws it; channel opt-outs (marketing_opt_outs) are managed by EatNow.
What to do when the provided details match an existing customer of the restaurant (same external_id, then same phone number, then same email): return_existing keeps the existing customer untouched and uses it; update writes the provided fields onto the existing customer; create_new skips matching and creates another customer anyway.
return_existing, update, create_new Response
Existing customer returned (result: matched) or updated (result: updated)
