On this page

Assemble a searchable customer entity from supported Golden resources, one resource at a time, and verify it through the API. Use a non-production environment.

The six teaching rows adapt customers from the Aurelia Utilities sample. This exercise uses an explicit sourceRef column to teach column identity; the supplied sample uses _id and _source instead. The configurations are intentionally different.

Before you begin

You need:

  • the ADMIN role for configuration and synchronization;
  • an access token and the Golden base URL; and
  • curl and jq for upload, configuration, and verification.
export GOLDEN_URL="https://golden.example.com"
export GOLDEN_ADMIN_TOKEN="<administrator-token>"

The examples show resource definitions as JSON. In Resources, choose the matching resource type, enter the fields, select Test, and save only after validation succeeds. For API-based resource management, see Manage resources.

Every identifier below is prefixed tutorial_. Keep that prefix: the cleanup step depends on it.

1. Prepare the input

Create tutorial-customers.csv with these synthetic teaching rows:

sourceRef;fullName;taxId;email;phone;street;city;postcode;contractDate
CRM-0001;Ana García Muñoz;12345678Z;ana.garcia@example.com;611000000;Calle Mayor 12;Madrid;28013;2024-01-01
CRM-0002;Luis Fernández Ortiz;23456789S;luis.fernandez@example.com;611000137;Calle Fuencarral 45;Madrid;28004;2024-02-02
CRM-0003;María López Peña;34567890Q;maria.lopez@example.com;611000274;Carrer de Ferran 9;Barcelona;08002;2024-03-03
CRM-0004;Jordi Puig Sabaté;45678901T;jordi.puig@example.com;611000411;Carrer de Girona 88;Barcelona;08013;2024-04-04
CRM-0005;Carmen Ruiz Ibáñez;56789012P;carmen.ruiz@example.com;611000548;Calle Alemanes 3;Sevilla;41004;2024-05-05
CRM-0006;Miguel Santos Aguiló;67890123D;miguel.santos@example.com;611000685;Carrer de la Pau 21;Valencia;46002;2024-06-06

Save it as UTF-8. After loading, verify that accented names retain their characters.

Do not substitute real customer records for this exercise.

2. Define the dataset

The dataset defines the record schema. Each column’s token determines its semantic treatment; for example, NAME_LAST and TEXT have different roles.

{
  "type": "dataset",
  "_id": "tutorial_customer_dataset",
  "description": "Tutorial customers, from the Aurelia CRM export",
  "dataType": "RECORD",
  "identityType": "DEFAULT",
  "columns": [
    { "key": "sourceRef", "description": "Source reference", "type": "TOKEN", "token": "TEXT", "identity": true, "empty": false },
    { "key": "fullName", "description": "Full name", "type": "TOKEN", "token": "NAME", "empty": true },
    { "key": "taxId", "description": "Tax ID (NIF)", "type": "TOKEN", "token": "ID", "empty": true },
    { "key": "email", "description": "Email", "type": "TOKEN", "token": "EMAIL", "empty": true, "validation": "DEFAULT" },
    { "key": "phone", "description": "Phone", "type": "TOKEN", "token": "PHONE", "empty": true },
    { "key": "street", "description": "Street", "type": "TOKEN", "token": "ADDRESS_STREET", "empty": true },
    { "key": "city", "description": "City", "type": "TOKEN", "token": "ADDRESS_CITY", "empty": true },
    { "key": "postcode", "description": "Postcode", "type": "TOKEN", "token": "ADDRESS_POSTCODE", "empty": true },
    { "key": "contractDate", "description": "Contract date", "type": "TOKEN", "token": "DATE", "empty": true, "validation": "PARSER", "source": "yyyy-MM-dd" }
  ]
}

Save this initial resource as tutorial-dataset.json. It temporarily uses DEFAULT so it can be created before its merger. Do not load any records yet. Create it through the API:

jq -Rs '{create:true, resource:.}' tutorial-dataset.json |
curl --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $GOLDEN_ADMIN_TOKEN" \
  -H "Content-Type: application/json" --data-binary @- \
  "$GOLDEN_URL/api/resources"

Create tutorial-merger.json:

{
  "type":"merger-weight",
  "_id":"tutorial_customer_merger",
  "description":"Prefer the most recent contract date",
  "dataset":"tutorial_customer_dataset",
  "mergeType":"DATE",
  "mergeSort":"HIGHEST_WEIGHT",
  "key":"contractDate"
}

Submit it with the same command, substituting tutorial-merger.json. Now complete the dataset’s identity configuration:

jq '.identityType="COLUMN" | .merger="tutorial_customer_merger" |
    {create:false,resource:tojson}' tutorial-dataset.json |
curl --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $GOLDEN_ADMIN_TOKEN" \
  -H "Content-Type: application/json" --data-binary @- \
  "$GOLDEN_URL/api/resources"

COLUMN and SCRIPT identities require a merger referencing the same dataset. Creating the dataset first, then the merger, then completing the dataset resolves that dependency. Three other choices matter:

  • identity: true identifies the source key. This exercise uses exactly one such column so repeated loads address the same record.
  • A DATE column needs a parse pattern. Set validation: "PARSER" and source: "yyyy-MM-dd". Without it the column has no usable formatter, and the entity fails validation with “Column contractDate is not ready: define a valid formatter.”
  • Use FOREIGN_ID for a lookup reference. That token means “an identifier resolved against a lookup table” and requires a foreignKey. Without one, building the dataset fails with “Invalid null or empty foreign key.” The sample uses it properly on province and supplyPoint; see Dataset tokens.

3. Upload the file

curl --fail-with-body --silent --show-error \
  --request POST \
  --header "Authorization: Bearer ${GOLDEN_ADMIN_TOKEN}" \
  --form "file=@tutorial-customers.csv" \
  --form "name=tutorial-customers.csv" \
  --form "description=Tutorial customers" \
  "${GOLDEN_URL}/api/files"

Keep the returned file identifier for cleanup. The stored name is what the source pattern matches.

4. Configure the file source

{
  "type": "source-file",
  "_id": "tutorial_customer_source",
  "description": "Tutorial CSV",
  "dataset": "tutorial_customer_dataset",
  "inputPattern": "tutorial-customers.csv",
  "format": "CSV",
  "separator": ";",
  "header": true,
  "ignoreQuotes": false
}

Save as tutorial-source.json and submit with the step 2 resource-creation command. The separator is a semicolon, matching the file in step 1. Use the resource test to confirm Golden finds the uploaded file and parses the nine columns.

5. Configure search mappings

The indexer declares how records can be reached. Each mapping becomes a searchable key.

{
  "type": "indexer",
  "_id": "tutorial_customer_indexer",
  "description": "Tutorial customer search",
  "dataset": "tutorial_customer_dataset",
  "defaultKeyOptions": ["IGNORE_CASE", "IGNORE_ACCENTS"],
  "mappings": [
    {
      "id": "by-tax-id",
      "description": "Exact tax identifier",
      "duplicates": false,
      "matching": "EXACT",
      "type": "COLUMNS",
      "combine": false,
      "keys": ["taxId"]
    },
    {
      "id": "by-email",
      "description": "Exact email, case and accents normalized",
      "duplicates": false,
      "matching": "EXACT",
      "type": "COLUMNS",
      "combine": false,
      "keys": ["email"]
    },
    {
      "id": "by-name-postcode",
      "description": "Fuzzy full name combined with postcode",
      "duplicates": false,
      "matching": "FUZZY",
      "type": "COLUMNS",
      "combine": true,
      "keys": ["fullName", "postcode"],
      "fuzzyMaximumTypos": 2
    }
  ]
}

Save as tutorial-indexer.json and submit with the step 2 resource-creation command.

duplicates: false makes these search keys without starting a duplicate review workflow. The sample’s indexer is the same three mappings with duplicates: true, plus a fourth on phone — which is how the same configuration becomes a deduplication entity rather than a search one.

6. Create the table

curl --fail-with-body --silent --show-error \
  --request POST \
  --header "Authorization: Bearer ${GOLDEN_ADMIN_TOKEN}" \
  --header "Content-Type: application/json" \
  --data '{
    "table": "tutorial_customers",
    "description": "Tutorial customers",
    "dataset": "tutorial_customer_dataset",
    "history": true,
    "auditable": true
  }' \
  "${GOLDEN_URL}/api/tables"

Table identifiers use underscores rather than hyphens. tutorial_customers, not tutorial-customers. Golden normalizes hyphens to underscores and letters to lowercase. Use the canonical identifier returned by Golden in subsequent calls. Resource identifiers also accept hyphens.

history: true retains contributors for supported recovery; merge undo also has other preconditions. See Tables.

7. Create and synchronize the entity

Create the entity and its source schedule explicitly:

curl --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $GOLDEN_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"create":true,"id":"tutorial_customers","description":"Tutorial customers",
       "table":"tutorial_customers","type":"SEARCH",
       "indexer":"tutorial_customer_indexer",
       "sources":[{"resource":"tutorial_customer_source",
                   "cronType":"EVERY_DAY_AT_2AM","incremental":false}]}' \
  "$GOLDEN_URL/api/entities"

The source needs a valid schedule even for this manual exercise. Leave the entity’s automatic mode off. Start a full source load and the indexing needed for this search entity, without delivery:

curl --fail-with-body --silent --show-error --request PUT \
  -H "Authorization: Bearer $GOLDEN_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":"tutorial_customers","loadMask":"FULL",
       "indexClassificationMask":"CHANGES","sinkMask":"NONE"}' \
  "$GOLDEN_URL/api/entities/synchronize" > tutorial-sync.json
RUN_ID=$(jq -er '.run.id' tutorial-sync.json)

curl --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $GOLDEN_ADMIN_TOKEN" \
  "$GOLDEN_URL/api/jobs/runs/$RUN_ID"

Follow the run until SUCCEEDED; stop and inspect its error if it fails. Confirm the entity reports READY and six records. CHANGES selects the necessary indexing for SEARCH without requesting duplicate classification.

A record-shaped query:

curl --fail-with-body --silent --show-error \
  --request POST \
  --header "Authorization: Bearer ${GOLDEN_ADMIN_TOKEN}" \
  --header "Content-Type: application/json" \
  --data '{
    "record": {"taxId": "12345678Z"},
    "options": {},
    "pageNumber": 0,
    "pageSize": 10
  }' \
  "${GOLDEN_URL}/api/golden/tutorial_customers/search"

The response reports count: 1 and Ana’s record in result.

Ask the entity what it can be searched by, rather than assuming:

curl --fail-with-body --silent --show-error \
  --header "Authorization: Bearer ${GOLDEN_ADMIN_TOKEN}" \
  "${GOLDEN_URL}/api/golden/tutorial_customers/search/capabilities"
{
  "entity": "tutorial_customers",
  "searchable": true,
  "freeTextIndexed": true,
  "fields": [
    { "mapping": "by-tax-id", "columns": ["taxId"], "matching": "EXACT", "combined": false },
    { "mapping": "by-email", "columns": ["email"], "matching": "EXACT", "combined": false },
    { "mapping": "by-name-postcode", "columns": ["fullName", "postcode"], "matching": "FUZZY", "combined": true }
  ]
}

This is the same answer the web application uses to build its search bar placeholder. An integration that reads it does not have to hard-code the entity’s keys.

Finally, confirm the fuzzy key does what it was configured to do. Search for Maria Lopez Pena — no accents, which is not how the record is stored:

curl --fail-with-body --silent --show-error \
  --request POST \
  --header "Authorization: Bearer ${GOLDEN_ADMIN_TOKEN}" \
  --header "Content-Type: application/json" \
  --data '{"query": "Maria Lopez Pena", "pageNumber": 0, "pageSize": 10}' \
  "${GOLDEN_URL}/api/golden/tutorial_customers/search/text"

María López Peña comes back. The accents were normalized at index time by IGNORE_ACCENTS, which is the reason that option is on the indexer rather than on each query.

Also inspect the stored rows directly through GET /api/tables/tutorial_customers/records?pageNumber=0&pageSize=10.

Clean up

Stop any active run and keep automatic mode disabled. Remove only the objects created by this exercise, in this order:

  1. Delete entity tutorial_customers.
  2. Delete resources tutorial_customer_source and tutorial_customer_indexer.
  3. Delete table tutorial_customers.
  4. Update tutorial_customer_dataset to identityType:"DEFAULT" and merger:null to break its reference to the merger.
  5. Delete tutorial_customer_merger, then tutorial_customer_dataset.
  6. Delete the uploaded file by the identifier returned at upload; confirm its stored name is tutorial-customers.csv first.

The routes are DELETE /api/entities/{id}, DELETE /api/resources/id/{id}, DELETE /api/tables/{name}, and DELETE /api/files/{id}. Use the same resource save wrapper with create:false for step 4. A dependency refusal means a reference remains; inspect it instead of broadening the deletion.

Next steps

You built a SEARCH entity. To see what the same assembly looks like with duplicate resolution on top, install the Aurelia Utilities sample: 378 supplied records, related search mappings, and a classifier and merger deciding what the indexes find.

Golden 3.0.0 · Published 2026-10-04