curl --request PUT \
--url https://api.protodesk.io/v1/customers \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"externalId": "renter_123",
"name": "Maya",
"email": "maya@example.com",
"phone": "+628123456789",
"company": "Bali Villa Hub",
"language": "id",
"timezone": "Asia/Jakarta",
"metadata": {
"tier": "gold"
}
}
'import requests
url = "https://api.protodesk.io/v1/customers"
payload = {
"externalId": "renter_123",
"name": "Maya",
"email": "maya@example.com",
"phone": "+628123456789",
"company": "Bali Villa Hub",
"language": "id",
"timezone": "Asia/Jakarta",
"metadata": { "tier": "gold" }
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PUT',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
externalId: 'renter_123',
name: 'Maya',
email: 'maya@example.com',
phone: '+628123456789',
company: 'Bali Villa Hub',
language: 'id',
timezone: 'Asia/Jakarta',
metadata: {tier: 'gold'}
})
};
fetch('https://api.protodesk.io/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://api.protodesk.io/v1/customers",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode([
'externalId' => 'renter_123',
'name' => 'Maya',
'email' => 'maya@example.com',
'phone' => '+628123456789',
'company' => 'Bali Villa Hub',
'language' => 'id',
'timezone' => 'Asia/Jakarta',
'metadata' => [
'tier' => 'gold'
]
]),
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://api.protodesk.io/v1/customers"
payload := strings.NewReader("{\n \"externalId\": \"renter_123\",\n \"name\": \"Maya\",\n \"email\": \"maya@example.com\",\n \"phone\": \"+628123456789\",\n \"company\": \"Bali Villa Hub\",\n \"language\": \"id\",\n \"timezone\": \"Asia/Jakarta\",\n \"metadata\": {\n \"tier\": \"gold\"\n }\n}")
req, _ := http.NewRequest("PUT", 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.put("https://api.protodesk.io/v1/customers")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"externalId\": \"renter_123\",\n \"name\": \"Maya\",\n \"email\": \"maya@example.com\",\n \"phone\": \"+628123456789\",\n \"company\": \"Bali Villa Hub\",\n \"language\": \"id\",\n \"timezone\": \"Asia/Jakarta\",\n \"metadata\": {\n \"tier\": \"gold\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.protodesk.io/v1/customers")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"externalId\": \"renter_123\",\n \"name\": \"Maya\",\n \"email\": \"maya@example.com\",\n \"phone\": \"+628123456789\",\n \"company\": \"Bali Villa Hub\",\n \"language\": \"id\",\n \"timezone\": \"Asia/Jakarta\",\n \"metadata\": {\n \"tier\": \"gold\"\n }\n}"
response = http.request(request)
puts response.read_body{
"id": "cus_x6q5vl75f5d53m7oet2k4r5w6a",
"externalId": "renter_123",
"name": "Maya",
"email": "maya@example.com",
"phone": "+628123456789",
"company": "Bali Villa Hub",
"language": "id",
"timezone": "Asia/Jakarta",
"avatarUrl": "https://cdn.example.com/avatars/maya.png",
"claimedEmail": "<string>",
"emailStatus": "<string>",
"lastActiveChannel": "whatsapp",
"firstContactAt": "2023-11-07T05:31:56Z",
"lastSeenAt": "2023-11-07T05:31:56Z",
"tags": [
"<string>"
],
"metadata": {},
"createdAt": "2023-11-07T05:31:56Z",
"updatedAt": "2023-11-07T05:31:56Z",
"conversationCount": 123
}{
"id": "cus_x6q5vl75f5d53m7oet2k4r5w6a",
"externalId": "renter_123",
"name": "Maya",
"email": "maya@example.com",
"phone": "+628123456789",
"company": "Bali Villa Hub",
"language": "id",
"timezone": "Asia/Jakarta",
"avatarUrl": "https://cdn.example.com/avatars/maya.png",
"claimedEmail": "<string>",
"emailStatus": "<string>",
"lastActiveChannel": "whatsapp",
"firstContactAt": "2023-11-07T05:31:56Z",
"lastSeenAt": "2023-11-07T05:31:56Z",
"tags": [
"<string>"
],
"metadata": {},
"createdAt": "2023-11-07T05:31:56Z",
"updatedAt": "2023-11-07T05:31:56Z",
"conversationCount": 123
}{
"error": {
"code": "route.not_found",
"message": "Not found",
"requestId": "req_x6q5vl75f5d53m7oet2k4r5w6a"
}
}Upsert a customer by externalId
Creates a customer when no customer with the given externalId exists (201), or patches the existing one (200). Only the provided fields are written on update. externalId is the natural key and is required.
curl --request PUT \
--url https://api.protodesk.io/v1/customers \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"externalId": "renter_123",
"name": "Maya",
"email": "maya@example.com",
"phone": "+628123456789",
"company": "Bali Villa Hub",
"language": "id",
"timezone": "Asia/Jakarta",
"metadata": {
"tier": "gold"
}
}
'import requests
url = "https://api.protodesk.io/v1/customers"
payload = {
"externalId": "renter_123",
"name": "Maya",
"email": "maya@example.com",
"phone": "+628123456789",
"company": "Bali Villa Hub",
"language": "id",
"timezone": "Asia/Jakarta",
"metadata": { "tier": "gold" }
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PUT',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
externalId: 'renter_123',
name: 'Maya',
email: 'maya@example.com',
phone: '+628123456789',
company: 'Bali Villa Hub',
language: 'id',
timezone: 'Asia/Jakarta',
metadata: {tier: 'gold'}
})
};
fetch('https://api.protodesk.io/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://api.protodesk.io/v1/customers",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode([
'externalId' => 'renter_123',
'name' => 'Maya',
'email' => 'maya@example.com',
'phone' => '+628123456789',
'company' => 'Bali Villa Hub',
'language' => 'id',
'timezone' => 'Asia/Jakarta',
'metadata' => [
'tier' => 'gold'
]
]),
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://api.protodesk.io/v1/customers"
payload := strings.NewReader("{\n \"externalId\": \"renter_123\",\n \"name\": \"Maya\",\n \"email\": \"maya@example.com\",\n \"phone\": \"+628123456789\",\n \"company\": \"Bali Villa Hub\",\n \"language\": \"id\",\n \"timezone\": \"Asia/Jakarta\",\n \"metadata\": {\n \"tier\": \"gold\"\n }\n}")
req, _ := http.NewRequest("PUT", 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.put("https://api.protodesk.io/v1/customers")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"externalId\": \"renter_123\",\n \"name\": \"Maya\",\n \"email\": \"maya@example.com\",\n \"phone\": \"+628123456789\",\n \"company\": \"Bali Villa Hub\",\n \"language\": \"id\",\n \"timezone\": \"Asia/Jakarta\",\n \"metadata\": {\n \"tier\": \"gold\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.protodesk.io/v1/customers")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"externalId\": \"renter_123\",\n \"name\": \"Maya\",\n \"email\": \"maya@example.com\",\n \"phone\": \"+628123456789\",\n \"company\": \"Bali Villa Hub\",\n \"language\": \"id\",\n \"timezone\": \"Asia/Jakarta\",\n \"metadata\": {\n \"tier\": \"gold\"\n }\n}"
response = http.request(request)
puts response.read_body{
"id": "cus_x6q5vl75f5d53m7oet2k4r5w6a",
"externalId": "renter_123",
"name": "Maya",
"email": "maya@example.com",
"phone": "+628123456789",
"company": "Bali Villa Hub",
"language": "id",
"timezone": "Asia/Jakarta",
"avatarUrl": "https://cdn.example.com/avatars/maya.png",
"claimedEmail": "<string>",
"emailStatus": "<string>",
"lastActiveChannel": "whatsapp",
"firstContactAt": "2023-11-07T05:31:56Z",
"lastSeenAt": "2023-11-07T05:31:56Z",
"tags": [
"<string>"
],
"metadata": {},
"createdAt": "2023-11-07T05:31:56Z",
"updatedAt": "2023-11-07T05:31:56Z",
"conversationCount": 123
}{
"id": "cus_x6q5vl75f5d53m7oet2k4r5w6a",
"externalId": "renter_123",
"name": "Maya",
"email": "maya@example.com",
"phone": "+628123456789",
"company": "Bali Villa Hub",
"language": "id",
"timezone": "Asia/Jakarta",
"avatarUrl": "https://cdn.example.com/avatars/maya.png",
"claimedEmail": "<string>",
"emailStatus": "<string>",
"lastActiveChannel": "whatsapp",
"firstContactAt": "2023-11-07T05:31:56Z",
"lastSeenAt": "2023-11-07T05:31:56Z",
"tags": [
"<string>"
],
"metadata": {},
"createdAt": "2023-11-07T05:31:56Z",
"updatedAt": "2023-11-07T05:31:56Z",
"conversationCount": 123
}{
"error": {
"code": "route.not_found",
"message": "Not found",
"requestId": "req_x6q5vl75f5d53m7oet2k4r5w6a"
}
}Authorizations
Use a workspace API key in the Authorization header. Keys use pk_live_<key_id> for production and pk_test<key_id>_ for test mode.
Headers
Optional retry key for safely replaying create requests. Reusing the same key with a different request body returns 409.
8 - 255"8db8e596-7c1a-4fd5-a728-4d6c99f4e66b"
Body
externalId is the natural key and is required. On update, only the provided fields are written; omitted fields are left unchanged.
Your stable id for this customer; the upsert key.
"renter_123"
"Maya"
"maya@example.com"
"+628123456789"
"Bali Villa Hub"
"id"
"Asia/Jakarta"
Replaces the full customer metadata object. Evelin reads account context only from the reserved tenant object and only permits id, name, plan, status, domain, region, createdAt, and updatedAt.
{ "tier": "gold" }
Response
Existing customer patched.
Every key is always present; optional fields are null when unset (never empty strings).
"cus_x6q5vl75f5d53m7oet2k4r5w6a"
"renter_123"
"Maya"
"maya@example.com"
"+628123456789"
"Bali Villa Hub"
"id"
"Asia/Jakarta"
"https://cdn.example.com/avatars/maya.png"
An email the customer claimed but that is not verified.
Email deliverability/suppression state (e.g. active, unsubscribed, bounced).
"whatsapp"
Number of conversations for this customer. Present on list responses only.
