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

EnvironmentBase URL
Stagehttps://preferences-stage.cortextech.io
Productionhttps://preferences.cortextech.io

Authentication

Three caller types are supported:

CallerCredentialUse case
Logged-in fanAuthorization: Bearer <jwt>Read or update the fan's own preferences.
Anonymoussecret query parameter, or X-UUID: <secret> headerRead or update an anonymous visitor's preferences.
Admin / serverX-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

Preferences Centre 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

FieldTypeDescription
clientIdStringYour client ID.
nameStringThe preference name, e.g. "Favourite player". Set in Cortex, mandatory.
descriptionStringOptional secondary label, e.g. "Choose your favourite player to stay up to date".
keyStringUnique identifier. Mandatory, immutable once saved.
setBoolean (nullable)Whether the fan has set this preference. null when no auth token is provided.
typeString"OptIn" if the Opt-in toggle is on in Cortex, otherwise an empty string. Use this to identify marketing preferences.
visualTypeStringPreferred input style: Checkbox, Select, Radio.
lastUpdatedString (ISO 8601)Last update to this preference or its options.
deprecatedBooleanWhether this preference is deprecated. Hide from new users.
optionsArrayOption entries for this preference.

Option Fields

FieldTypeDescription
idIntegerUnique ID for the option.
indexIntegerSort order for display.
valueStringDisplay label, e.g. "Player Name 1".
selectedBoolean (nullable)Whether the fan selected this option. null when no auth token is provided.
deprecatedBooleanWhether this option is deprecated. Hide from new users, but keep visible for fans who previously selected it.
metadataObject (nullable)Custom key-value pairs configured in Cortex, e.g. {"colour": "blue"}. null if unset.
linkedIdsArray (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

  1. Fan enters their email on the preferences page.
  2. Client calls POST /v1/preferences/link with the email and clientId.
  3. Preferences Centre upserts the anonymous user: creates one if needed, or rotates the secret if it already exists. Always 200 OK.
  4. Magic-link email is sent with the secret appended, e.g. https://preferences.example.com/preferences?secret=<uuid>.
  5. Fan clicks the link.
  6. Client reads and writes using ?secret=<uuid> or the X-UUID: <uuid> header.

Example: read

GET /v1/clients/CPFC/client-preferences?secret=7b1c1f5c-9c2a-4e7e-9a6b-3b5e2c7f1a22 HTTP/1.1
Host: preferences.cortextech.io

Equivalent using the header:

GET /v1/clients/CPFC/client-preferences HTTP/1.1
Host: preferences.cortextech.io
X-UUID: 7b1c1f5c-9c2a-4e7e-9a6b-3b5e2c7f1a22

Example: 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.

MethodPathPurpose
GET/v1/admin/clients/{clientId}/client-preferencesList the client's preference catalogue
POST/v1/admin/clients/{clientId}/client-preferencesCreate 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}/optionsCreate 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/clientpreferencesFetch a single user's preferences
PUT/v1/admin/users/clientpreferencesUpdate 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 paramRequiredDescription
clientIdYesClient to read.
userIdYesUser to filter by. For anonymous users, use the Preferences Centre user ID.
anonymousNotrue if userId refers to an anonymous user. Default false.
pageNoZero-indexed page number. Default 0.
sizeNoPage 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. optionId is null and value is "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_CENTRE for 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

FieldDescription
client_idClient the suppression applies to. Never crosses between clients.
emailAddress to suppress.
providerDelivery provider, e.g. COMMUNICATOR. Scoped to the client's configured email provider.
reasonFree-text reason, e.g. "Unsubscribed", "Bounced", "Complaint".
created_atWhen the suppression was first recorded.
last_modifiedWhen 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 specifying client_id and Suppression { email, provider, reason }.
  • RemoveSuppression (client-streaming): removes suppressions by client_id, email, and provider.

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 provider must match the client's configured email provider.

Fetch

Reads are also on ConsentManagementPlatformService:

  • GetSuppressionsByEmail (unary): all suppressions for a given client_id and email. Use for per-fan lookups.
  • GetSuppressionsByProvider (server-streaming): all suppressions for a given client_id and provider. 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.

  1. Left-hand menu, under Data & Marketing, click Fan Preferences.
  2. Select a preference to open its edit page.
  3. 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.

  1. Left-hand menu, under Data & Marketing, select Fan Manager.
  2. Click the row for the fan, or filter by user name, email, or user ID.
  3. Edit profile data, preferences, entitlements, linked accounts, ticketing, devices, and opt-ins.