Prerequisites and safety
Resolve a canonical product ID and inspect its current offers. Start with a sandbox key with catalog:read. The browser exercise uses isolated representative data and needs no key.
Run the isolated exercise
- 1Reject an invalid limit
- 2Find product identities
- 3Inspect the resolved product
Run from your server
set -euo pipefail
# Requires bash, curl and jq. Use an isolated sandbox API key.
DOCS_BASE_URL="${DOCS_BASE_URL:-https://hintadata.fi}"
: "${HINTADATA_API_KEY:?Set a sandbox API key privately}"
DOCS_VARIABLES=$(jq -cn '{}')
DOCS_RESPONSE_FILE=$(mktemp)
trap 'rm -f -- "$DOCS_RESPONSE_FILE"' EXIT
# invalid: expected HTTP 400
DOCS_PATH="$(jq -cnr --argjson vars "$DOCS_VARIABLES" '"/v1/search?q=sony&limit=0"')"
DOCS_STATUS=$(curl --silent --show-error --max-time 20 --output "$DOCS_RESPONSE_FILE" --write-out '%{http_code}' -X 'GET' "$DOCS_BASE_URL$DOCS_PATH" -H "Authorization: Bearer $HINTADATA_API_KEY")
[[ "$DOCS_STATUS" == '400' ]] || { printf '%s\n' 'Unexpected HTTP status at invalid' >&2; exit 1; }
DOCS_RESPONSE=$(jq -c . "$DOCS_RESPONSE_FILE")
jq -en --argjson vars "$DOCS_VARIABLES" --argjson response "$DOCS_RESPONSE" --argjson status "$DOCS_STATUS" '((($response | if type == "object" or type == "array" then has("error") else false end) and ($response["error"] | if type == "object" or type == "array" then has("code") else false end))) and ($response["error"]["code"] == ("invalid_request"))' >/dev/null
printf '%s\n' 'invalid: verified'
# search: expected HTTP 200
DOCS_PATH="$(jq -cnr --argjson vars "$DOCS_VARIABLES" '"/v1/search?q=sony&limit=2"')"
DOCS_STATUS=$(curl --silent --show-error --max-time 20 --output "$DOCS_RESPONSE_FILE" --write-out '%{http_code}' -X 'GET' "$DOCS_BASE_URL$DOCS_PATH" -H "Authorization: Bearer $HINTADATA_API_KEY")
[[ "$DOCS_STATUS" == '200' ]] || { printf '%s\n' 'Unexpected HTTP status at search' >&2; exit 1; }
DOCS_RESPONSE=$(jq -c . "$DOCS_RESPONSE_FILE")
jq -en --argjson vars "$DOCS_VARIABLES" --argjson response "$DOCS_RESPONSE" --argjson status "$DOCS_STATUS" '((($response | if type == "object" or type == "array" then has("query") else false end))) and ($response["query"] == ("sony"))' >/dev/null
jq -en --argjson vars "$DOCS_VARIABLES" --argjson response "$DOCS_RESPONSE" --argjson status "$DOCS_STATUS" '((($response | if type == "object" or type == "array" then has("items") else false end) and ($response["items"] | if type == "object" or type == "array" then has(0) else false end) and ($response["items"][0] | if type == "object" or type == "array" then has("canonical_product_id") else false end)) == true)' >/dev/null
DOCS_VARIABLES=$(jq -cn --argjson vars "$DOCS_VARIABLES" --argjson response "$DOCS_RESPONSE" '$vars + {"productId":$response["items"][0]["canonical_product_id"],"secondId":$response["items"][1]["canonical_product_id"]}')
printf '%s\n' 'search: verified'
# inspect: expected HTTP 200
jq -e 'has("productId")' <<< "$DOCS_VARIABLES" >/dev/null
DOCS_PATH="$(jq -cnr --argjson vars "$DOCS_VARIABLES" '"/v1/products/" + ($vars["productId"] | tostring | @uri) + ""')"
DOCS_STATUS=$(curl --silent --show-error --max-time 20 --output "$DOCS_RESPONSE_FILE" --write-out '%{http_code}' -X 'GET' "$DOCS_BASE_URL$DOCS_PATH" -H "Authorization: Bearer $HINTADATA_API_KEY")
[[ "$DOCS_STATUS" == '200' ]] || { printf '%s\n' 'Unexpected HTTP status at inspect' >&2; exit 1; }
DOCS_RESPONSE=$(jq -c . "$DOCS_RESPONSE_FILE")
jq -en --argjson vars "$DOCS_VARIABLES" --argjson response "$DOCS_RESPONSE" --argjson status "$DOCS_STATUS" '((($response | if type == "object" or type == "array" then has("canonical_product_id") else false end))) and ($response["canonical_product_id"] == ($vars["productId"]))' >/dev/null
jq -en --argjson vars "$DOCS_VARIABLES" --argjson response "$DOCS_RESPONSE" --argjson status "$DOCS_STATUS" '((($response | if type == "object" or type == "array" then has("offers") else false end) and ($response["offers"] | if type == "object" or type == "array" then has(0) else false end) and ($response["offers"][0] | if type == "object" or type == "array" then has("delivered_price") else false end)) == true)' >/dev/null
jq -en --argjson vars "$DOCS_VARIABLES" --argjson response "$DOCS_RESPONSE" --argjson status "$DOCS_STATUS" '((($response | if type == "object" or type == "array" then has("canonical_product_id") else false end))) and ($response["canonical_product_id"] == ($vars["productId"]))' >/dev/null
printf '%s\n' 'inspect: verified'Verify the result
Expect HTTP 200 with query, items, total and next_offset. Save canonical_product_id, not a display name. An empty items array is a valid search result: refine q before reading an ID. X-Data-Mode: demo is not live evidence.
Recovery and cleanup
400 invalid_request: set q to 1–120 characters and limit to 1–100. 401: replace the invalid key; 403: check scope and subscription. 503 source_unavailable/source_stale: ask the source owner to publish fresh data. Do not present preview data as a live fallback.
For 429 or temporary 5xx, respect Retry-After and use bounded retries. Keep API keys and response bodies out of logs and support tickets.
Optional live task measurement
Explicit consent records a start and the first successful live search in the same authenticated workspace and environment. The receipt expires after 30 minutes and grants no API access. Reading, sandbox runs and test-key requests never count as live success. Only task, locale, release, timestamps and an opaque correlation reference are sent to analytics; no search text, email, key or response body. Omit both measurement headers to opt out. If measurement is unavailable, continue with the normal search command above.
set -euo pipefail
# Optional: explicit analytics consent; use a LIVE key only when ready for live data.
ATTEMPT=$(curl --fail-with-body --silent --show-error https://hintadata.fi/api/docs/tasks \
-H "Authorization: Bearer $HINTADATA_API_KEY" -H 'X-Analytics-Consent: granted' \
-H 'Content-Type: application/json' \
--data '{"task":"first-product-search","locale":"en"}' | jq -er '.receipt')
curl --fail-with-body --silent --show-error --get https://hintadata.fi/v1/search \
-H "Authorization: Bearer $HINTADATA_API_KEY" -H 'X-Analytics-Consent: granted' \
-H "X-Docs-Attempt: $ATTEMPT" --data-urlencode 'q=sony' --data 'limit=2' | jq .
unset ATTEMPT