Prerequisites and safety
Use a sandbox API key with catalog:read and compare:read, curl and jq. Search for at least two distinct products. Live requests require fresh source evidence and may consume usage credits.
Run the isolated exercise
- 1Find product identities
- 2Compare delivered prices
- 3Read observed price history
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
# 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'
# compare: expected HTTP 200
jq -e 'has("productId")' <<< "$DOCS_VARIABLES" >/dev/null
jq -e 'has("secondId")' <<< "$DOCS_VARIABLES" >/dev/null
DOCS_PATH="$(jq -cnr --argjson vars "$DOCS_VARIABLES" '"/v1/compare?ids=" + ($vars["productId"] | tostring | @uri) + "," + ($vars["secondId"] | 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 compare' >&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("products") else false end) and ($response["products"] | if type == "object" or type == "array" then has(0) else false end) and ($response["products"][0] | if type == "object" or type == "array" then has("canonical_product_id") else false end))) and ($response["products"][0]["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("products") else false end) and ($response["products"] | if type == "object" or type == "array" then has(1) else false end) and ($response["products"][1] | if type == "object" or type == "array" then has("canonical_product_id") else false end))) and ($response["products"][1]["canonical_product_id"] == ($vars["secondId"]))' >/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("products") else false end) and ($response["products"] | if type == "object" or type == "array" then has(0) else false end) and ($response["products"][0] | if type == "object" or type == "array" then has("canonical_product_id") else false end))) and ($response["products"][0]["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("products") else false end) and ($response["products"] | if type == "object" or type == "array" then has(1) else false end) and ($response["products"][1] | if type == "object" or type == "array" then has("canonical_product_id") else false end))) and ($response["products"][1]["canonical_product_id"] == ($vars["secondId"]))' >/dev/null
printf '%s\n' 'compare: verified'
# history: 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) + "/history?range=30d"')"
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 history' >&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("range") else false end))) and ($response["range"] == ("30d"))' >/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("summary") else false end) and ($response["summary"] | if type == "object" or type == "array" then has("observation_count") 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
jq -en --argjson vars "$DOCS_VARIABLES" --argjson response "$DOCS_RESPONSE" --argjson status "$DOCS_STATUS" '((($response | if type == "object" or type == "array" then has("range") else false end))) and ($response["range"] == ("30d"))' >/dev/null
printf '%s\n' 'history: verified'Verify the result
The comparison returns HTTP 200, products and each best_offer.delivered_price. History returns the same canonical_product_id, range, series and summary.observation_count. Zero observations mean no evidence for that interval, not zero price or a price forecast.
Recovery and cleanup
400: choose 2–4 distinct IDs; compare accepts no goal or range. Only history accepts these range values: 7d, 30d, 90d, 1y. 404: search again instead of guessing an ID. 503: verify source freshness. A null best_offer means no known available offer.
For 429 or temporary 5xx, respect Retry-After and use bounded retries. Keep API keys and response bodies out of logs and support tickets.