On this page
Build a customer search integration
Assemble a dataset, file source, indexer, table, and searchable entity from Aurelia Utilities data, then verify the result through the Golden API.
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
ADMINrole for configuration and synchronization; - an access token and the Golden base URL; and
curlandjqfor 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: trueidentifies the source key. This exercise uses exactly one such column so repeated loads address the same record.- A
DATEcolumn needs a parse pattern. Setvalidation: "PARSER"andsource: "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_IDfor a lookup reference. That token means “an identifier resolved against a lookup table” and requires aforeignKey. Without one, building the dataset fails with “Invalid null or empty foreign key.” The sample uses it properly onprovinceandsupplyPoint; 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.
8. Verify search
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:
- Delete entity
tutorial_customers. - Delete resources
tutorial_customer_sourceandtutorial_customer_indexer. - Delete table
tutorial_customers. - Update
tutorial_customer_datasettoidentityType:"DEFAULT"andmerger:nullto break its reference to the merger. - Delete
tutorial_customer_merger, thentutorial_customer_dataset. - Delete the uploaded file by the identifier returned at upload; confirm its
stored name is
tutorial-customers.csvfirst.
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.