Unified Preference Centre
Capture, store, and manage fan preferences: marketing opt-ins and GDPR consent alongside personal interests for personalisation.
The Preferences Centre is a standalone service. Do not go through the Profile or Auth services to read or update preferences.
Environments
| Environment | Base URL |
|---|---|
| Stage | https://preferences-stage.cortextech.io |
| Production | https://preferences.cortextech.io |
Authentication
Three caller types are supported:
| Caller | Credential | Use case |
|---|---|---|
| Logged-in fan | Authorization: Bearer <jwt> | Read or update the fan's own preferences. |
| Anonymous | secret query parameter, or X-UUID: <secret> header | Read or update an anonymous visitor's preferences. |
| Admin / server | X-APP-ID: <app_id> and X-API-KEY: <api_key> | Manage the preference catalogue, bulk, and log endpoints. |
Fan-facing read endpoints also accept no credential at all. Without one, responses contain preference definitions with set and each option's selected field returned as null. Add a Bearer JWT or anonymous secret to get the caller's selections filled in.
Postman Collection
For the full interactive API Reference, see the Preferences Centre API Reference.
Preference Response Shape
{
"status": "success",
"data": [
{
"clientId": "DEMO",
"deprecated": false,
"id": 100,
"name": "Favourite player",
"description": "Choose your favourite player to stay up to date",
"key": "favouritePlayer",
"set": true,
"type": "OptIn",
"visualType": "Radio",
"lastUpdated": "2024-07-08T13:19:24.000Z",
"options": [
{
"deprecated": false,
"id": 3672,
"index": 1,
"value": "Player Name 1",
"linkedIds": [
{
"sourceSystem": "OPTA_FOOTBALL_PLAYER",
"sourceSystemId": "96454"
}
],
"metadata": {
"colour": "blue"
},
"selected": true
},
{
"deprecated": true,
"id": 3673,
"index": 2,
"value": "Player Name 2",
"metadata": null,
"linkedIds": null,
"selected": false
}
]
}
],
"metadata": {
"createdAt": "2024-07-08T13:19:24.000Z",
"pageItems": 1,
"totalItems": 1,
"totalPages": 1,
"pageNumber": 0,
"pageSize": 20,
"sort": "id"
}
}Preference Fields
| Field | Type | Description |
|---|---|---|
clientId | String | Your client ID. |
name | String | The preference name, e.g. "Favourite player". Set in Cortex, mandatory. |
description | String | Optional secondary label, e.g. "Choose your favourite player to stay up to date". |
key | String | Unique identifier. Mandatory, immutable once saved. |
set | Boolean (nullable) | Whether the fan has set this preference. null when no auth token is provided. |
type | String | "OptIn" if the Opt-in toggle is on in Cortex, otherwise an empty string. Use this to identify marketing preferences. |
visualType | String | Preferred input style: Checkbox, Select, Radio. |
lastUpdated | String (ISO 8601) | Last update to this preference or its options. |
deprecated | Boolean | Whether this preference is deprecated. Hide from new users. |
options | Array | Option entries for this preference. |
Option Fields
| Field | Type | Description |
|---|---|---|
id | Integer | Unique ID for the option. |
index | Integer | Sort order for display. |
value | String | Display label, e.g. "Player Name 1". |
selected | Boolean (nullable) | Whether the fan selected this option. null when no auth token is provided. |
deprecated | Boolean | Whether this option is deprecated. Hide from new users, but keep visible for fans who previously selected it. |
metadata | Object (nullable) | Custom key-value pairs configured in Cortex, e.g. {"colour": "blue"}. null if unset. |
linkedIds | Array (nullable) | Links to external records. Each entry has sourceSystem (e.g. "OPTA_FOOTBALL_PLAYER") and sourceSystemId. null if unset. |
Fan-facing Endpoints
List preferences
GET /v1/clients/{clientId}/client-preferences HTTP/1.1
Host: preferences.cortextech.io
Authorization: Bearer <fan_access_token>Returns all preferences for the client. With a Bearer JWT, set and selected reflect the caller's state; without one, both are null.
Get a single preference
GET /v1/clients/{clientId}/client-preferences/{preferenceKey} HTTP/1.1
Host: preferences.cortextech.io
Authorization: Bearer <fan_access_token>Update preferences
POST /v2/clientpreferences?clientId={clientId} HTTP/1.1
Host: preferences.cortextech.io
Authorization: Bearer <fan_access_token>
Content-Type: application/json
{
"preferences": [
{ "key": "favouritePlayer", "selectedOptionIds": [3672] },
{ "key": "marketingOptIn", "selectedOptionIds": [1] }
]
}Each entry replaces any previously-selected options for that key. Pass an empty selectedOptionIds to clear a preference.
Send preferences link
POST /v1/preferences/link?clientId={clientId} HTTP/1.1
Host: preferences.cortextech.io
Content-Type: application/json
{ "email": "[email protected]" }Sends a magic-link email containing a single-use secret. Always returns 200 OK, whether or not the address is known, so the endpoint is safe to expose unauthenticated without leaking which addresses are registered.
Anonymous Preferences
Anonymous preferences let a fan express preferences without creating an SSO account. The fan enters their email, receives a magic-link email, and clicks through to view or change their preferences.
Each anonymous visitor is identified by (clientId, email) and by an opaque UUID secret. The secret replaces the JWT on all read and write calls. Must be enabled per client (contact Cortex Support).
Flow
- Fan enters their email on the preferences page.
- Client calls
POST /v1/preferences/linkwith the email andclientId. - Preferences Centre upserts the anonymous user: creates one if needed, or rotates the secret if it already exists. Always
200 OK. - Magic-link email is sent with the secret appended, e.g.
https://preferences.example.com/preferences?secret=<uuid>. - Fan clicks the link.
- Client reads and writes using
?secret=<uuid>or theX-UUID: <uuid>header.
Example: read
GET /v1/clients/CPFC/client-preferences?secret=7b1c1f5c-9c2a-4e7e-9a6b-3b5e2c7f1a22 HTTP/1.1
Host: preferences.cortextech.ioEquivalent using the header:
GET /v1/clients/CPFC/client-preferences HTTP/1.1
Host: preferences.cortextech.io
X-UUID: 7b1c1f5c-9c2a-4e7e-9a6b-3b5e2c7f1a22Example: update
POST /v2/clientpreferences?clientId=CPFC HTTP/1.1
Host: preferences.cortextech.io
X-UUID: 7b1c1f5c-9c2a-4e7e-9a6b-3b5e2c7f1a22
Content-Type: application/json
{
"preferences": [
{ "key": "newsletter", "selectedOptionIds": [1] }
]
}Notes
- Anonymous users are keyed on (
clientId,email). The same email under two clients is two separate records. - The secret is the only credential. Treat the magic-link URL as a bearer credential; always deliver over HTTPS.
- Anonymous preferences are not automatically migrated when a fan later creates an SSO account. Reconciliation is the integrator's responsibility.
- No TTL: records persist until removed via Bulk remove anonymous users.
Admin Endpoints
All admin endpoints require X-APP-ID and X-API-KEY headers. See the Preferences Centre API Reference for full schemas.
| Method | Path | Purpose |
|---|---|---|
GET | /v1/admin/clients/{clientId}/client-preferences | List the client's preference catalogue |
POST | /v1/admin/clients/{clientId}/client-preferences | Create a preference definition |
GET | /v1/admin/clients/{clientId}/client-preferences/{id} | Fetch a preference definition |
PUT | /v1/admin/clients/{clientId}/client-preferences/{id} | Replace a preference definition |
PATCH | /v1/admin/clients/{clientId}/client-preferences/{id} | Update selected fields on a preference |
POST | /v1/admin/clients/{clientId}/client-preferences/{id}/options | Create an option |
PUT | /v1/admin/clients/{clientId}/client-preferences/{id}/options/{optionId} | Replace an option |
PATCH | /v1/admin/clients/{clientId}/client-preferences/{id}/options/{optionId} | Update selected fields on an option |
GET | /v1/admin/users/clientpreferences | Fetch a single user's preferences |
PUT | /v1/admin/users/clientpreferences | Update a single user's preferences |
Bulk APIs
Bulk endpoints cap the input list at 1,000 accounts per request and return a per-item response. Inspect the per-item error field rather than relying on the top-level status.
Bulk list user preferences (paged export)
GET /v1/admin/bulk/preferences?clientId={clientId}&page=0&size=100 HTTP/1.1
Host: preferences.cortextech.io
X-APP-ID: <app_id>
X-API-KEY: <api_key>Paginated export of all user preferences for a client.
Bulk list user preferences (specific accounts)
POST /v1/admin/bulk/preferences?clientId={clientId} HTTP/1.1
Host: preferences.cortextech.io
X-APP-ID: <app_id>
X-API-KEY: <api_key>
Content-Type: application/json
[
{ "userId": 28561, "anonymous": false },
{ "userId": 491736, "anonymous": true }
]Response:
{
"status": "success",
"data": [
{
"userId": 28561,
"anonymous": false,
"preferences": [
{ "key": "test-2", "selectedOptionIds": [1, 2] },
{ "key": "test-3", "selectedOptionIds": [5] }
]
},
{
"userId": 491736,
"anonymous": true,
"preferences": [
{ "key": "test-2", "selectedOptionIds": [3] }
]
}
],
"metadata": {
"createdAt": "2024-09-09T12:04:47.159484Z",
"pageItems": 2,
"totalItems": 2,
"totalPages": 1,
"pageNumber": 0,
"pageSize": 20,
"sort": "id"
}
}Bulk update user preferences
PUT /v1/admin/bulk/preferences?clientId={clientId} HTTP/1.1
Host: preferences.cortextech.io
X-APP-ID: <app_id>
X-API-KEY: <api_key>
Content-Type: application/json
[
{
"userId": 28561, "anonymous": false,
"preferences": [
{ "key": "test-2", "selectedOptionIds": [1, 2] }
]
},
{
"userId": 491736, "anonymous": true,
"preferences": [
{ "key": "test-3", "selectedOptionIds": [4] }
]
}
]Up to 10 concurrent updates server-side per request. The top-level status is success whenever the request is well-formed; check each entry's error field.
Bulk add user accounts
POST /v1/admin/bulk/accounts?clientId={clientId} HTTP/1.1
Host: preferences.cortextech.io
X-APP-ID: <app_id>
X-API-KEY: <api_key>
Content-Type: application/json
[
{ "email": "[email protected]" },
{ "userId": 12345 }
]Provide either email (upserts an anonymous user) or userId (associates a secret with an existing SSO user), not both. Optionally pass uuid to preserve an existing secret. Response returns each entry populated with userId, email, and uuid, or an error.
Bulk remove anonymous users
DELETE /v1/admin/bulk/anonymous?clientId={clientId} HTTP/1.1
Host: preferences.cortextech.io
X-APP-ID: <app_id>
X-API-KEY: <api_key>
Content-Type: application/json
[
"[email protected]",
"[email protected]"
]Soft-deletes anonymous user records for the supplied addresses. Unknown or already-deleted addresses are a no-op.
Preferences Log
An append-only audit log of every preference change. Log entries are never amended or deleted.
GET /v1/admin/logs/clientpreferences?clientId={clientId}&userId=12345&page=0&size=50 HTTP/1.1
Host: preferences.cortextech.io
X-APP-ID: <app_id>
X-API-KEY: <api_key>| Query param | Required | Description |
|---|---|---|
clientId | Yes | Client to read. |
userId | Yes | User to filter by. For anonymous users, use the Preferences Centre user ID. |
anonymous | No | true if userId refers to an anonymous user. Default false. |
page | No | Zero-indexed page number. Default 0. |
size | No | Page size (1-100). Default 50. |
Response:
{
"status": "success",
"metadata": {
"sort": "id",
"totalItems": 3,
"pageSize": 20,
"pageItems": 3,
"createdAt": "2024-09-09T12:04:47.159484Z",
"totalPages": 1,
"pageNumber": 0
},
"data": [
{
"id": 1,
"key": "test-1",
"name": "Test #1",
"optionId": null,
"value": "none",
"updatedByUserId": 12345,
"updatedBy": "USER",
"updatedBySystem": "PREFERENCES_CENTRE",
"createdAt": "2024-09-09T12:04:47.159484Z"
},
{
"id": 2,
"key": "test-2",
"name": "Test #2",
"optionId": 1,
"value": "Yes",
"updatedByUserId": 12345,
"updatedBy": "USER",
"updatedBySystem": "PREFERENCES_CENTRE",
"createdAt": "2024-09-09T12:05:46.124318Z"
}
]
}Each entry records a single option change:
key/name: the preference that changed.optionId/value: the option selected.optionIdisnullandvalueis"none"when all options were cleared.updatedByUserId: the fan whose preference changed.updatedBy: who initiated the change. One of:USER: the fan via a fan-facing endpoint.ADMIN: an operator via admin endpoints or the Cortex UI.GRPC: an internal service via gRPC.BULK: a bulk update via/v1/admin/bulk/preferences.
updatedBySystem: the source system.PREFERENCES_CENTREfor changes via this service.
Suppressions
A suppression excludes an email address from delivery for a given client and provider. The email tier checks the suppression list before sending and drops the message on match.
Suppressions sit alongside preferences rather than overriding them: preferences decide whether the fan wants a message, suppressions decide whether the provider will accept one for that address. A fan can have an active marketing opt-in and still be suppressed (e.g. because a previous delivery bounced).
Data model
| Field | Description |
|---|---|
client_id | Client the suppression applies to. Never crosses between clients. |
email | Address to suppress. |
provider | Delivery provider, e.g. COMMUNICATOR. Scoped to the client's configured email provider. |
reason | Free-text reason, e.g. "Unsubscribed", "Bounced", "Complaint". |
created_at | When the suppression was first recorded. |
last_modified | When the suppression was last changed. |
A fan can have multiple suppression rows, one per (client_id, provider) pair.
Set and unset
Suppressions are driven by the Consent Management Platform (CMP), which reacts to unsubscribe clicks, provider bounce and complaint webhooks, and admin actions. The CMP streams add and remove events into the Preferences Centre.
Writes are served over the internal gRPC service ConsentManagementPlatformService:
AddSuppression(client-streaming): adds suppressions specifyingclient_idandSuppression { email, provider, reason }.RemoveSuppression(client-streaming): removes suppressions byclient_id,email, andprovider.
These RPCs are internal only and not exposed on preferences.cortextech.io. Integrations should go through the CMP; contact Cortex Support to connect a new suppression source.
The service validates every call:
- The client must be configured to support suppressions.
- The request
providermust match the client's configured email provider.
Fetch
Reads are also on ConsentManagementPlatformService:
GetSuppressionsByEmail(unary): all suppressions for a givenclient_idandemail. Use for per-fan lookups.GetSuppressionsByProvider(server-streaming): all suppressions for a givenclient_idandprovider. Use for reconciliation and exports.
Example GetSuppressionsByEmail response:
{
"client_id": "CORTEXTECH",
"suppressions": [
{
"email": "[email protected]",
"provider": "COMMUNICATOR",
"reason": "Unsubscribed"
}
]
}An empty suppressions array means the address is not suppressed. For ad-hoc per-fan checks, suppression status is also visible in Fan Manager in Cortex.
Preferences in Cortex
Preferences are managed in two Cortex areas: Fan Preferences and Fan Manager.
Fan Preferences
View, set up, and edit the preferences offered to fans.
- Left-hand menu, under Data & Marketing, click Fan Preferences.
- Select a preference to open its edit page.
- Set Name, Description, Input values (multi-select, radio, dropdown), Preference key, and the opt-in toggle.
Below that, add Options, Linked IDs, and metadata.
Fan Manager
Inspect and edit an individual fan's data.
- Left-hand menu, under Data & Marketing, select Fan Manager.
- Click the row for the fan, or filter by user name, email, or user ID.
- Edit profile data, preferences, entitlements, linked accounts, ticketing, devices, and opt-ins.
Updated 9 days ago
