Customer API

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 /profile

Authentication

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.

FieldTypeDescription
firstNamestringCustomer's first name
lastNamestringCustomer's last name
titlestringTitle code (mr, mrs, ms, miss, mx, dr, prof) or any other title text. An empty string clears it. See Title and nickname
customTitlestringCustom title text, used with title: "other"
nicknamestringThe name the customer likes to be called. An empty string clears it
phonestringPhone number, in international format (e.g. +1234567890) or national format with phoneCountry
phoneCountrystringTwo-letter ISO country code (e.g. GB) for a national-format phone. See National-format phone numbers
profilePhotostringURL to a profile photo
portalSlugstringOptional active portal whose enabled profile parameters additionally restrict edits
profileParametersobjectCustomer-editable parameters keyed by system ID, custom schema ID, name, or normalized name
emailsarrayFull email list using { address, isPrimary, order } entries
phonesarrayFull phone list using { number, isPrimary, order } entries, each with an optional countryCode for a national-format number
addressesarrayFull address list, up to 10. Requires portalSlug for a portal with address editing enabled. See the DOM Package address rules
taxIdstringPersonal 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.

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/send
curl -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/verify
curl -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.

StatusEmail-verification error
400Invalid email or code, expired or missing code, or the five-address limit was reached
401Missing, invalid, or expired token
403No customer profile is linked
404Portal branding was not found when sending a code
409Address already used, or verification email delivery rejected; a rejected delivery can include bounced: true and senderEmail
413JSON body exceeds 8,192 bytes
429Too many requests or incorrect attempts; wait or request a new code
500Verification 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:

  1. Request an upload URL.
  2. Upload the image to that URL, then finalise the profile photo with the returned storageId.

Create upload URL

POST /profile/photo-upload-url

The 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/photo
curl -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

StatusDescription
400First name is missing during profile creation, a title or nickname is invalid, or one or more field or parameter values are invalid
401Invalid or expired access token
500Internal 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.

On this page