Update Profile
Update the authenticated customer's profile fields
Update one or more fields on the authenticated customer's profile. Only the fields you include in the request body will be changed.
Endpoint
PATCH /profileAuthentication
Requires a valid JWT access token.
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9...Request body
For an existing profile, all fields are optional and only included fields are changed. If the authenticated account does not yet have a customer profile, this endpoint creates and links one; firstName is then required.
| Field | Type | Description |
|---|---|---|
firstName | string | Customer's first name |
lastName | string | Customer's last name |
title | string | Title code (mr, mrs, ms, miss, mx, dr, prof) or any other title text. An empty string clears it. See Title and nickname |
customTitle | string | Custom title text, used with title: "other" |
nickname | string | The name the customer likes to be called. An empty string clears it |
phone | string | Phone number, in international format (e.g. +1234567890) or national format with phoneCountry |
phoneCountry | string | Two-letter ISO country code (e.g. GB) for a national-format phone. See National-format phone numbers |
profilePhoto | string | URL to a profile photo |
portalSlug | string | Optional active portal whose enabled profile parameters additionally restrict edits |
profileParameters | object | Customer-editable parameters keyed by system ID, custom schema ID, name, or normalized name |
emails | array | Full email list using { address, isPrimary, order } entries |
phones | array | Full phone list using { number, isPrimary, order } entries, each with an optional countryCode for a national-format number |
addresses | array | Full address list, up to 10. Requires portalSlug for a portal with address editing enabled. See the DOM Package address rules |
taxId | string | Personal tax number. Requires portalSlug. See Update the portal Tax ID |
displayName is read-only. Tiquo derives it from firstName and lastName whenever either name changes.
When both phones and phone are supplied, the full phones array takes precedence. Sending an empty phone removes the current primary phone. Parameter values are validated against the organization's customer-editable parameter definitions.
During initial profile creation, the account email from the JWT becomes the primary email and the single phone field (with phoneCountry, when supplied) is used. Apply full emails or phones arrays in a later update after the profile exists.
The DOM Package also accepts a browser File or Blob for profilePhoto. When a file is provided, the SDK uploads the image through the profile photo upload endpoints before saving the customer profile.
createdAt and firstActiveAt are read-only system fields. They are returned on the customer object but are not accepted as profile updates. The Hosted and DOM packages also remove these fields from dynamically constructed update payloads.
New secondary email addresses cannot be introduced through PATCH /profile.
Verify ownership with the two endpoints below first.
Title and nickname
title accepts one of the codes mr, mrs, ms, miss, mx, dr, or prof. Case, spaces, and dots are ignored, so "Mr." is saved as mr. Any other text, such as "Sir", is saved as a custom title: the profile then returns title: "other" with the text in customTitle. You can also send title: "other" with customTitle directly. Sending a code clears any custom title text.
Custom titles are shortened to 40 characters and nicknames to 60 characters. A title, customTitle, or nickname value longer than 200 characters, or one that isn't a string, is rejected with 400. Send an empty string to clear the title or nickname.
{
"title": "dr",
"nickname": "Sam"
}The profile stores the code, not a translated word. Show mrs as "Mrs", "Mme" or "Frau" to suit your website's language.
National-format phone numbers
Phone numbers are saved in international format. A number that starts with + or 00 keeps its own calling code. For a number typed without one, such as 07911 123456, send the country the customer selected in phoneCountry so Tiquo adds the right calling code:
{
"phone": "07911 123456",
"phoneCountry": "GB"
}This saves +447911123456. Without phoneCountry, no calling code is inferred. In a full phones list, give each national-format entry its own countryCode:
{
"phones": [
{ "number": "07911 123456", "countryCode": "GB", "isPrimary": true, "order": 0 },
{ "number": "06 12 34 56 78", "countryCode": "FR", "isPrimary": false, "order": 1 }
]
}countryCode is only used while saving and is not returned on the profile. Values that aren't two-letter country codes are ignored.
Customer parameters and consent
The authenticated token determines which customer is updated; callers cannot
choose another customer's ID. Each parameter must allow customer editing.
When a portalSlug is supplied, the portal must be active and each parameter
must also be enabled in that portal's configured profile parameters.
{
"profileParameters": {
"system_marketing_opt_in": true,
"system_sms_opt_in": false
}
}true opts in and false opts out independently for Marketing and SMS Opt-In.
The server records an opt-in timestamp on a false-to-true transition, preserves
it while consent stays true, and removes it on opt-out. The timestamp is not an
editable field. Returned consent values are serialized JSON strings such as
{"Opt-In":"true"}; profile responses do not expose the internal opt-in timestamp.
Parameter values may be strings, numbers, booleans, string arrays, or null.
Omitted keys remain unchanged. Empty values clear optional fields; required
fields must remain populated. In profile updates, null is an empty value (and
sets consent to false), unlike Admin API parameter writes where null is ignored.
Prefer explicit true/false for consent. Multiselects accept string arrays;
compound fields accept JSON strings. Configured option/type validation and edit
permissions are enforced before the mutation commits.
This is the supported browser flow for editing customer preferences.
createEnquiry() and POST /enquiries can only initialize missing customer-editable parameters through customer.customerParameterValues or legacy customer.details. They cannot overwrite existing values, including seeded consent defaults.
Add a verified secondary email
Send a verification code
POST /profile/email-verification/sendcurl -X POST "https://edge.tiquo.app/api/client/v1/profile/email-verification/send" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiJ9..." \
-H "Content-Type: application/json" \
-d '{"email":"second@example.com","portalSlug":"members"}'portalSlug is optional and selects the portal branding for the verification
email. The six-digit code expires after 10 minutes. A successful response contains success: true and may include delivery: { id, senderEmail }. Delivery metadata is optional; success does not guarantee delivery, and disposable addresses may be silently skipped.
Verify and add the address
POST /profile/email-verification/verifycurl -X POST "https://edge.tiquo.app/api/client/v1/profile/email-verification/verify" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiJ9..." \
-H "Content-Type: application/json" \
-d '{"email":"second@example.com","otp":"123456"}'{
"success": true,
"data": {
"email": "second@example.com",
"emails": [
{ "address": "customer@example.com", "isPrimary": true, "order": 0 },
{ "address": "second@example.com", "isPrimary": false, "order": 1 }
]
}
}A profile can have up to five email addresses. The new address must not already be on this profile or another customer profile in the organization. Send and verify requests are rate limited, and repeated incorrect codes require a new code. Both endpoints accept JSON bodies up to 8,192 bytes.
| Status | Email-verification error |
|---|---|
400 | Invalid email or code, expired or missing code, or the five-address limit was reached |
401 | Missing, invalid, or expired token |
403 | No customer profile is linked |
404 | Portal branding was not found when sending a code |
409 | Address already used, or verification email delivery rejected; a rejected delivery can include bounced: true and senderEmail |
413 | JSON body exceeds 8,192 bytes |
429 | Too many requests or incorrect attempts; wait or request a new code |
500 | Verification could not be completed |
Example request
curl -X PATCH "https://edge.tiquo.app/api/client/v1/profile" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiJ9..." \
-H "Content-Type: application/json" \
-d '{
"firstName": "John",
"lastName": "Smith",
"phone": "+1234567890"
}'Response
{
"success": true,
"data": {
"customer": {
"id": "cust_456",
"firstName": "John",
"lastName": "Smith",
"displayName": "John Smith",
"customerNumber": "CUST-000001",
"email": "customer@example.com",
"phone": "+1234567890",
"profilePhoto": null,
"createdAt": 1704067200000,
"firstActiveAt": 1706745600000,
"emails": [
{ "address": "customer@example.com", "isPrimary": true, "order": 0 }
],
"phones": [
{ "number": "+1234567890", "isPrimary": true, "order": 0 }
],
"parameters": [],
"parameterValues": {}
}
}
}For an existing profile, a successful response is 200 OK and includes the updated customer at both customer and data.customer. The customer has the same fields as Get Profile, including addresses, taxId, and titleText.
When the endpoint creates a missing profile, it returns 201 Created. The new customer is always available at the top-level customer property. If profileParameters were also applied, the response additionally includes data.customer.
Profile photo uploads
For browser-based uploads, use the DOM Package uploadProfilePhoto() helper where possible.
If you are calling the Customer API directly, profile photo uploads use two steps:
- Request an upload URL.
- Upload the image to that URL, then finalise the profile photo with the returned
storageId.
Create upload URL
POST /profile/photo-upload-urlThe request body can be an empty JSON object.
curl -X POST "https://edge.tiquo.app/api/client/v1/profile/photo-upload-url" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiJ9..." \
-H "Content-Type: application/json" \
-d '{}'The response includes a temporary uploadUrl.
{
"success": true,
"data": {
"uploadUrl": "https://..."
}
}Upload the image file to the returned URL using POST and the image content type. The upload response includes a storageId.
Finalise profile photo
POST /profile/photocurl -X POST "https://edge.tiquo.app/api/client/v1/profile/photo" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiJ9..." \
-H "Content-Type: application/json" \
-d '{
"storageId": "kg2..."
}'The upload must be a JPEG, PNG, GIF, or WebP image and be no larger than 5 MB.
The response includes the updated customer, the resolved profilePhoto URL, and the storageId.
Errors
| Status | Description |
|---|---|
400 | First name is missing during profile creation, a title or nickname is invalid, or one or more field or parameter values are invalid |
401 | Invalid or expired access token |
500 | Internal server error |
Code examples
JavaScript
const response = await fetch('https://edge.tiquo.app/api/client/v1/profile', {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
firstName: 'John',
lastName: 'Smith',
}),
});
const { success, customer, data, error } = await response.json();
if (success) {
const updatedCustomer = data?.customer ?? customer;
console.log('Profile updated:', updatedCustomer.displayName);
}Swift
var request = URLRequest(url: URL(string: "https://edge.tiquo.app/api/client/v1/profile")!)
request.httpMethod = "PATCH"
request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONEncoder().encode(["firstName": "John", "lastName": "Smith"])
let (data, _) = try await URLSession.shared.data(for: request)Kotlin
val body = """{"firstName": "John", "lastName": "Smith"}"""
.toRequestBody("application/json".toMediaType())
val request = Request.Builder()
.url("https://edge.tiquo.app/api/client/v1/profile")
.patch(body)
.addHeader("Authorization", "Bearer $accessToken")
.build()
val response = client.newCall(request).execute()Update the portal Tax ID
Send taxId with the active portalSlug when that portal enables its built-in Tax ID field. The server removes spaces, dots, and hyphens, converts the value to uppercase, and accepts up to 40 characters after normalisation. An empty string clears it.
{
"portalSlug": "members",
"taxId": "PT 123456789"
}Omit taxId when it has not changed. It cannot be changed through this endpoint without the enabled portal field. The Tax ID is a built-in invoice field, separate from profileParameters.